##// END OF EJS Templates
archive: add XZ support if built with Python 3
David Demelier -
r43210:c04e0836 default
parent child Browse files
Show More
@@ -1,350 +1,355 b''
1 1 # archival.py - revision archival for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import gzip
11 11 import os
12 12 import struct
13 13 import tarfile
14 14 import time
15 15 import zipfile
16 16 import zlib
17 17
18 18 from .i18n import _
19 19 from .node import (
20 20 nullrev,
21 21 )
22 22
23 23 from . import (
24 24 error,
25 25 formatter,
26 26 match as matchmod,
27 27 pycompat,
28 28 scmutil,
29 29 util,
30 30 vfs as vfsmod,
31 31 )
32 32 stringio = util.stringio
33 33
34 34 # from unzip source code:
35 35 _UNX_IFREG = 0x8000
36 36 _UNX_IFLNK = 0xa000
37 37
38 38 def tidyprefix(dest, kind, prefix):
39 39 '''choose prefix to use for names in archive. make sure prefix is
40 40 safe for consumers.'''
41 41
42 42 if prefix:
43 43 prefix = util.normpath(prefix)
44 44 else:
45 45 if not isinstance(dest, bytes):
46 46 raise ValueError('dest must be string if no prefix')
47 47 prefix = os.path.basename(dest)
48 48 lower = prefix.lower()
49 49 for sfx in exts.get(kind, []):
50 50 if lower.endswith(sfx):
51 51 prefix = prefix[:-len(sfx)]
52 52 break
53 53 lpfx = os.path.normpath(util.localpath(prefix))
54 54 prefix = util.pconvert(lpfx)
55 55 if not prefix.endswith('/'):
56 56 prefix += '/'
57 57 # Drop the leading '.' path component if present, so Windows can read the
58 58 # zip files (issue4634)
59 59 if prefix.startswith('./'):
60 60 prefix = prefix[2:]
61 61 if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix:
62 62 raise error.Abort(_('archive prefix contains illegal components'))
63 63 return prefix
64 64
65 65 exts = {
66 66 'tar': ['.tar'],
67 67 'tbz2': ['.tbz2', '.tar.bz2'],
68 68 'tgz': ['.tgz', '.tar.gz'],
69 69 'zip': ['.zip'],
70 'txz': ['.txz', '.tar.xz']
70 71 }
71 72
72 73 def guesskind(dest):
73 74 for kind, extensions in exts.iteritems():
74 75 if any(dest.endswith(ext) for ext in extensions):
75 76 return kind
76 77 return None
77 78
78 79 def _rootctx(repo):
79 80 # repo[0] may be hidden
80 81 for rev in repo:
81 82 return repo[rev]
82 83 return repo[nullrev]
83 84
84 85 # {tags} on ctx includes local tags and 'tip', with no current way to limit
85 86 # that to global tags. Therefore, use {latesttag} as a substitute when
86 87 # the distance is 0, since that will be the list of global tags on ctx.
87 88 _defaultmetatemplate = br'''
88 89 repo: {root}
89 90 node: {ifcontains(rev, revset("wdir()"), "{p1node}{dirty}", "{node}")}
90 91 branch: {branch|utf8}
91 92 {ifeq(latesttagdistance, 0, join(latesttag % "tag: {tag}", "\n"),
92 93 separate("\n",
93 94 join(latesttag % "latesttag: {tag}", "\n"),
94 95 "latesttagdistance: {latesttagdistance}",
95 96 "changessincelatesttag: {changessincelatesttag}"))}
96 97 '''[1:] # drop leading '\n'
97 98
98 99 def buildmetadata(ctx):
99 100 '''build content of .hg_archival.txt'''
100 101 repo = ctx.repo()
101 102
102 103 opts = {
103 104 'template': repo.ui.config('experimental', 'archivemetatemplate',
104 105 _defaultmetatemplate)
105 106 }
106 107
107 108 out = util.stringio()
108 109
109 110 fm = formatter.formatter(repo.ui, out, 'archive', opts)
110 111 fm.startitem()
111 112 fm.context(ctx=ctx)
112 113 fm.data(root=_rootctx(repo).hex())
113 114
114 115 if ctx.rev() is None:
115 116 dirty = ''
116 117 if ctx.dirty(missing=True):
117 118 dirty = '+'
118 119 fm.data(dirty=dirty)
119 120 fm.end()
120 121
121 122 return out.getvalue()
122 123
123 124 class tarit(object):
124 125 '''write archive to tar file or stream. can write uncompressed,
125 126 or compress with gzip or bzip2.'''
126 127
127 128 class GzipFileWithTime(gzip.GzipFile):
128 129
129 130 def __init__(self, *args, **kw):
130 131 timestamp = None
131 132 if r'timestamp' in kw:
132 133 timestamp = kw.pop(r'timestamp')
133 134 if timestamp is None:
134 135 self.timestamp = time.time()
135 136 else:
136 137 self.timestamp = timestamp
137 138 gzip.GzipFile.__init__(self, *args, **kw)
138 139
139 140 def _write_gzip_header(self):
140 141 self.fileobj.write('\037\213') # magic header
141 142 self.fileobj.write('\010') # compression method
142 143 fname = self.name
143 144 if fname and fname.endswith('.gz'):
144 145 fname = fname[:-3]
145 146 flags = 0
146 147 if fname:
147 148 flags = gzip.FNAME
148 149 self.fileobj.write(pycompat.bytechr(flags))
149 150 gzip.write32u(self.fileobj, int(self.timestamp))
150 151 self.fileobj.write('\002')
151 152 self.fileobj.write('\377')
152 153 if fname:
153 154 self.fileobj.write(fname + '\000')
154 155
155 156 def __init__(self, dest, mtime, kind=''):
156 157 self.mtime = mtime
157 158 self.fileobj = None
158 159
159 160 def taropen(mode, name='', fileobj=None):
160 161 if kind == 'gz':
161 162 mode = mode[0:1]
162 163 if not fileobj:
163 164 fileobj = open(name, mode + 'b')
164 165 gzfileobj = self.GzipFileWithTime(name,
165 166 pycompat.sysstr(mode + 'b'),
166 167 zlib.Z_BEST_COMPRESSION,
167 168 fileobj, timestamp=mtime)
168 169 self.fileobj = gzfileobj
169 170 return tarfile.TarFile.taropen(
170 171 name, pycompat.sysstr(mode), gzfileobj)
171 172 else:
172 173 return tarfile.open(
173 174 name, pycompat.sysstr(mode + kind), fileobj)
174 175
175 176 if isinstance(dest, bytes):
176 177 self.z = taropen('w:', name=dest)
177 178 else:
178 179 self.z = taropen('w|', fileobj=dest)
179 180
180 181 def addfile(self, name, mode, islink, data):
181 182 name = pycompat.fsdecode(name)
182 183 i = tarfile.TarInfo(name)
183 184 i.mtime = self.mtime
184 185 i.size = len(data)
185 186 if islink:
186 187 i.type = tarfile.SYMTYPE
187 188 i.mode = 0o777
188 189 i.linkname = pycompat.fsdecode(data)
189 190 data = None
190 191 i.size = 0
191 192 else:
192 193 i.mode = mode
193 194 data = stringio(data)
194 195 self.z.addfile(i, data)
195 196
196 197 def done(self):
197 198 self.z.close()
198 199 if self.fileobj:
199 200 self.fileobj.close()
200 201
201 202 class zipit(object):
202 203 '''write archive to zip file or stream. can write uncompressed,
203 204 or compressed with deflate.'''
204 205
205 206 def __init__(self, dest, mtime, compress=True):
206 207 if isinstance(dest, bytes):
207 208 dest = pycompat.fsdecode(dest)
208 209 self.z = zipfile.ZipFile(dest, r'w',
209 210 compress and zipfile.ZIP_DEFLATED or
210 211 zipfile.ZIP_STORED)
211 212
212 213 # Python's zipfile module emits deprecation warnings if we try
213 214 # to store files with a date before 1980.
214 215 epoch = 315532800 # calendar.timegm((1980, 1, 1, 0, 0, 0, 1, 1, 0))
215 216 if mtime < epoch:
216 217 mtime = epoch
217 218
218 219 self.mtime = mtime
219 220 self.date_time = time.gmtime(mtime)[:6]
220 221
221 222 def addfile(self, name, mode, islink, data):
222 223 i = zipfile.ZipInfo(pycompat.fsdecode(name), self.date_time)
223 224 i.compress_type = self.z.compression
224 225 # unzip will not honor unix file modes unless file creator is
225 226 # set to unix (id 3).
226 227 i.create_system = 3
227 228 ftype = _UNX_IFREG
228 229 if islink:
229 230 mode = 0o777
230 231 ftype = _UNX_IFLNK
231 232 i.external_attr = (mode | ftype) << 16
232 233 # add "extended-timestamp" extra block, because zip archives
233 234 # without this will be extracted with unexpected timestamp,
234 235 # if TZ is not configured as GMT
235 236 i.extra += struct.pack('<hhBl',
236 237 0x5455, # block type: "extended-timestamp"
237 238 1 + 4, # size of this block
238 239 1, # "modification time is present"
239 240 int(self.mtime)) # last modification (UTC)
240 241 self.z.writestr(i, data)
241 242
242 243 def done(self):
243 244 self.z.close()
244 245
245 246 class fileit(object):
246 247 '''write archive as files in directory.'''
247 248
248 249 def __init__(self, name, mtime):
249 250 self.basedir = name
250 251 self.opener = vfsmod.vfs(self.basedir)
251 252 self.mtime = mtime
252 253
253 254 def addfile(self, name, mode, islink, data):
254 255 if islink:
255 256 self.opener.symlink(data, name)
256 257 return
257 258 f = self.opener(name, "w", atomictemp=False)
258 259 f.write(data)
259 260 f.close()
260 261 destfile = os.path.join(self.basedir, name)
261 262 os.chmod(destfile, mode)
262 263 if self.mtime is not None:
263 264 os.utime(destfile, (self.mtime, self.mtime))
264 265
265 266 def done(self):
266 267 pass
267 268
268 269 archivers = {
269 270 'files': fileit,
270 271 'tar': tarit,
271 272 'tbz2': lambda name, mtime: tarit(name, mtime, 'bz2'),
272 273 'tgz': lambda name, mtime: tarit(name, mtime, 'gz'),
274 'txz': lambda name, mtime: tarit(name, mtime, 'xz'),
273 275 'uzip': lambda name, mtime: zipit(name, mtime, False),
274 276 'zip': zipit,
275 277 }
276 278
277 279 def archive(repo, dest, node, kind, decode=True, match=None,
278 280 prefix='', mtime=None, subrepos=False):
279 281 '''create archive of repo as it was at node.
280 282
281 283 dest can be name of directory, name of archive file, or file
282 284 object to write archive to.
283 285
284 286 kind is type of archive to create.
285 287
286 288 decode tells whether to put files through decode filters from
287 289 hgrc.
288 290
289 291 match is a matcher to filter names of files to write to archive.
290 292
291 293 prefix is name of path to put before every archive member.
292 294
293 295 mtime is the modified time, in seconds, or None to use the changeset time.
294 296
295 297 subrepos tells whether to include subrepos.
296 298 '''
297 299
300 if kind == 'txz' and not pycompat.ispy3:
301 raise error.Abort(_('xz compression is only available in Python 3'))
302
298 303 if kind == 'files':
299 304 if prefix:
300 305 raise error.Abort(_('cannot give prefix when archiving to files'))
301 306 else:
302 307 prefix = tidyprefix(dest, kind, prefix)
303 308
304 309 def write(name, mode, islink, getdata):
305 310 data = getdata()
306 311 if decode:
307 312 data = repo.wwritedata(name, data)
308 313 archiver.addfile(prefix + name, mode, islink, data)
309 314
310 315 if kind not in archivers:
311 316 raise error.Abort(_("unknown archive type '%s'") % kind)
312 317
313 318 ctx = repo[node]
314 319 archiver = archivers[kind](dest, mtime or ctx.date()[0])
315 320
316 321 if not match:
317 322 match = scmutil.matchall(repo)
318 323
319 324 if repo.ui.configbool("ui", "archivemeta"):
320 325 name = '.hg_archival.txt'
321 326 if match(name):
322 327 write(name, 0o644, False, lambda: buildmetadata(ctx))
323 328
324 329 files = [f for f in ctx.manifest().matches(match)]
325 330 total = len(files)
326 331 if total:
327 332 files.sort()
328 333 scmutil.prefetchfiles(repo, [ctx.rev()],
329 334 scmutil.matchfiles(repo, files))
330 335 progress = repo.ui.makeprogress(_('archiving'), unit=_('files'),
331 336 total=total)
332 337 progress.update(0)
333 338 for f in files:
334 339 ff = ctx.flags(f)
335 340 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, ctx[f].data)
336 341 progress.increment(item=f)
337 342 progress.complete()
338 343
339 344 if subrepos:
340 345 for subpath in sorted(ctx.substate):
341 346 sub = ctx.workingsub(subpath)
342 347 submatch = matchmod.subdirmatcher(subpath, match)
343 348 subprefix = prefix + subpath + '/'
344 349 total += sub.archive(archiver, subprefix, submatch, decode)
345 350
346 351 if total == 0:
347 352 raise error.Abort(_('no files match the archive pattern'))
348 353
349 354 archiver.done()
350 355 return total
@@ -1,6451 +1,6452 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import difflib
11 11 import errno
12 12 import os
13 13 import re
14 14 import sys
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 hex,
19 19 nullid,
20 20 nullrev,
21 21 short,
22 22 wdirhex,
23 23 wdirrev,
24 24 )
25 25 from . import (
26 26 archival,
27 27 bookmarks,
28 28 bundle2,
29 29 changegroup,
30 30 cmdutil,
31 31 copies,
32 32 debugcommands as debugcommandsmod,
33 33 destutil,
34 34 dirstateguard,
35 35 discovery,
36 36 encoding,
37 37 error,
38 38 exchange,
39 39 extensions,
40 40 filemerge,
41 41 formatter,
42 42 graphmod,
43 43 hbisect,
44 44 help,
45 45 hg,
46 46 logcmdutil,
47 47 merge as mergemod,
48 48 narrowspec,
49 49 obsolete,
50 50 obsutil,
51 51 patch,
52 52 phases,
53 53 pycompat,
54 54 rcutil,
55 55 registrar,
56 56 revsetlang,
57 57 rewriteutil,
58 58 scmutil,
59 59 server,
60 60 shelve as shelvemod,
61 61 state as statemod,
62 62 streamclone,
63 63 tags as tagsmod,
64 64 ui as uimod,
65 65 util,
66 66 verify as verifymod,
67 67 wireprotoserver,
68 68 )
69 69 from .utils import (
70 70 dateutil,
71 71 stringutil,
72 72 )
73 73
74 74 table = {}
75 75 table.update(debugcommandsmod.command._table)
76 76
77 77 command = registrar.command(table)
78 78 INTENT_READONLY = registrar.INTENT_READONLY
79 79
80 80 # common command options
81 81
82 82 globalopts = [
83 83 ('R', 'repository', '',
84 84 _('repository root directory or name of overlay bundle file'),
85 85 _('REPO')),
86 86 ('', 'cwd', '',
87 87 _('change working directory'), _('DIR')),
88 88 ('y', 'noninteractive', None,
89 89 _('do not prompt, automatically pick the first choice for all prompts')),
90 90 ('q', 'quiet', None, _('suppress output')),
91 91 ('v', 'verbose', None, _('enable additional output')),
92 92 ('', 'color', '',
93 93 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
94 94 # and should not be translated
95 95 _("when to colorize (boolean, always, auto, never, or debug)"),
96 96 _('TYPE')),
97 97 ('', 'config', [],
98 98 _('set/override config option (use \'section.name=value\')'),
99 99 _('CONFIG')),
100 100 ('', 'debug', None, _('enable debugging output')),
101 101 ('', 'debugger', None, _('start debugger')),
102 102 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
103 103 _('ENCODE')),
104 104 ('', 'encodingmode', encoding.encodingmode,
105 105 _('set the charset encoding mode'), _('MODE')),
106 106 ('', 'traceback', None, _('always print a traceback on exception')),
107 107 ('', 'time', None, _('time how long the command takes')),
108 108 ('', 'profile', None, _('print command execution profile')),
109 109 ('', 'version', None, _('output version information and exit')),
110 110 ('h', 'help', None, _('display help and exit')),
111 111 ('', 'hidden', False, _('consider hidden changesets')),
112 112 ('', 'pager', 'auto',
113 113 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
114 114 ]
115 115
116 116 dryrunopts = cmdutil.dryrunopts
117 117 remoteopts = cmdutil.remoteopts
118 118 walkopts = cmdutil.walkopts
119 119 commitopts = cmdutil.commitopts
120 120 commitopts2 = cmdutil.commitopts2
121 121 commitopts3 = cmdutil.commitopts3
122 122 formatteropts = cmdutil.formatteropts
123 123 templateopts = cmdutil.templateopts
124 124 logopts = cmdutil.logopts
125 125 diffopts = cmdutil.diffopts
126 126 diffwsopts = cmdutil.diffwsopts
127 127 diffopts2 = cmdutil.diffopts2
128 128 mergetoolopts = cmdutil.mergetoolopts
129 129 similarityopts = cmdutil.similarityopts
130 130 subrepoopts = cmdutil.subrepoopts
131 131 debugrevlogopts = cmdutil.debugrevlogopts
132 132
133 133 # Commands start here, listed alphabetically
134 134
135 135 @command('abort',
136 136 dryrunopts, helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
137 137 helpbasic=True)
138 138 def abort(ui, repo, **opts):
139 139 """abort an unfinished operation (EXPERIMENTAL)
140 140
141 141 Aborts a multistep operation like graft, histedit, rebase, merge,
142 142 and unshelve if they are in an unfinished state.
143 143
144 144 use --dry-run/-n to dry run the command.
145 145 """
146 146 dryrun = opts.get(r'dry_run')
147 147 abortstate = cmdutil.getunfinishedstate(repo)
148 148 if not abortstate:
149 149 raise error.Abort(_('no operation in progress'))
150 150 if not abortstate.abortfunc:
151 151 raise error.Abort((_("%s in progress but does not support 'hg abort'") %
152 152 (abortstate._opname)), hint=abortstate.hint())
153 153 if dryrun:
154 154 ui.status(_('%s in progress, will be aborted\n') % (abortstate._opname))
155 155 return
156 156 return abortstate.abortfunc(ui, repo)
157 157
158 158 @command('add',
159 159 walkopts + subrepoopts + dryrunopts,
160 160 _('[OPTION]... [FILE]...'),
161 161 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
162 162 helpbasic=True, inferrepo=True)
163 163 def add(ui, repo, *pats, **opts):
164 164 """add the specified files on the next commit
165 165
166 166 Schedule files to be version controlled and added to the
167 167 repository.
168 168
169 169 The files will be added to the repository at the next commit. To
170 170 undo an add before that, see :hg:`forget`.
171 171
172 172 If no names are given, add all files to the repository (except
173 173 files matching ``.hgignore``).
174 174
175 175 .. container:: verbose
176 176
177 177 Examples:
178 178
179 179 - New (unknown) files are added
180 180 automatically by :hg:`add`::
181 181
182 182 $ ls
183 183 foo.c
184 184 $ hg status
185 185 ? foo.c
186 186 $ hg add
187 187 adding foo.c
188 188 $ hg status
189 189 A foo.c
190 190
191 191 - Specific files to be added can be specified::
192 192
193 193 $ ls
194 194 bar.c foo.c
195 195 $ hg status
196 196 ? bar.c
197 197 ? foo.c
198 198 $ hg add bar.c
199 199 $ hg status
200 200 A bar.c
201 201 ? foo.c
202 202
203 203 Returns 0 if all files are successfully added.
204 204 """
205 205
206 206 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
207 207 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
208 208 rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
209 209 return rejected and 1 or 0
210 210
211 211 @command('addremove',
212 212 similarityopts + subrepoopts + walkopts + dryrunopts,
213 213 _('[OPTION]... [FILE]...'),
214 214 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
215 215 inferrepo=True)
216 216 def addremove(ui, repo, *pats, **opts):
217 217 """add all new files, delete all missing files
218 218
219 219 Add all new files and remove all missing files from the
220 220 repository.
221 221
222 222 Unless names are given, new files are ignored if they match any of
223 223 the patterns in ``.hgignore``. As with add, these changes take
224 224 effect at the next commit.
225 225
226 226 Use the -s/--similarity option to detect renamed files. This
227 227 option takes a percentage between 0 (disabled) and 100 (files must
228 228 be identical) as its parameter. With a parameter greater than 0,
229 229 this compares every removed file with every added file and records
230 230 those similar enough as renames. Detecting renamed files this way
231 231 can be expensive. After using this option, :hg:`status -C` can be
232 232 used to check which files were identified as moved or renamed. If
233 233 not specified, -s/--similarity defaults to 100 and only renames of
234 234 identical files are detected.
235 235
236 236 .. container:: verbose
237 237
238 238 Examples:
239 239
240 240 - A number of files (bar.c and foo.c) are new,
241 241 while foobar.c has been removed (without using :hg:`remove`)
242 242 from the repository::
243 243
244 244 $ ls
245 245 bar.c foo.c
246 246 $ hg status
247 247 ! foobar.c
248 248 ? bar.c
249 249 ? foo.c
250 250 $ hg addremove
251 251 adding bar.c
252 252 adding foo.c
253 253 removing foobar.c
254 254 $ hg status
255 255 A bar.c
256 256 A foo.c
257 257 R foobar.c
258 258
259 259 - A file foobar.c was moved to foo.c without using :hg:`rename`.
260 260 Afterwards, it was edited slightly::
261 261
262 262 $ ls
263 263 foo.c
264 264 $ hg status
265 265 ! foobar.c
266 266 ? foo.c
267 267 $ hg addremove --similarity 90
268 268 removing foobar.c
269 269 adding foo.c
270 270 recording removal of foobar.c as rename to foo.c (94% similar)
271 271 $ hg status -C
272 272 A foo.c
273 273 foobar.c
274 274 R foobar.c
275 275
276 276 Returns 0 if all files are successfully added.
277 277 """
278 278 opts = pycompat.byteskwargs(opts)
279 279 if not opts.get('similarity'):
280 280 opts['similarity'] = '100'
281 281 matcher = scmutil.match(repo[None], pats, opts)
282 282 relative = scmutil.anypats(pats, opts)
283 283 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
284 284 return scmutil.addremove(repo, matcher, "", uipathfn, opts)
285 285
286 286 @command('annotate|blame',
287 287 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
288 288 ('', 'follow', None,
289 289 _('follow copies/renames and list the filename (DEPRECATED)')),
290 290 ('', 'no-follow', None, _("don't follow copies and renames")),
291 291 ('a', 'text', None, _('treat all files as text')),
292 292 ('u', 'user', None, _('list the author (long with -v)')),
293 293 ('f', 'file', None, _('list the filename')),
294 294 ('d', 'date', None, _('list the date (short with -q)')),
295 295 ('n', 'number', None, _('list the revision number (default)')),
296 296 ('c', 'changeset', None, _('list the changeset')),
297 297 ('l', 'line-number', None, _('show line number at the first appearance')),
298 298 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
299 299 ] + diffwsopts + walkopts + formatteropts,
300 300 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
301 301 helpcategory=command.CATEGORY_FILE_CONTENTS,
302 302 helpbasic=True, inferrepo=True)
303 303 def annotate(ui, repo, *pats, **opts):
304 304 """show changeset information by line for each file
305 305
306 306 List changes in files, showing the revision id responsible for
307 307 each line.
308 308
309 309 This command is useful for discovering when a change was made and
310 310 by whom.
311 311
312 312 If you include --file, --user, or --date, the revision number is
313 313 suppressed unless you also include --number.
314 314
315 315 Without the -a/--text option, annotate will avoid processing files
316 316 it detects as binary. With -a, annotate will annotate the file
317 317 anyway, although the results will probably be neither useful
318 318 nor desirable.
319 319
320 320 .. container:: verbose
321 321
322 322 Template:
323 323
324 324 The following keywords are supported in addition to the common template
325 325 keywords and functions. See also :hg:`help templates`.
326 326
327 327 :lines: List of lines with annotation data.
328 328 :path: String. Repository-absolute path of the specified file.
329 329
330 330 And each entry of ``{lines}`` provides the following sub-keywords in
331 331 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
332 332
333 333 :line: String. Line content.
334 334 :lineno: Integer. Line number at that revision.
335 335 :path: String. Repository-absolute path of the file at that revision.
336 336
337 337 See :hg:`help templates.operators` for the list expansion syntax.
338 338
339 339 Returns 0 on success.
340 340 """
341 341 opts = pycompat.byteskwargs(opts)
342 342 if not pats:
343 343 raise error.Abort(_('at least one filename or pattern is required'))
344 344
345 345 if opts.get('follow'):
346 346 # --follow is deprecated and now just an alias for -f/--file
347 347 # to mimic the behavior of Mercurial before version 1.5
348 348 opts['file'] = True
349 349
350 350 if (not opts.get('user') and not opts.get('changeset')
351 351 and not opts.get('date') and not opts.get('file')):
352 352 opts['number'] = True
353 353
354 354 linenumber = opts.get('line_number') is not None
355 355 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
356 356 raise error.Abort(_('at least one of -n/-c is required for -l'))
357 357
358 358 rev = opts.get('rev')
359 359 if rev:
360 360 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
361 361 ctx = scmutil.revsingle(repo, rev)
362 362
363 363 ui.pager('annotate')
364 364 rootfm = ui.formatter('annotate', opts)
365 365 if ui.debugflag:
366 366 shorthex = pycompat.identity
367 367 else:
368 368 def shorthex(h):
369 369 return h[:12]
370 370 if ui.quiet:
371 371 datefunc = dateutil.shortdate
372 372 else:
373 373 datefunc = dateutil.datestr
374 374 if ctx.rev() is None:
375 375 if opts.get('changeset'):
376 376 # omit "+" suffix which is appended to node hex
377 377 def formatrev(rev):
378 378 if rev == wdirrev:
379 379 return '%d' % ctx.p1().rev()
380 380 else:
381 381 return '%d' % rev
382 382 else:
383 383 def formatrev(rev):
384 384 if rev == wdirrev:
385 385 return '%d+' % ctx.p1().rev()
386 386 else:
387 387 return '%d ' % rev
388 388 def formathex(h):
389 389 if h == wdirhex:
390 390 return '%s+' % shorthex(hex(ctx.p1().node()))
391 391 else:
392 392 return '%s ' % shorthex(h)
393 393 else:
394 394 formatrev = b'%d'.__mod__
395 395 formathex = shorthex
396 396
397 397 opmap = [
398 398 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
399 399 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
400 400 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
401 401 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
402 402 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
403 403 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
404 404 ]
405 405 opnamemap = {
406 406 'rev': 'number',
407 407 'node': 'changeset',
408 408 'path': 'file',
409 409 'lineno': 'line_number',
410 410 }
411 411
412 412 if rootfm.isplain():
413 413 def makefunc(get, fmt):
414 414 return lambda x: fmt(get(x))
415 415 else:
416 416 def makefunc(get, fmt):
417 417 return get
418 418 datahint = rootfm.datahint()
419 419 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
420 420 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
421 421 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
422 422 fields = ' '.join(fn for fn, sep, get, fmt in opmap
423 423 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
424 424
425 425 def bad(x, y):
426 426 raise error.Abort("%s: %s" % (x, y))
427 427
428 428 m = scmutil.match(ctx, pats, opts, badfn=bad)
429 429
430 430 follow = not opts.get('no_follow')
431 431 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
432 432 whitespace=True)
433 433 skiprevs = opts.get('skip')
434 434 if skiprevs:
435 435 skiprevs = scmutil.revrange(repo, skiprevs)
436 436
437 437 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
438 438 for abs in ctx.walk(m):
439 439 fctx = ctx[abs]
440 440 rootfm.startitem()
441 441 rootfm.data(path=abs)
442 442 if not opts.get('text') and fctx.isbinary():
443 443 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
444 444 continue
445 445
446 446 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
447 447 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
448 448 diffopts=diffopts)
449 449 if not lines:
450 450 fm.end()
451 451 continue
452 452 formats = []
453 453 pieces = []
454 454
455 455 for f, sep in funcmap:
456 456 l = [f(n) for n in lines]
457 457 if fm.isplain():
458 458 sizes = [encoding.colwidth(x) for x in l]
459 459 ml = max(sizes)
460 460 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
461 461 else:
462 462 formats.append(['%s' for x in l])
463 463 pieces.append(l)
464 464
465 465 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
466 466 fm.startitem()
467 467 fm.context(fctx=n.fctx)
468 468 fm.write(fields, "".join(f), *p)
469 469 if n.skip:
470 470 fmt = "* %s"
471 471 else:
472 472 fmt = ": %s"
473 473 fm.write('line', fmt, n.text)
474 474
475 475 if not lines[-1].text.endswith('\n'):
476 476 fm.plain('\n')
477 477 fm.end()
478 478
479 479 rootfm.end()
480 480
481 481 @command('archive',
482 482 [('', 'no-decode', None, _('do not pass files through decoders')),
483 483 ('p', 'prefix', '', _('directory prefix for files in archive'),
484 484 _('PREFIX')),
485 485 ('r', 'rev', '', _('revision to distribute'), _('REV')),
486 486 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
487 487 ] + subrepoopts + walkopts,
488 488 _('[OPTION]... DEST'),
489 489 helpcategory=command.CATEGORY_IMPORT_EXPORT)
490 490 def archive(ui, repo, dest, **opts):
491 491 '''create an unversioned archive of a repository revision
492 492
493 493 By default, the revision used is the parent of the working
494 494 directory; use -r/--rev to specify a different revision.
495 495
496 496 The archive type is automatically detected based on file
497 497 extension (to override, use -t/--type).
498 498
499 499 .. container:: verbose
500 500
501 501 Examples:
502 502
503 503 - create a zip file containing the 1.0 release::
504 504
505 505 hg archive -r 1.0 project-1.0.zip
506 506
507 507 - create a tarball excluding .hg files::
508 508
509 509 hg archive project.tar.gz -X ".hg*"
510 510
511 511 Valid types are:
512 512
513 513 :``files``: a directory full of files (default)
514 514 :``tar``: tar archive, uncompressed
515 515 :``tbz2``: tar archive, compressed using bzip2
516 516 :``tgz``: tar archive, compressed using gzip
517 :``txz``: tar archive, compressed using lzma (only in Python 3)
517 518 :``uzip``: zip archive, uncompressed
518 519 :``zip``: zip archive, compressed using deflate
519 520
520 521 The exact name of the destination archive or directory is given
521 522 using a format string; see :hg:`help export` for details.
522 523
523 524 Each member added to an archive file has a directory prefix
524 525 prepended. Use -p/--prefix to specify a format string for the
525 526 prefix. The default is the basename of the archive, with suffixes
526 527 removed.
527 528
528 529 Returns 0 on success.
529 530 '''
530 531
531 532 opts = pycompat.byteskwargs(opts)
532 533 rev = opts.get('rev')
533 534 if rev:
534 535 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
535 536 ctx = scmutil.revsingle(repo, rev)
536 537 if not ctx:
537 538 raise error.Abort(_('no working directory: please specify a revision'))
538 539 node = ctx.node()
539 540 dest = cmdutil.makefilename(ctx, dest)
540 541 if os.path.realpath(dest) == repo.root:
541 542 raise error.Abort(_('repository root cannot be destination'))
542 543
543 544 kind = opts.get('type') or archival.guesskind(dest) or 'files'
544 545 prefix = opts.get('prefix')
545 546
546 547 if dest == '-':
547 548 if kind == 'files':
548 549 raise error.Abort(_('cannot archive plain files to stdout'))
549 550 dest = cmdutil.makefileobj(ctx, dest)
550 551 if not prefix:
551 552 prefix = os.path.basename(repo.root) + '-%h'
552 553
553 554 prefix = cmdutil.makefilename(ctx, prefix)
554 555 match = scmutil.match(ctx, [], opts)
555 556 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
556 557 match, prefix, subrepos=opts.get('subrepos'))
557 558
558 559 @command('backout',
559 560 [('', 'merge', None, _('merge with old dirstate parent after backout')),
560 561 ('', 'commit', None,
561 562 _('commit if no conflicts were encountered (DEPRECATED)')),
562 563 ('', 'no-commit', None, _('do not commit')),
563 564 ('', 'parent', '',
564 565 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
565 566 ('r', 'rev', '', _('revision to backout'), _('REV')),
566 567 ('e', 'edit', False, _('invoke editor on commit messages')),
567 568 ] + mergetoolopts + walkopts + commitopts + commitopts2,
568 569 _('[OPTION]... [-r] REV'),
569 570 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
570 571 def backout(ui, repo, node=None, rev=None, **opts):
571 572 '''reverse effect of earlier changeset
572 573
573 574 Prepare a new changeset with the effect of REV undone in the
574 575 current working directory. If no conflicts were encountered,
575 576 it will be committed immediately.
576 577
577 578 If REV is the parent of the working directory, then this new changeset
578 579 is committed automatically (unless --no-commit is specified).
579 580
580 581 .. note::
581 582
582 583 :hg:`backout` cannot be used to fix either an unwanted or
583 584 incorrect merge.
584 585
585 586 .. container:: verbose
586 587
587 588 Examples:
588 589
589 590 - Reverse the effect of the parent of the working directory.
590 591 This backout will be committed immediately::
591 592
592 593 hg backout -r .
593 594
594 595 - Reverse the effect of previous bad revision 23::
595 596
596 597 hg backout -r 23
597 598
598 599 - Reverse the effect of previous bad revision 23 and
599 600 leave changes uncommitted::
600 601
601 602 hg backout -r 23 --no-commit
602 603 hg commit -m "Backout revision 23"
603 604
604 605 By default, the pending changeset will have one parent,
605 606 maintaining a linear history. With --merge, the pending
606 607 changeset will instead have two parents: the old parent of the
607 608 working directory and a new child of REV that simply undoes REV.
608 609
609 610 Before version 1.7, the behavior without --merge was equivalent
610 611 to specifying --merge followed by :hg:`update --clean .` to
611 612 cancel the merge and leave the child of REV as a head to be
612 613 merged separately.
613 614
614 615 See :hg:`help dates` for a list of formats valid for -d/--date.
615 616
616 617 See :hg:`help revert` for a way to restore files to the state
617 618 of another revision.
618 619
619 620 Returns 0 on success, 1 if nothing to backout or there are unresolved
620 621 files.
621 622 '''
622 623 with repo.wlock(), repo.lock():
623 624 return _dobackout(ui, repo, node, rev, **opts)
624 625
625 626 def _dobackout(ui, repo, node=None, rev=None, **opts):
626 627 opts = pycompat.byteskwargs(opts)
627 628 if opts.get('commit') and opts.get('no_commit'):
628 629 raise error.Abort(_("cannot use --commit with --no-commit"))
629 630 if opts.get('merge') and opts.get('no_commit'):
630 631 raise error.Abort(_("cannot use --merge with --no-commit"))
631 632
632 633 if rev and node:
633 634 raise error.Abort(_("please specify just one revision"))
634 635
635 636 if not rev:
636 637 rev = node
637 638
638 639 if not rev:
639 640 raise error.Abort(_("please specify a revision to backout"))
640 641
641 642 date = opts.get('date')
642 643 if date:
643 644 opts['date'] = dateutil.parsedate(date)
644 645
645 646 cmdutil.checkunfinished(repo)
646 647 cmdutil.bailifchanged(repo)
647 648 node = scmutil.revsingle(repo, rev).node()
648 649
649 650 op1, op2 = repo.dirstate.parents()
650 651 if not repo.changelog.isancestor(node, op1):
651 652 raise error.Abort(_('cannot backout change that is not an ancestor'))
652 653
653 654 p1, p2 = repo.changelog.parents(node)
654 655 if p1 == nullid:
655 656 raise error.Abort(_('cannot backout a change with no parents'))
656 657 if p2 != nullid:
657 658 if not opts.get('parent'):
658 659 raise error.Abort(_('cannot backout a merge changeset'))
659 660 p = repo.lookup(opts['parent'])
660 661 if p not in (p1, p2):
661 662 raise error.Abort(_('%s is not a parent of %s') %
662 663 (short(p), short(node)))
663 664 parent = p
664 665 else:
665 666 if opts.get('parent'):
666 667 raise error.Abort(_('cannot use --parent on non-merge changeset'))
667 668 parent = p1
668 669
669 670 # the backout should appear on the same branch
670 671 branch = repo.dirstate.branch()
671 672 bheads = repo.branchheads(branch)
672 673 rctx = scmutil.revsingle(repo, hex(parent))
673 674 if not opts.get('merge') and op1 != node:
674 675 with dirstateguard.dirstateguard(repo, 'backout'):
675 676 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
676 677 with ui.configoverride(overrides, 'backout'):
677 678 stats = mergemod.update(repo, parent, branchmerge=True,
678 679 force=True, ancestor=node,
679 680 mergeancestor=False)
680 681 repo.setparents(op1, op2)
681 682 hg._showstats(repo, stats)
682 683 if stats.unresolvedcount:
683 684 repo.ui.status(_("use 'hg resolve' to retry unresolved "
684 685 "file merges\n"))
685 686 return 1
686 687 else:
687 688 hg.clean(repo, node, show_stats=False)
688 689 repo.dirstate.setbranch(branch)
689 690 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
690 691
691 692 if opts.get('no_commit'):
692 693 msg = _("changeset %s backed out, "
693 694 "don't forget to commit.\n")
694 695 ui.status(msg % short(node))
695 696 return 0
696 697
697 698 def commitfunc(ui, repo, message, match, opts):
698 699 editform = 'backout'
699 700 e = cmdutil.getcommiteditor(editform=editform,
700 701 **pycompat.strkwargs(opts))
701 702 if not message:
702 703 # we don't translate commit messages
703 704 message = "Backed out changeset %s" % short(node)
704 705 e = cmdutil.getcommiteditor(edit=True, editform=editform)
705 706 return repo.commit(message, opts.get('user'), opts.get('date'),
706 707 match, editor=e)
707 708 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
708 709 if not newnode:
709 710 ui.status(_("nothing changed\n"))
710 711 return 1
711 712 cmdutil.commitstatus(repo, newnode, branch, bheads)
712 713
713 714 def nice(node):
714 715 return '%d:%s' % (repo.changelog.rev(node), short(node))
715 716 ui.status(_('changeset %s backs out changeset %s\n') %
716 717 (nice(repo.changelog.tip()), nice(node)))
717 718 if opts.get('merge') and op1 != node:
718 719 hg.clean(repo, op1, show_stats=False)
719 720 ui.status(_('merging with changeset %s\n')
720 721 % nice(repo.changelog.tip()))
721 722 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
722 723 with ui.configoverride(overrides, 'backout'):
723 724 return hg.merge(repo, hex(repo.changelog.tip()))
724 725 return 0
725 726
726 727 @command('bisect',
727 728 [('r', 'reset', False, _('reset bisect state')),
728 729 ('g', 'good', False, _('mark changeset good')),
729 730 ('b', 'bad', False, _('mark changeset bad')),
730 731 ('s', 'skip', False, _('skip testing changeset')),
731 732 ('e', 'extend', False, _('extend the bisect range')),
732 733 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
733 734 ('U', 'noupdate', False, _('do not update to target'))],
734 735 _("[-gbsr] [-U] [-c CMD] [REV]"),
735 736 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
736 737 def bisect(ui, repo, rev=None, extra=None, command=None,
737 738 reset=None, good=None, bad=None, skip=None, extend=None,
738 739 noupdate=None):
739 740 """subdivision search of changesets
740 741
741 742 This command helps to find changesets which introduce problems. To
742 743 use, mark the earliest changeset you know exhibits the problem as
743 744 bad, then mark the latest changeset which is free from the problem
744 745 as good. Bisect will update your working directory to a revision
745 746 for testing (unless the -U/--noupdate option is specified). Once
746 747 you have performed tests, mark the working directory as good or
747 748 bad, and bisect will either update to another candidate changeset
748 749 or announce that it has found the bad revision.
749 750
750 751 As a shortcut, you can also use the revision argument to mark a
751 752 revision as good or bad without checking it out first.
752 753
753 754 If you supply a command, it will be used for automatic bisection.
754 755 The environment variable HG_NODE will contain the ID of the
755 756 changeset being tested. The exit status of the command will be
756 757 used to mark revisions as good or bad: status 0 means good, 125
757 758 means to skip the revision, 127 (command not found) will abort the
758 759 bisection, and any other non-zero exit status means the revision
759 760 is bad.
760 761
761 762 .. container:: verbose
762 763
763 764 Some examples:
764 765
765 766 - start a bisection with known bad revision 34, and good revision 12::
766 767
767 768 hg bisect --bad 34
768 769 hg bisect --good 12
769 770
770 771 - advance the current bisection by marking current revision as good or
771 772 bad::
772 773
773 774 hg bisect --good
774 775 hg bisect --bad
775 776
776 777 - mark the current revision, or a known revision, to be skipped (e.g. if
777 778 that revision is not usable because of another issue)::
778 779
779 780 hg bisect --skip
780 781 hg bisect --skip 23
781 782
782 783 - skip all revisions that do not touch directories ``foo`` or ``bar``::
783 784
784 785 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
785 786
786 787 - forget the current bisection::
787 788
788 789 hg bisect --reset
789 790
790 791 - use 'make && make tests' to automatically find the first broken
791 792 revision::
792 793
793 794 hg bisect --reset
794 795 hg bisect --bad 34
795 796 hg bisect --good 12
796 797 hg bisect --command "make && make tests"
797 798
798 799 - see all changesets whose states are already known in the current
799 800 bisection::
800 801
801 802 hg log -r "bisect(pruned)"
802 803
803 804 - see the changeset currently being bisected (especially useful
804 805 if running with -U/--noupdate)::
805 806
806 807 hg log -r "bisect(current)"
807 808
808 809 - see all changesets that took part in the current bisection::
809 810
810 811 hg log -r "bisect(range)"
811 812
812 813 - you can even get a nice graph::
813 814
814 815 hg log --graph -r "bisect(range)"
815 816
816 817 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
817 818
818 819 Returns 0 on success.
819 820 """
820 821 # backward compatibility
821 822 if rev in "good bad reset init".split():
822 823 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
823 824 cmd, rev, extra = rev, extra, None
824 825 if cmd == "good":
825 826 good = True
826 827 elif cmd == "bad":
827 828 bad = True
828 829 else:
829 830 reset = True
830 831 elif extra:
831 832 raise error.Abort(_('incompatible arguments'))
832 833
833 834 incompatibles = {
834 835 '--bad': bad,
835 836 '--command': bool(command),
836 837 '--extend': extend,
837 838 '--good': good,
838 839 '--reset': reset,
839 840 '--skip': skip,
840 841 }
841 842
842 843 enabled = [x for x in incompatibles if incompatibles[x]]
843 844
844 845 if len(enabled) > 1:
845 846 raise error.Abort(_('%s and %s are incompatible') %
846 847 tuple(sorted(enabled)[0:2]))
847 848
848 849 if reset:
849 850 hbisect.resetstate(repo)
850 851 return
851 852
852 853 state = hbisect.load_state(repo)
853 854
854 855 # update state
855 856 if good or bad or skip:
856 857 if rev:
857 858 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
858 859 else:
859 860 nodes = [repo.lookup('.')]
860 861 if good:
861 862 state['good'] += nodes
862 863 elif bad:
863 864 state['bad'] += nodes
864 865 elif skip:
865 866 state['skip'] += nodes
866 867 hbisect.save_state(repo, state)
867 868 if not (state['good'] and state['bad']):
868 869 return
869 870
870 871 def mayupdate(repo, node, show_stats=True):
871 872 """common used update sequence"""
872 873 if noupdate:
873 874 return
874 875 cmdutil.checkunfinished(repo)
875 876 cmdutil.bailifchanged(repo)
876 877 return hg.clean(repo, node, show_stats=show_stats)
877 878
878 879 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
879 880
880 881 if command:
881 882 changesets = 1
882 883 if noupdate:
883 884 try:
884 885 node = state['current'][0]
885 886 except LookupError:
886 887 raise error.Abort(_('current bisect revision is unknown - '
887 888 'start a new bisect to fix'))
888 889 else:
889 890 node, p2 = repo.dirstate.parents()
890 891 if p2 != nullid:
891 892 raise error.Abort(_('current bisect revision is a merge'))
892 893 if rev:
893 894 node = repo[scmutil.revsingle(repo, rev, node)].node()
894 895 try:
895 896 while changesets:
896 897 # update state
897 898 state['current'] = [node]
898 899 hbisect.save_state(repo, state)
899 900 status = ui.system(command, environ={'HG_NODE': hex(node)},
900 901 blockedtag='bisect_check')
901 902 if status == 125:
902 903 transition = "skip"
903 904 elif status == 0:
904 905 transition = "good"
905 906 # status < 0 means process was killed
906 907 elif status == 127:
907 908 raise error.Abort(_("failed to execute %s") % command)
908 909 elif status < 0:
909 910 raise error.Abort(_("%s killed") % command)
910 911 else:
911 912 transition = "bad"
912 913 state[transition].append(node)
913 914 ctx = repo[node]
914 915 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
915 916 transition))
916 917 hbisect.checkstate(state)
917 918 # bisect
918 919 nodes, changesets, bgood = hbisect.bisect(repo, state)
919 920 # update to next check
920 921 node = nodes[0]
921 922 mayupdate(repo, node, show_stats=False)
922 923 finally:
923 924 state['current'] = [node]
924 925 hbisect.save_state(repo, state)
925 926 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
926 927 return
927 928
928 929 hbisect.checkstate(state)
929 930
930 931 # actually bisect
931 932 nodes, changesets, good = hbisect.bisect(repo, state)
932 933 if extend:
933 934 if not changesets:
934 935 extendnode = hbisect.extendrange(repo, state, nodes, good)
935 936 if extendnode is not None:
936 937 ui.write(_("Extending search to changeset %d:%s\n")
937 938 % (extendnode.rev(), extendnode))
938 939 state['current'] = [extendnode.node()]
939 940 hbisect.save_state(repo, state)
940 941 return mayupdate(repo, extendnode.node())
941 942 raise error.Abort(_("nothing to extend"))
942 943
943 944 if changesets == 0:
944 945 hbisect.printresult(ui, repo, state, displayer, nodes, good)
945 946 else:
946 947 assert len(nodes) == 1 # only a single node can be tested next
947 948 node = nodes[0]
948 949 # compute the approximate number of remaining tests
949 950 tests, size = 0, 2
950 951 while size <= changesets:
951 952 tests, size = tests + 1, size * 2
952 953 rev = repo.changelog.rev(node)
953 954 ui.write(_("Testing changeset %d:%s "
954 955 "(%d changesets remaining, ~%d tests)\n")
955 956 % (rev, short(node), changesets, tests))
956 957 state['current'] = [node]
957 958 hbisect.save_state(repo, state)
958 959 return mayupdate(repo, node)
959 960
960 961 @command('bookmarks|bookmark',
961 962 [('f', 'force', False, _('force')),
962 963 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
963 964 ('d', 'delete', False, _('delete a given bookmark')),
964 965 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
965 966 ('i', 'inactive', False, _('mark a bookmark inactive')),
966 967 ('l', 'list', False, _('list existing bookmarks')),
967 968 ] + formatteropts,
968 969 _('hg bookmarks [OPTIONS]... [NAME]...'),
969 970 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
970 971 def bookmark(ui, repo, *names, **opts):
971 972 '''create a new bookmark or list existing bookmarks
972 973
973 974 Bookmarks are labels on changesets to help track lines of development.
974 975 Bookmarks are unversioned and can be moved, renamed and deleted.
975 976 Deleting or moving a bookmark has no effect on the associated changesets.
976 977
977 978 Creating or updating to a bookmark causes it to be marked as 'active'.
978 979 The active bookmark is indicated with a '*'.
979 980 When a commit is made, the active bookmark will advance to the new commit.
980 981 A plain :hg:`update` will also advance an active bookmark, if possible.
981 982 Updating away from a bookmark will cause it to be deactivated.
982 983
983 984 Bookmarks can be pushed and pulled between repositories (see
984 985 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
985 986 diverged, a new 'divergent bookmark' of the form 'name@path' will
986 987 be created. Using :hg:`merge` will resolve the divergence.
987 988
988 989 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
989 990 the active bookmark's name.
990 991
991 992 A bookmark named '@' has the special property that :hg:`clone` will
992 993 check it out by default if it exists.
993 994
994 995 .. container:: verbose
995 996
996 997 Template:
997 998
998 999 The following keywords are supported in addition to the common template
999 1000 keywords and functions such as ``{bookmark}``. See also
1000 1001 :hg:`help templates`.
1001 1002
1002 1003 :active: Boolean. True if the bookmark is active.
1003 1004
1004 1005 Examples:
1005 1006
1006 1007 - create an active bookmark for a new line of development::
1007 1008
1008 1009 hg book new-feature
1009 1010
1010 1011 - create an inactive bookmark as a place marker::
1011 1012
1012 1013 hg book -i reviewed
1013 1014
1014 1015 - create an inactive bookmark on another changeset::
1015 1016
1016 1017 hg book -r .^ tested
1017 1018
1018 1019 - rename bookmark turkey to dinner::
1019 1020
1020 1021 hg book -m turkey dinner
1021 1022
1022 1023 - move the '@' bookmark from another branch::
1023 1024
1024 1025 hg book -f @
1025 1026
1026 1027 - print only the active bookmark name::
1027 1028
1028 1029 hg book -ql .
1029 1030 '''
1030 1031 opts = pycompat.byteskwargs(opts)
1031 1032 force = opts.get('force')
1032 1033 rev = opts.get('rev')
1033 1034 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1034 1035
1035 1036 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1036 1037 if len(selactions) > 1:
1037 1038 raise error.Abort(_('--%s and --%s are incompatible')
1038 1039 % tuple(selactions[:2]))
1039 1040 if selactions:
1040 1041 action = selactions[0]
1041 1042 elif names or rev:
1042 1043 action = 'add'
1043 1044 elif inactive:
1044 1045 action = 'inactive' # meaning deactivate
1045 1046 else:
1046 1047 action = 'list'
1047 1048
1048 1049 if rev and action in {'delete', 'rename', 'list'}:
1049 1050 raise error.Abort(_("--rev is incompatible with --%s") % action)
1050 1051 if inactive and action in {'delete', 'list'}:
1051 1052 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1052 1053 if not names and action in {'add', 'delete'}:
1053 1054 raise error.Abort(_("bookmark name required"))
1054 1055
1055 1056 if action in {'add', 'delete', 'rename', 'inactive'}:
1056 1057 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1057 1058 if action == 'delete':
1058 1059 names = pycompat.maplist(repo._bookmarks.expandname, names)
1059 1060 bookmarks.delete(repo, tr, names)
1060 1061 elif action == 'rename':
1061 1062 if not names:
1062 1063 raise error.Abort(_("new bookmark name required"))
1063 1064 elif len(names) > 1:
1064 1065 raise error.Abort(_("only one new bookmark name allowed"))
1065 1066 oldname = repo._bookmarks.expandname(opts['rename'])
1066 1067 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1067 1068 elif action == 'add':
1068 1069 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1069 1070 elif action == 'inactive':
1070 1071 if len(repo._bookmarks) == 0:
1071 1072 ui.status(_("no bookmarks set\n"))
1072 1073 elif not repo._activebookmark:
1073 1074 ui.status(_("no active bookmark\n"))
1074 1075 else:
1075 1076 bookmarks.deactivate(repo)
1076 1077 elif action == 'list':
1077 1078 names = pycompat.maplist(repo._bookmarks.expandname, names)
1078 1079 with ui.formatter('bookmarks', opts) as fm:
1079 1080 bookmarks.printbookmarks(ui, repo, fm, names)
1080 1081 else:
1081 1082 raise error.ProgrammingError('invalid action: %s' % action)
1082 1083
1083 1084 @command('branch',
1084 1085 [('f', 'force', None,
1085 1086 _('set branch name even if it shadows an existing branch')),
1086 1087 ('C', 'clean', None, _('reset branch name to parent branch name')),
1087 1088 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1088 1089 ],
1089 1090 _('[-fC] [NAME]'),
1090 1091 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1091 1092 def branch(ui, repo, label=None, **opts):
1092 1093 """set or show the current branch name
1093 1094
1094 1095 .. note::
1095 1096
1096 1097 Branch names are permanent and global. Use :hg:`bookmark` to create a
1097 1098 light-weight bookmark instead. See :hg:`help glossary` for more
1098 1099 information about named branches and bookmarks.
1099 1100
1100 1101 With no argument, show the current branch name. With one argument,
1101 1102 set the working directory branch name (the branch will not exist
1102 1103 in the repository until the next commit). Standard practice
1103 1104 recommends that primary development take place on the 'default'
1104 1105 branch.
1105 1106
1106 1107 Unless -f/--force is specified, branch will not let you set a
1107 1108 branch name that already exists.
1108 1109
1109 1110 Use -C/--clean to reset the working directory branch to that of
1110 1111 the parent of the working directory, negating a previous branch
1111 1112 change.
1112 1113
1113 1114 Use the command :hg:`update` to switch to an existing branch. Use
1114 1115 :hg:`commit --close-branch` to mark this branch head as closed.
1115 1116 When all heads of a branch are closed, the branch will be
1116 1117 considered closed.
1117 1118
1118 1119 Returns 0 on success.
1119 1120 """
1120 1121 opts = pycompat.byteskwargs(opts)
1121 1122 revs = opts.get('rev')
1122 1123 if label:
1123 1124 label = label.strip()
1124 1125
1125 1126 if not opts.get('clean') and not label:
1126 1127 if revs:
1127 1128 raise error.Abort(_("no branch name specified for the revisions"))
1128 1129 ui.write("%s\n" % repo.dirstate.branch())
1129 1130 return
1130 1131
1131 1132 with repo.wlock():
1132 1133 if opts.get('clean'):
1133 1134 label = repo['.'].branch()
1134 1135 repo.dirstate.setbranch(label)
1135 1136 ui.status(_('reset working directory to branch %s\n') % label)
1136 1137 elif label:
1137 1138
1138 1139 scmutil.checknewlabel(repo, label, 'branch')
1139 1140 if revs:
1140 1141 return cmdutil.changebranch(ui, repo, revs, label)
1141 1142
1142 1143 if not opts.get('force') and label in repo.branchmap():
1143 1144 if label not in [p.branch() for p in repo[None].parents()]:
1144 1145 raise error.Abort(_('a branch of the same name already'
1145 1146 ' exists'),
1146 1147 # i18n: "it" refers to an existing branch
1147 1148 hint=_("use 'hg update' to switch to it"))
1148 1149
1149 1150 repo.dirstate.setbranch(label)
1150 1151 ui.status(_('marked working directory as branch %s\n') % label)
1151 1152
1152 1153 # find any open named branches aside from default
1153 1154 for n, h, t, c in repo.branchmap().iterbranches():
1154 1155 if n != "default" and not c:
1155 1156 return 0
1156 1157 ui.status(_('(branches are permanent and global, '
1157 1158 'did you want a bookmark?)\n'))
1158 1159
1159 1160 @command('branches',
1160 1161 [('a', 'active', False,
1161 1162 _('show only branches that have unmerged heads (DEPRECATED)')),
1162 1163 ('c', 'closed', False, _('show normal and closed branches')),
1163 1164 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1164 1165 ] + formatteropts,
1165 1166 _('[-c]'),
1166 1167 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1167 1168 intents={INTENT_READONLY})
1168 1169 def branches(ui, repo, active=False, closed=False, **opts):
1169 1170 """list repository named branches
1170 1171
1171 1172 List the repository's named branches, indicating which ones are
1172 1173 inactive. If -c/--closed is specified, also list branches which have
1173 1174 been marked closed (see :hg:`commit --close-branch`).
1174 1175
1175 1176 Use the command :hg:`update` to switch to an existing branch.
1176 1177
1177 1178 .. container:: verbose
1178 1179
1179 1180 Template:
1180 1181
1181 1182 The following keywords are supported in addition to the common template
1182 1183 keywords and functions such as ``{branch}``. See also
1183 1184 :hg:`help templates`.
1184 1185
1185 1186 :active: Boolean. True if the branch is active.
1186 1187 :closed: Boolean. True if the branch is closed.
1187 1188 :current: Boolean. True if it is the current branch.
1188 1189
1189 1190 Returns 0.
1190 1191 """
1191 1192
1192 1193 opts = pycompat.byteskwargs(opts)
1193 1194 revs = opts.get('rev')
1194 1195 selectedbranches = None
1195 1196 if revs:
1196 1197 revs = scmutil.revrange(repo, revs)
1197 1198 getbi = repo.revbranchcache().branchinfo
1198 1199 selectedbranches = {getbi(r)[0] for r in revs}
1199 1200
1200 1201 ui.pager('branches')
1201 1202 fm = ui.formatter('branches', opts)
1202 1203 hexfunc = fm.hexfunc
1203 1204
1204 1205 allheads = set(repo.heads())
1205 1206 branches = []
1206 1207 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1207 1208 if selectedbranches is not None and tag not in selectedbranches:
1208 1209 continue
1209 1210 isactive = False
1210 1211 if not isclosed:
1211 1212 openheads = set(repo.branchmap().iteropen(heads))
1212 1213 isactive = bool(openheads & allheads)
1213 1214 branches.append((tag, repo[tip], isactive, not isclosed))
1214 1215 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1215 1216 reverse=True)
1216 1217
1217 1218 for tag, ctx, isactive, isopen in branches:
1218 1219 if active and not isactive:
1219 1220 continue
1220 1221 if isactive:
1221 1222 label = 'branches.active'
1222 1223 notice = ''
1223 1224 elif not isopen:
1224 1225 if not closed:
1225 1226 continue
1226 1227 label = 'branches.closed'
1227 1228 notice = _(' (closed)')
1228 1229 else:
1229 1230 label = 'branches.inactive'
1230 1231 notice = _(' (inactive)')
1231 1232 current = (tag == repo.dirstate.branch())
1232 1233 if current:
1233 1234 label = 'branches.current'
1234 1235
1235 1236 fm.startitem()
1236 1237 fm.write('branch', '%s', tag, label=label)
1237 1238 rev = ctx.rev()
1238 1239 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1239 1240 fmt = ' ' * padsize + ' %d:%s'
1240 1241 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1241 1242 label='log.changeset changeset.%s' % ctx.phasestr())
1242 1243 fm.context(ctx=ctx)
1243 1244 fm.data(active=isactive, closed=not isopen, current=current)
1244 1245 if not ui.quiet:
1245 1246 fm.plain(notice)
1246 1247 fm.plain('\n')
1247 1248 fm.end()
1248 1249
1249 1250 @command('bundle',
1250 1251 [('f', 'force', None, _('run even when the destination is unrelated')),
1251 1252 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1252 1253 _('REV')),
1253 1254 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1254 1255 _('BRANCH')),
1255 1256 ('', 'base', [],
1256 1257 _('a base changeset assumed to be available at the destination'),
1257 1258 _('REV')),
1258 1259 ('a', 'all', None, _('bundle all changesets in the repository')),
1259 1260 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1260 1261 ] + remoteopts,
1261 1262 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1262 1263 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1263 1264 def bundle(ui, repo, fname, dest=None, **opts):
1264 1265 """create a bundle file
1265 1266
1266 1267 Generate a bundle file containing data to be transferred to another
1267 1268 repository.
1268 1269
1269 1270 To create a bundle containing all changesets, use -a/--all
1270 1271 (or --base null). Otherwise, hg assumes the destination will have
1271 1272 all the nodes you specify with --base parameters. Otherwise, hg
1272 1273 will assume the repository has all the nodes in destination, or
1273 1274 default-push/default if no destination is specified, where destination
1274 1275 is the repository you provide through DEST option.
1275 1276
1276 1277 You can change bundle format with the -t/--type option. See
1277 1278 :hg:`help bundlespec` for documentation on this format. By default,
1278 1279 the most appropriate format is used and compression defaults to
1279 1280 bzip2.
1280 1281
1281 1282 The bundle file can then be transferred using conventional means
1282 1283 and applied to another repository with the unbundle or pull
1283 1284 command. This is useful when direct push and pull are not
1284 1285 available or when exporting an entire repository is undesirable.
1285 1286
1286 1287 Applying bundles preserves all changeset contents including
1287 1288 permissions, copy/rename information, and revision history.
1288 1289
1289 1290 Returns 0 on success, 1 if no changes found.
1290 1291 """
1291 1292 opts = pycompat.byteskwargs(opts)
1292 1293 revs = None
1293 1294 if 'rev' in opts:
1294 1295 revstrings = opts['rev']
1295 1296 revs = scmutil.revrange(repo, revstrings)
1296 1297 if revstrings and not revs:
1297 1298 raise error.Abort(_('no commits to bundle'))
1298 1299
1299 1300 bundletype = opts.get('type', 'bzip2').lower()
1300 1301 try:
1301 1302 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1302 1303 except error.UnsupportedBundleSpecification as e:
1303 1304 raise error.Abort(pycompat.bytestr(e),
1304 1305 hint=_("see 'hg help bundlespec' for supported "
1305 1306 "values for --type"))
1306 1307 cgversion = bundlespec.contentopts["cg.version"]
1307 1308
1308 1309 # Packed bundles are a pseudo bundle format for now.
1309 1310 if cgversion == 's1':
1310 1311 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1311 1312 hint=_("use 'hg debugcreatestreamclonebundle'"))
1312 1313
1313 1314 if opts.get('all'):
1314 1315 if dest:
1315 1316 raise error.Abort(_("--all is incompatible with specifying "
1316 1317 "a destination"))
1317 1318 if opts.get('base'):
1318 1319 ui.warn(_("ignoring --base because --all was specified\n"))
1319 1320 base = [nullrev]
1320 1321 else:
1321 1322 base = scmutil.revrange(repo, opts.get('base'))
1322 1323 if cgversion not in changegroup.supportedoutgoingversions(repo):
1323 1324 raise error.Abort(_("repository does not support bundle version %s") %
1324 1325 cgversion)
1325 1326
1326 1327 if base:
1327 1328 if dest:
1328 1329 raise error.Abort(_("--base is incompatible with specifying "
1329 1330 "a destination"))
1330 1331 common = [repo[rev].node() for rev in base]
1331 1332 heads = [repo[r].node() for r in revs] if revs else None
1332 1333 outgoing = discovery.outgoing(repo, common, heads)
1333 1334 else:
1334 1335 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1335 1336 dest, branches = hg.parseurl(dest, opts.get('branch'))
1336 1337 other = hg.peer(repo, opts, dest)
1337 1338 revs = [repo[r].hex() for r in revs]
1338 1339 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1339 1340 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1340 1341 outgoing = discovery.findcommonoutgoing(repo, other,
1341 1342 onlyheads=heads,
1342 1343 force=opts.get('force'),
1343 1344 portable=True)
1344 1345
1345 1346 if not outgoing.missing:
1346 1347 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1347 1348 return 1
1348 1349
1349 1350 if cgversion == '01': #bundle1
1350 1351 bversion = 'HG10' + bundlespec.wirecompression
1351 1352 bcompression = None
1352 1353 elif cgversion in ('02', '03'):
1353 1354 bversion = 'HG20'
1354 1355 bcompression = bundlespec.wirecompression
1355 1356 else:
1356 1357 raise error.ProgrammingError(
1357 1358 'bundle: unexpected changegroup version %s' % cgversion)
1358 1359
1359 1360 # TODO compression options should be derived from bundlespec parsing.
1360 1361 # This is a temporary hack to allow adjusting bundle compression
1361 1362 # level without a) formalizing the bundlespec changes to declare it
1362 1363 # b) introducing a command flag.
1363 1364 compopts = {}
1364 1365 complevel = ui.configint('experimental',
1365 1366 'bundlecomplevel.' + bundlespec.compression)
1366 1367 if complevel is None:
1367 1368 complevel = ui.configint('experimental', 'bundlecomplevel')
1368 1369 if complevel is not None:
1369 1370 compopts['level'] = complevel
1370 1371
1371 1372 # Allow overriding the bundling of obsmarker in phases through
1372 1373 # configuration while we don't have a bundle version that include them
1373 1374 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1374 1375 bundlespec.contentopts['obsolescence'] = True
1375 1376 if repo.ui.configbool('experimental', 'bundle-phases'):
1376 1377 bundlespec.contentopts['phases'] = True
1377 1378
1378 1379 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1379 1380 bundlespec.contentopts, compression=bcompression,
1380 1381 compopts=compopts)
1381 1382
1382 1383 @command('cat',
1383 1384 [('o', 'output', '',
1384 1385 _('print output to file with formatted name'), _('FORMAT')),
1385 1386 ('r', 'rev', '', _('print the given revision'), _('REV')),
1386 1387 ('', 'decode', None, _('apply any matching decode filter')),
1387 1388 ] + walkopts + formatteropts,
1388 1389 _('[OPTION]... FILE...'),
1389 1390 helpcategory=command.CATEGORY_FILE_CONTENTS,
1390 1391 inferrepo=True,
1391 1392 intents={INTENT_READONLY})
1392 1393 def cat(ui, repo, file1, *pats, **opts):
1393 1394 """output the current or given revision of files
1394 1395
1395 1396 Print the specified files as they were at the given revision. If
1396 1397 no revision is given, the parent of the working directory is used.
1397 1398
1398 1399 Output may be to a file, in which case the name of the file is
1399 1400 given using a template string. See :hg:`help templates`. In addition
1400 1401 to the common template keywords, the following formatting rules are
1401 1402 supported:
1402 1403
1403 1404 :``%%``: literal "%" character
1404 1405 :``%s``: basename of file being printed
1405 1406 :``%d``: dirname of file being printed, or '.' if in repository root
1406 1407 :``%p``: root-relative path name of file being printed
1407 1408 :``%H``: changeset hash (40 hexadecimal digits)
1408 1409 :``%R``: changeset revision number
1409 1410 :``%h``: short-form changeset hash (12 hexadecimal digits)
1410 1411 :``%r``: zero-padded changeset revision number
1411 1412 :``%b``: basename of the exporting repository
1412 1413 :``\\``: literal "\\" character
1413 1414
1414 1415 .. container:: verbose
1415 1416
1416 1417 Template:
1417 1418
1418 1419 The following keywords are supported in addition to the common template
1419 1420 keywords and functions. See also :hg:`help templates`.
1420 1421
1421 1422 :data: String. File content.
1422 1423 :path: String. Repository-absolute path of the file.
1423 1424
1424 1425 Returns 0 on success.
1425 1426 """
1426 1427 opts = pycompat.byteskwargs(opts)
1427 1428 rev = opts.get('rev')
1428 1429 if rev:
1429 1430 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1430 1431 ctx = scmutil.revsingle(repo, rev)
1431 1432 m = scmutil.match(ctx, (file1,) + pats, opts)
1432 1433 fntemplate = opts.pop('output', '')
1433 1434 if cmdutil.isstdiofilename(fntemplate):
1434 1435 fntemplate = ''
1435 1436
1436 1437 if fntemplate:
1437 1438 fm = formatter.nullformatter(ui, 'cat', opts)
1438 1439 else:
1439 1440 ui.pager('cat')
1440 1441 fm = ui.formatter('cat', opts)
1441 1442 with fm:
1442 1443 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1443 1444 **pycompat.strkwargs(opts))
1444 1445
1445 1446 @command('clone',
1446 1447 [('U', 'noupdate', None, _('the clone will include an empty working '
1447 1448 'directory (only a repository)')),
1448 1449 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1449 1450 _('REV')),
1450 1451 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1451 1452 ' and its ancestors'), _('REV')),
1452 1453 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1453 1454 ' changesets and their ancestors'), _('BRANCH')),
1454 1455 ('', 'pull', None, _('use pull protocol to copy metadata')),
1455 1456 ('', 'uncompressed', None,
1456 1457 _('an alias to --stream (DEPRECATED)')),
1457 1458 ('', 'stream', None,
1458 1459 _('clone with minimal data processing')),
1459 1460 ] + remoteopts,
1460 1461 _('[OPTION]... SOURCE [DEST]'),
1461 1462 helpcategory=command.CATEGORY_REPO_CREATION,
1462 1463 helpbasic=True, norepo=True)
1463 1464 def clone(ui, source, dest=None, **opts):
1464 1465 """make a copy of an existing repository
1465 1466
1466 1467 Create a copy of an existing repository in a new directory.
1467 1468
1468 1469 If no destination directory name is specified, it defaults to the
1469 1470 basename of the source.
1470 1471
1471 1472 The location of the source is added to the new repository's
1472 1473 ``.hg/hgrc`` file, as the default to be used for future pulls.
1473 1474
1474 1475 Only local paths and ``ssh://`` URLs are supported as
1475 1476 destinations. For ``ssh://`` destinations, no working directory or
1476 1477 ``.hg/hgrc`` will be created on the remote side.
1477 1478
1478 1479 If the source repository has a bookmark called '@' set, that
1479 1480 revision will be checked out in the new repository by default.
1480 1481
1481 1482 To check out a particular version, use -u/--update, or
1482 1483 -U/--noupdate to create a clone with no working directory.
1483 1484
1484 1485 To pull only a subset of changesets, specify one or more revisions
1485 1486 identifiers with -r/--rev or branches with -b/--branch. The
1486 1487 resulting clone will contain only the specified changesets and
1487 1488 their ancestors. These options (or 'clone src#rev dest') imply
1488 1489 --pull, even for local source repositories.
1489 1490
1490 1491 In normal clone mode, the remote normalizes repository data into a common
1491 1492 exchange format and the receiving end translates this data into its local
1492 1493 storage format. --stream activates a different clone mode that essentially
1493 1494 copies repository files from the remote with minimal data processing. This
1494 1495 significantly reduces the CPU cost of a clone both remotely and locally.
1495 1496 However, it often increases the transferred data size by 30-40%. This can
1496 1497 result in substantially faster clones where I/O throughput is plentiful,
1497 1498 especially for larger repositories. A side-effect of --stream clones is
1498 1499 that storage settings and requirements on the remote are applied locally:
1499 1500 a modern client may inherit legacy or inefficient storage used by the
1500 1501 remote or a legacy Mercurial client may not be able to clone from a
1501 1502 modern Mercurial remote.
1502 1503
1503 1504 .. note::
1504 1505
1505 1506 Specifying a tag will include the tagged changeset but not the
1506 1507 changeset containing the tag.
1507 1508
1508 1509 .. container:: verbose
1509 1510
1510 1511 For efficiency, hardlinks are used for cloning whenever the
1511 1512 source and destination are on the same filesystem (note this
1512 1513 applies only to the repository data, not to the working
1513 1514 directory). Some filesystems, such as AFS, implement hardlinking
1514 1515 incorrectly, but do not report errors. In these cases, use the
1515 1516 --pull option to avoid hardlinking.
1516 1517
1517 1518 Mercurial will update the working directory to the first applicable
1518 1519 revision from this list:
1519 1520
1520 1521 a) null if -U or the source repository has no changesets
1521 1522 b) if -u . and the source repository is local, the first parent of
1522 1523 the source repository's working directory
1523 1524 c) the changeset specified with -u (if a branch name, this means the
1524 1525 latest head of that branch)
1525 1526 d) the changeset specified with -r
1526 1527 e) the tipmost head specified with -b
1527 1528 f) the tipmost head specified with the url#branch source syntax
1528 1529 g) the revision marked with the '@' bookmark, if present
1529 1530 h) the tipmost head of the default branch
1530 1531 i) tip
1531 1532
1532 1533 When cloning from servers that support it, Mercurial may fetch
1533 1534 pre-generated data from a server-advertised URL or inline from the
1534 1535 same stream. When this is done, hooks operating on incoming changesets
1535 1536 and changegroups may fire more than once, once for each pre-generated
1536 1537 bundle and as well as for any additional remaining data. In addition,
1537 1538 if an error occurs, the repository may be rolled back to a partial
1538 1539 clone. This behavior may change in future releases.
1539 1540 See :hg:`help -e clonebundles` for more.
1540 1541
1541 1542 Examples:
1542 1543
1543 1544 - clone a remote repository to a new directory named hg/::
1544 1545
1545 1546 hg clone https://www.mercurial-scm.org/repo/hg/
1546 1547
1547 1548 - create a lightweight local clone::
1548 1549
1549 1550 hg clone project/ project-feature/
1550 1551
1551 1552 - clone from an absolute path on an ssh server (note double-slash)::
1552 1553
1553 1554 hg clone ssh://user@server//home/projects/alpha/
1554 1555
1555 1556 - do a streaming clone while checking out a specified version::
1556 1557
1557 1558 hg clone --stream http://server/repo -u 1.5
1558 1559
1559 1560 - create a repository without changesets after a particular revision::
1560 1561
1561 1562 hg clone -r 04e544 experimental/ good/
1562 1563
1563 1564 - clone (and track) a particular named branch::
1564 1565
1565 1566 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1566 1567
1567 1568 See :hg:`help urls` for details on specifying URLs.
1568 1569
1569 1570 Returns 0 on success.
1570 1571 """
1571 1572 opts = pycompat.byteskwargs(opts)
1572 1573 if opts.get('noupdate') and opts.get('updaterev'):
1573 1574 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1574 1575
1575 1576 # --include/--exclude can come from narrow or sparse.
1576 1577 includepats, excludepats = None, None
1577 1578
1578 1579 # hg.clone() differentiates between None and an empty set. So make sure
1579 1580 # patterns are sets if narrow is requested without patterns.
1580 1581 if opts.get('narrow'):
1581 1582 includepats = set()
1582 1583 excludepats = set()
1583 1584
1584 1585 if opts.get('include'):
1585 1586 includepats = narrowspec.parsepatterns(opts.get('include'))
1586 1587 if opts.get('exclude'):
1587 1588 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1588 1589
1589 1590 r = hg.clone(ui, opts, source, dest,
1590 1591 pull=opts.get('pull'),
1591 1592 stream=opts.get('stream') or opts.get('uncompressed'),
1592 1593 revs=opts.get('rev'),
1593 1594 update=opts.get('updaterev') or not opts.get('noupdate'),
1594 1595 branch=opts.get('branch'),
1595 1596 shareopts=opts.get('shareopts'),
1596 1597 storeincludepats=includepats,
1597 1598 storeexcludepats=excludepats,
1598 1599 depth=opts.get('depth') or None)
1599 1600
1600 1601 return r is None
1601 1602
1602 1603 @command('commit|ci',
1603 1604 [('A', 'addremove', None,
1604 1605 _('mark new/missing files as added/removed before committing')),
1605 1606 ('', 'close-branch', None,
1606 1607 _('mark a branch head as closed')),
1607 1608 ('', 'amend', None, _('amend the parent of the working directory')),
1608 1609 ('s', 'secret', None, _('use the secret phase for committing')),
1609 1610 ('e', 'edit', None, _('invoke editor on commit messages')),
1610 1611 ('', 'force-close-branch', None,
1611 1612 _('forcibly close branch from a non-head changeset (ADVANCED)')),
1612 1613 ('i', 'interactive', None, _('use interactive mode')),
1613 1614 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1614 1615 _('[OPTION]... [FILE]...'),
1615 1616 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1616 1617 inferrepo=True)
1617 1618 def commit(ui, repo, *pats, **opts):
1618 1619 """commit the specified files or all outstanding changes
1619 1620
1620 1621 Commit changes to the given files into the repository. Unlike a
1621 1622 centralized SCM, this operation is a local operation. See
1622 1623 :hg:`push` for a way to actively distribute your changes.
1623 1624
1624 1625 If a list of files is omitted, all changes reported by :hg:`status`
1625 1626 will be committed.
1626 1627
1627 1628 If you are committing the result of a merge, do not provide any
1628 1629 filenames or -I/-X filters.
1629 1630
1630 1631 If no commit message is specified, Mercurial starts your
1631 1632 configured editor where you can enter a message. In case your
1632 1633 commit fails, you will find a backup of your message in
1633 1634 ``.hg/last-message.txt``.
1634 1635
1635 1636 The --close-branch flag can be used to mark the current branch
1636 1637 head closed. When all heads of a branch are closed, the branch
1637 1638 will be considered closed and no longer listed.
1638 1639
1639 1640 The --amend flag can be used to amend the parent of the
1640 1641 working directory with a new commit that contains the changes
1641 1642 in the parent in addition to those currently reported by :hg:`status`,
1642 1643 if there are any. The old commit is stored in a backup bundle in
1643 1644 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1644 1645 on how to restore it).
1645 1646
1646 1647 Message, user and date are taken from the amended commit unless
1647 1648 specified. When a message isn't specified on the command line,
1648 1649 the editor will open with the message of the amended commit.
1649 1650
1650 1651 It is not possible to amend public changesets (see :hg:`help phases`)
1651 1652 or changesets that have children.
1652 1653
1653 1654 See :hg:`help dates` for a list of formats valid for -d/--date.
1654 1655
1655 1656 Returns 0 on success, 1 if nothing changed.
1656 1657
1657 1658 .. container:: verbose
1658 1659
1659 1660 Examples:
1660 1661
1661 1662 - commit all files ending in .py::
1662 1663
1663 1664 hg commit --include "set:**.py"
1664 1665
1665 1666 - commit all non-binary files::
1666 1667
1667 1668 hg commit --exclude "set:binary()"
1668 1669
1669 1670 - amend the current commit and set the date to now::
1670 1671
1671 1672 hg commit --amend --date now
1672 1673 """
1673 1674 with repo.wlock(), repo.lock():
1674 1675 return _docommit(ui, repo, *pats, **opts)
1675 1676
1676 1677 def _docommit(ui, repo, *pats, **opts):
1677 1678 if opts.get(r'interactive'):
1678 1679 opts.pop(r'interactive')
1679 1680 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1680 1681 cmdutil.recordfilter, *pats,
1681 1682 **opts)
1682 1683 # ret can be 0 (no changes to record) or the value returned by
1683 1684 # commit(), 1 if nothing changed or None on success.
1684 1685 return 1 if ret == 0 else ret
1685 1686
1686 1687 opts = pycompat.byteskwargs(opts)
1687 1688 if opts.get('subrepos'):
1688 1689 if opts.get('amend'):
1689 1690 raise error.Abort(_('cannot amend with --subrepos'))
1690 1691 # Let --subrepos on the command line override config setting.
1691 1692 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1692 1693
1693 1694 cmdutil.checkunfinished(repo, commit=True)
1694 1695
1695 1696 branch = repo[None].branch()
1696 1697 bheads = repo.branchheads(branch)
1697 1698
1698 1699 extra = {}
1699 1700 if opts.get('close_branch') or opts.get('force_close_branch'):
1700 1701 extra['close'] = '1'
1701 1702
1702 1703 if repo['.'].closesbranch():
1703 1704 raise error.Abort(_('current revision is already a branch closing'
1704 1705 ' head'))
1705 1706 elif not bheads:
1706 1707 raise error.Abort(_('branch "%s" has no heads to close') % branch)
1707 1708 elif (branch == repo['.'].branch() and repo['.'].node() not in bheads
1708 1709 and not opts.get('force_close_branch')):
1709 1710 hint = _('use --force-close-branch to close branch from a non-head'
1710 1711 ' changeset')
1711 1712 raise error.Abort(_('can only close branch heads'), hint=hint)
1712 1713 elif opts.get('amend'):
1713 1714 if (repo['.'].p1().branch() != branch and
1714 1715 repo['.'].p2().branch() != branch):
1715 1716 raise error.Abort(_('can only close branch heads'))
1716 1717
1717 1718 if opts.get('amend'):
1718 1719 if ui.configbool('ui', 'commitsubrepos'):
1719 1720 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1720 1721
1721 1722 old = repo['.']
1722 1723 rewriteutil.precheck(repo, [old.rev()], 'amend')
1723 1724
1724 1725 # Currently histedit gets confused if an amend happens while histedit
1725 1726 # is in progress. Since we have a checkunfinished command, we are
1726 1727 # temporarily honoring it.
1727 1728 #
1728 1729 # Note: eventually this guard will be removed. Please do not expect
1729 1730 # this behavior to remain.
1730 1731 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1731 1732 cmdutil.checkunfinished(repo)
1732 1733
1733 1734 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1734 1735 if node == old.node():
1735 1736 ui.status(_("nothing changed\n"))
1736 1737 return 1
1737 1738 else:
1738 1739 def commitfunc(ui, repo, message, match, opts):
1739 1740 overrides = {}
1740 1741 if opts.get('secret'):
1741 1742 overrides[('phases', 'new-commit')] = 'secret'
1742 1743
1743 1744 baseui = repo.baseui
1744 1745 with baseui.configoverride(overrides, 'commit'):
1745 1746 with ui.configoverride(overrides, 'commit'):
1746 1747 editform = cmdutil.mergeeditform(repo[None],
1747 1748 'commit.normal')
1748 1749 editor = cmdutil.getcommiteditor(
1749 1750 editform=editform, **pycompat.strkwargs(opts))
1750 1751 return repo.commit(message,
1751 1752 opts.get('user'),
1752 1753 opts.get('date'),
1753 1754 match,
1754 1755 editor=editor,
1755 1756 extra=extra)
1756 1757
1757 1758 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1758 1759
1759 1760 if not node:
1760 1761 stat = cmdutil.postcommitstatus(repo, pats, opts)
1761 1762 if stat[3]:
1762 1763 ui.status(_("nothing changed (%d missing files, see "
1763 1764 "'hg status')\n") % len(stat[3]))
1764 1765 else:
1765 1766 ui.status(_("nothing changed\n"))
1766 1767 return 1
1767 1768
1768 1769 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1769 1770
1770 1771 if not ui.quiet and ui.configbool('commands', 'commit.post-status'):
1771 1772 status(ui, repo, modified=True, added=True, removed=True, deleted=True,
1772 1773 unknown=True, subrepos=opts.get('subrepos'))
1773 1774
1774 1775 @command('config|showconfig|debugconfig',
1775 1776 [('u', 'untrusted', None, _('show untrusted configuration options')),
1776 1777 ('e', 'edit', None, _('edit user config')),
1777 1778 ('l', 'local', None, _('edit repository config')),
1778 1779 ('g', 'global', None, _('edit global config'))] + formatteropts,
1779 1780 _('[-u] [NAME]...'),
1780 1781 helpcategory=command.CATEGORY_HELP,
1781 1782 optionalrepo=True,
1782 1783 intents={INTENT_READONLY})
1783 1784 def config(ui, repo, *values, **opts):
1784 1785 """show combined config settings from all hgrc files
1785 1786
1786 1787 With no arguments, print names and values of all config items.
1787 1788
1788 1789 With one argument of the form section.name, print just the value
1789 1790 of that config item.
1790 1791
1791 1792 With multiple arguments, print names and values of all config
1792 1793 items with matching section names or section.names.
1793 1794
1794 1795 With --edit, start an editor on the user-level config file. With
1795 1796 --global, edit the system-wide config file. With --local, edit the
1796 1797 repository-level config file.
1797 1798
1798 1799 With --debug, the source (filename and line number) is printed
1799 1800 for each config item.
1800 1801
1801 1802 See :hg:`help config` for more information about config files.
1802 1803
1803 1804 .. container:: verbose
1804 1805
1805 1806 Template:
1806 1807
1807 1808 The following keywords are supported. See also :hg:`help templates`.
1808 1809
1809 1810 :name: String. Config name.
1810 1811 :source: String. Filename and line number where the item is defined.
1811 1812 :value: String. Config value.
1812 1813
1813 1814 Returns 0 on success, 1 if NAME does not exist.
1814 1815
1815 1816 """
1816 1817
1817 1818 opts = pycompat.byteskwargs(opts)
1818 1819 if opts.get('edit') or opts.get('local') or opts.get('global'):
1819 1820 if opts.get('local') and opts.get('global'):
1820 1821 raise error.Abort(_("can't use --local and --global together"))
1821 1822
1822 1823 if opts.get('local'):
1823 1824 if not repo:
1824 1825 raise error.Abort(_("can't use --local outside a repository"))
1825 1826 paths = [repo.vfs.join('hgrc')]
1826 1827 elif opts.get('global'):
1827 1828 paths = rcutil.systemrcpath()
1828 1829 else:
1829 1830 paths = rcutil.userrcpath()
1830 1831
1831 1832 for f in paths:
1832 1833 if os.path.exists(f):
1833 1834 break
1834 1835 else:
1835 1836 if opts.get('global'):
1836 1837 samplehgrc = uimod.samplehgrcs['global']
1837 1838 elif opts.get('local'):
1838 1839 samplehgrc = uimod.samplehgrcs['local']
1839 1840 else:
1840 1841 samplehgrc = uimod.samplehgrcs['user']
1841 1842
1842 1843 f = paths[0]
1843 1844 fp = open(f, "wb")
1844 1845 fp.write(util.tonativeeol(samplehgrc))
1845 1846 fp.close()
1846 1847
1847 1848 editor = ui.geteditor()
1848 1849 ui.system("%s \"%s\"" % (editor, f),
1849 1850 onerr=error.Abort, errprefix=_("edit failed"),
1850 1851 blockedtag='config_edit')
1851 1852 return
1852 1853 ui.pager('config')
1853 1854 fm = ui.formatter('config', opts)
1854 1855 for t, f in rcutil.rccomponents():
1855 1856 if t == 'path':
1856 1857 ui.debug('read config from: %s\n' % f)
1857 1858 elif t == 'items':
1858 1859 for section, name, value, source in f:
1859 1860 ui.debug('set config by: %s\n' % source)
1860 1861 else:
1861 1862 raise error.ProgrammingError('unknown rctype: %s' % t)
1862 1863 untrusted = bool(opts.get('untrusted'))
1863 1864
1864 1865 selsections = selentries = []
1865 1866 if values:
1866 1867 selsections = [v for v in values if '.' not in v]
1867 1868 selentries = [v for v in values if '.' in v]
1868 1869 uniquesel = (len(selentries) == 1 and not selsections)
1869 1870 selsections = set(selsections)
1870 1871 selentries = set(selentries)
1871 1872
1872 1873 matched = False
1873 1874 for section, name, value in ui.walkconfig(untrusted=untrusted):
1874 1875 source = ui.configsource(section, name, untrusted)
1875 1876 value = pycompat.bytestr(value)
1876 1877 defaultvalue = ui.configdefault(section, name)
1877 1878 if fm.isplain():
1878 1879 source = source or 'none'
1879 1880 value = value.replace('\n', '\\n')
1880 1881 entryname = section + '.' + name
1881 1882 if values and not (section in selsections or entryname in selentries):
1882 1883 continue
1883 1884 fm.startitem()
1884 1885 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1885 1886 if uniquesel:
1886 1887 fm.data(name=entryname)
1887 1888 fm.write('value', '%s\n', value)
1888 1889 else:
1889 1890 fm.write('name value', '%s=%s\n', entryname, value)
1890 1891 fm.data(defaultvalue=defaultvalue)
1891 1892 matched = True
1892 1893 fm.end()
1893 1894 if matched:
1894 1895 return 0
1895 1896 return 1
1896 1897
1897 1898 @command('continue',
1898 1899 dryrunopts, helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
1899 1900 helpbasic=True)
1900 1901 def continuecmd(ui, repo, **opts):
1901 1902 """resumes an interrupted operation (EXPERIMENTAL)
1902 1903
1903 1904 Finishes a multistep operation like graft, histedit, rebase, merge,
1904 1905 and unshelve if they are in an interrupted state.
1905 1906
1906 1907 use --dry-run/-n to dry run the command.
1907 1908 """
1908 1909 dryrun = opts.get(r'dry_run')
1909 1910 contstate = cmdutil.getunfinishedstate(repo)
1910 1911 if not contstate:
1911 1912 raise error.Abort(_('no operation in progress'))
1912 1913 if not contstate.continuefunc:
1913 1914 raise error.Abort((_("%s in progress but does not support "
1914 1915 "'hg continue'") % (contstate._opname)),
1915 1916 hint=contstate.continuemsg())
1916 1917 if dryrun:
1917 1918 ui.status(_('%s in progress, will be resumed\n') % (contstate._opname))
1918 1919 return
1919 1920 return contstate.continuefunc(ui, repo)
1920 1921
1921 1922 @command('copy|cp',
1922 1923 [('A', 'after', None, _('record a copy that has already occurred')),
1923 1924 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1924 1925 ] + walkopts + dryrunopts,
1925 1926 _('[OPTION]... SOURCE... DEST'),
1926 1927 helpcategory=command.CATEGORY_FILE_CONTENTS)
1927 1928 def copy(ui, repo, *pats, **opts):
1928 1929 """mark files as copied for the next commit
1929 1930
1930 1931 Mark dest as having copies of source files. If dest is a
1931 1932 directory, copies are put in that directory. If dest is a file,
1932 1933 the source must be a single file.
1933 1934
1934 1935 By default, this command copies the contents of files as they
1935 1936 exist in the working directory. If invoked with -A/--after, the
1936 1937 operation is recorded, but no copying is performed.
1937 1938
1938 1939 This command takes effect with the next commit. To undo a copy
1939 1940 before that, see :hg:`revert`.
1940 1941
1941 1942 Returns 0 on success, 1 if errors are encountered.
1942 1943 """
1943 1944 opts = pycompat.byteskwargs(opts)
1944 1945 with repo.wlock(False):
1945 1946 return cmdutil.copy(ui, repo, pats, opts)
1946 1947
1947 1948 @command(
1948 1949 'debugcommands', [], _('[COMMAND]'),
1949 1950 helpcategory=command.CATEGORY_HELP,
1950 1951 norepo=True)
1951 1952 def debugcommands(ui, cmd='', *args):
1952 1953 """list all available commands and options"""
1953 1954 for cmd, vals in sorted(table.iteritems()):
1954 1955 cmd = cmd.split('|')[0]
1955 1956 opts = ', '.join([i[1] for i in vals[1]])
1956 1957 ui.write('%s: %s\n' % (cmd, opts))
1957 1958
1958 1959 @command('debugcomplete',
1959 1960 [('o', 'options', None, _('show the command options'))],
1960 1961 _('[-o] CMD'),
1961 1962 helpcategory=command.CATEGORY_HELP,
1962 1963 norepo=True)
1963 1964 def debugcomplete(ui, cmd='', **opts):
1964 1965 """returns the completion list associated with the given command"""
1965 1966
1966 1967 if opts.get(r'options'):
1967 1968 options = []
1968 1969 otables = [globalopts]
1969 1970 if cmd:
1970 1971 aliases, entry = cmdutil.findcmd(cmd, table, False)
1971 1972 otables.append(entry[1])
1972 1973 for t in otables:
1973 1974 for o in t:
1974 1975 if "(DEPRECATED)" in o[3]:
1975 1976 continue
1976 1977 if o[0]:
1977 1978 options.append('-%s' % o[0])
1978 1979 options.append('--%s' % o[1])
1979 1980 ui.write("%s\n" % "\n".join(options))
1980 1981 return
1981 1982
1982 1983 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1983 1984 if ui.verbose:
1984 1985 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1985 1986 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1986 1987
1987 1988 @command('diff',
1988 1989 [('r', 'rev', [], _('revision'), _('REV')),
1989 1990 ('c', 'change', '', _('change made by revision'), _('REV'))
1990 1991 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1991 1992 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1992 1993 helpcategory=command.CATEGORY_FILE_CONTENTS,
1993 1994 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1994 1995 def diff(ui, repo, *pats, **opts):
1995 1996 """diff repository (or selected files)
1996 1997
1997 1998 Show differences between revisions for the specified files.
1998 1999
1999 2000 Differences between files are shown using the unified diff format.
2000 2001
2001 2002 .. note::
2002 2003
2003 2004 :hg:`diff` may generate unexpected results for merges, as it will
2004 2005 default to comparing against the working directory's first
2005 2006 parent changeset if no revisions are specified.
2006 2007
2007 2008 When two revision arguments are given, then changes are shown
2008 2009 between those revisions. If only one revision is specified then
2009 2010 that revision is compared to the working directory, and, when no
2010 2011 revisions are specified, the working directory files are compared
2011 2012 to its first parent.
2012 2013
2013 2014 Alternatively you can specify -c/--change with a revision to see
2014 2015 the changes in that changeset relative to its first parent.
2015 2016
2016 2017 Without the -a/--text option, diff will avoid generating diffs of
2017 2018 files it detects as binary. With -a, diff will generate a diff
2018 2019 anyway, probably with undesirable results.
2019 2020
2020 2021 Use the -g/--git option to generate diffs in the git extended diff
2021 2022 format. For more information, read :hg:`help diffs`.
2022 2023
2023 2024 .. container:: verbose
2024 2025
2025 2026 Examples:
2026 2027
2027 2028 - compare a file in the current working directory to its parent::
2028 2029
2029 2030 hg diff foo.c
2030 2031
2031 2032 - compare two historical versions of a directory, with rename info::
2032 2033
2033 2034 hg diff --git -r 1.0:1.2 lib/
2034 2035
2035 2036 - get change stats relative to the last change on some date::
2036 2037
2037 2038 hg diff --stat -r "date('may 2')"
2038 2039
2039 2040 - diff all newly-added files that contain a keyword::
2040 2041
2041 2042 hg diff "set:added() and grep(GNU)"
2042 2043
2043 2044 - compare a revision and its parents::
2044 2045
2045 2046 hg diff -c 9353 # compare against first parent
2046 2047 hg diff -r 9353^:9353 # same using revset syntax
2047 2048 hg diff -r 9353^2:9353 # compare against the second parent
2048 2049
2049 2050 Returns 0 on success.
2050 2051 """
2051 2052
2052 2053 opts = pycompat.byteskwargs(opts)
2053 2054 revs = opts.get('rev')
2054 2055 change = opts.get('change')
2055 2056 stat = opts.get('stat')
2056 2057 reverse = opts.get('reverse')
2057 2058
2058 2059 if revs and change:
2059 2060 msg = _('cannot specify --rev and --change at the same time')
2060 2061 raise error.Abort(msg)
2061 2062 elif change:
2062 2063 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
2063 2064 ctx2 = scmutil.revsingle(repo, change, None)
2064 2065 ctx1 = ctx2.p1()
2065 2066 else:
2066 2067 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2067 2068 ctx1, ctx2 = scmutil.revpair(repo, revs)
2068 2069 node1, node2 = ctx1.node(), ctx2.node()
2069 2070
2070 2071 if reverse:
2071 2072 node1, node2 = node2, node1
2072 2073
2073 2074 diffopts = patch.diffallopts(ui, opts)
2074 2075 m = scmutil.match(ctx2, pats, opts)
2075 2076 m = repo.narrowmatch(m)
2076 2077 ui.pager('diff')
2077 2078 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2078 2079 listsubrepos=opts.get('subrepos'),
2079 2080 root=opts.get('root'))
2080 2081
2081 2082 @command('export',
2082 2083 [('B', 'bookmark', '',
2083 2084 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2084 2085 ('o', 'output', '',
2085 2086 _('print output to file with formatted name'), _('FORMAT')),
2086 2087 ('', 'switch-parent', None, _('diff against the second parent')),
2087 2088 ('r', 'rev', [], _('revisions to export'), _('REV')),
2088 2089 ] + diffopts + formatteropts,
2089 2090 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2090 2091 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2091 2092 helpbasic=True, intents={INTENT_READONLY})
2092 2093 def export(ui, repo, *changesets, **opts):
2093 2094 """dump the header and diffs for one or more changesets
2094 2095
2095 2096 Print the changeset header and diffs for one or more revisions.
2096 2097 If no revision is given, the parent of the working directory is used.
2097 2098
2098 2099 The information shown in the changeset header is: author, date,
2099 2100 branch name (if non-default), changeset hash, parent(s) and commit
2100 2101 comment.
2101 2102
2102 2103 .. note::
2103 2104
2104 2105 :hg:`export` may generate unexpected diff output for merge
2105 2106 changesets, as it will compare the merge changeset against its
2106 2107 first parent only.
2107 2108
2108 2109 Output may be to a file, in which case the name of the file is
2109 2110 given using a template string. See :hg:`help templates`. In addition
2110 2111 to the common template keywords, the following formatting rules are
2111 2112 supported:
2112 2113
2113 2114 :``%%``: literal "%" character
2114 2115 :``%H``: changeset hash (40 hexadecimal digits)
2115 2116 :``%N``: number of patches being generated
2116 2117 :``%R``: changeset revision number
2117 2118 :``%b``: basename of the exporting repository
2118 2119 :``%h``: short-form changeset hash (12 hexadecimal digits)
2119 2120 :``%m``: first line of the commit message (only alphanumeric characters)
2120 2121 :``%n``: zero-padded sequence number, starting at 1
2121 2122 :``%r``: zero-padded changeset revision number
2122 2123 :``\\``: literal "\\" character
2123 2124
2124 2125 Without the -a/--text option, export will avoid generating diffs
2125 2126 of files it detects as binary. With -a, export will generate a
2126 2127 diff anyway, probably with undesirable results.
2127 2128
2128 2129 With -B/--bookmark changesets reachable by the given bookmark are
2129 2130 selected.
2130 2131
2131 2132 Use the -g/--git option to generate diffs in the git extended diff
2132 2133 format. See :hg:`help diffs` for more information.
2133 2134
2134 2135 With the --switch-parent option, the diff will be against the
2135 2136 second parent. It can be useful to review a merge.
2136 2137
2137 2138 .. container:: verbose
2138 2139
2139 2140 Template:
2140 2141
2141 2142 The following keywords are supported in addition to the common template
2142 2143 keywords and functions. See also :hg:`help templates`.
2143 2144
2144 2145 :diff: String. Diff content.
2145 2146 :parents: List of strings. Parent nodes of the changeset.
2146 2147
2147 2148 Examples:
2148 2149
2149 2150 - use export and import to transplant a bugfix to the current
2150 2151 branch::
2151 2152
2152 2153 hg export -r 9353 | hg import -
2153 2154
2154 2155 - export all the changesets between two revisions to a file with
2155 2156 rename information::
2156 2157
2157 2158 hg export --git -r 123:150 > changes.txt
2158 2159
2159 2160 - split outgoing changes into a series of patches with
2160 2161 descriptive names::
2161 2162
2162 2163 hg export -r "outgoing()" -o "%n-%m.patch"
2163 2164
2164 2165 Returns 0 on success.
2165 2166 """
2166 2167 opts = pycompat.byteskwargs(opts)
2167 2168 bookmark = opts.get('bookmark')
2168 2169 changesets += tuple(opts.get('rev', []))
2169 2170
2170 2171 if bookmark and changesets:
2171 2172 raise error.Abort(_("-r and -B are mutually exclusive"))
2172 2173
2173 2174 if bookmark:
2174 2175 if bookmark not in repo._bookmarks:
2175 2176 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2176 2177
2177 2178 revs = scmutil.bookmarkrevs(repo, bookmark)
2178 2179 else:
2179 2180 if not changesets:
2180 2181 changesets = ['.']
2181 2182
2182 2183 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2183 2184 revs = scmutil.revrange(repo, changesets)
2184 2185
2185 2186 if not revs:
2186 2187 raise error.Abort(_("export requires at least one changeset"))
2187 2188 if len(revs) > 1:
2188 2189 ui.note(_('exporting patches:\n'))
2189 2190 else:
2190 2191 ui.note(_('exporting patch:\n'))
2191 2192
2192 2193 fntemplate = opts.get('output')
2193 2194 if cmdutil.isstdiofilename(fntemplate):
2194 2195 fntemplate = ''
2195 2196
2196 2197 if fntemplate:
2197 2198 fm = formatter.nullformatter(ui, 'export', opts)
2198 2199 else:
2199 2200 ui.pager('export')
2200 2201 fm = ui.formatter('export', opts)
2201 2202 with fm:
2202 2203 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2203 2204 switch_parent=opts.get('switch_parent'),
2204 2205 opts=patch.diffallopts(ui, opts))
2205 2206
2206 2207 @command('files',
2207 2208 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2208 2209 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2209 2210 ] + walkopts + formatteropts + subrepoopts,
2210 2211 _('[OPTION]... [FILE]...'),
2211 2212 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2212 2213 intents={INTENT_READONLY})
2213 2214 def files(ui, repo, *pats, **opts):
2214 2215 """list tracked files
2215 2216
2216 2217 Print files under Mercurial control in the working directory or
2217 2218 specified revision for given files (excluding removed files).
2218 2219 Files can be specified as filenames or filesets.
2219 2220
2220 2221 If no files are given to match, this command prints the names
2221 2222 of all files under Mercurial control.
2222 2223
2223 2224 .. container:: verbose
2224 2225
2225 2226 Template:
2226 2227
2227 2228 The following keywords are supported in addition to the common template
2228 2229 keywords and functions. See also :hg:`help templates`.
2229 2230
2230 2231 :flags: String. Character denoting file's symlink and executable bits.
2231 2232 :path: String. Repository-absolute path of the file.
2232 2233 :size: Integer. Size of the file in bytes.
2233 2234
2234 2235 Examples:
2235 2236
2236 2237 - list all files under the current directory::
2237 2238
2238 2239 hg files .
2239 2240
2240 2241 - shows sizes and flags for current revision::
2241 2242
2242 2243 hg files -vr .
2243 2244
2244 2245 - list all files named README::
2245 2246
2246 2247 hg files -I "**/README"
2247 2248
2248 2249 - list all binary files::
2249 2250
2250 2251 hg files "set:binary()"
2251 2252
2252 2253 - find files containing a regular expression::
2253 2254
2254 2255 hg files "set:grep('bob')"
2255 2256
2256 2257 - search tracked file contents with xargs and grep::
2257 2258
2258 2259 hg files -0 | xargs -0 grep foo
2259 2260
2260 2261 See :hg:`help patterns` and :hg:`help filesets` for more information
2261 2262 on specifying file patterns.
2262 2263
2263 2264 Returns 0 if a match is found, 1 otherwise.
2264 2265
2265 2266 """
2266 2267
2267 2268 opts = pycompat.byteskwargs(opts)
2268 2269 rev = opts.get('rev')
2269 2270 if rev:
2270 2271 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2271 2272 ctx = scmutil.revsingle(repo, rev, None)
2272 2273
2273 2274 end = '\n'
2274 2275 if opts.get('print0'):
2275 2276 end = '\0'
2276 2277 fmt = '%s' + end
2277 2278
2278 2279 m = scmutil.match(ctx, pats, opts)
2279 2280 ui.pager('files')
2280 2281 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2281 2282 with ui.formatter('files', opts) as fm:
2282 2283 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt,
2283 2284 opts.get('subrepos'))
2284 2285
2285 2286 @command(
2286 2287 'forget',
2287 2288 [('i', 'interactive', None, _('use interactive mode')),
2288 2289 ] + walkopts + dryrunopts,
2289 2290 _('[OPTION]... FILE...'),
2290 2291 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2291 2292 helpbasic=True, inferrepo=True)
2292 2293 def forget(ui, repo, *pats, **opts):
2293 2294 """forget the specified files on the next commit
2294 2295
2295 2296 Mark the specified files so they will no longer be tracked
2296 2297 after the next commit.
2297 2298
2298 2299 This only removes files from the current branch, not from the
2299 2300 entire project history, and it does not delete them from the
2300 2301 working directory.
2301 2302
2302 2303 To delete the file from the working directory, see :hg:`remove`.
2303 2304
2304 2305 To undo a forget before the next commit, see :hg:`add`.
2305 2306
2306 2307 .. container:: verbose
2307 2308
2308 2309 Examples:
2309 2310
2310 2311 - forget newly-added binary files::
2311 2312
2312 2313 hg forget "set:added() and binary()"
2313 2314
2314 2315 - forget files that would be excluded by .hgignore::
2315 2316
2316 2317 hg forget "set:hgignore()"
2317 2318
2318 2319 Returns 0 on success.
2319 2320 """
2320 2321
2321 2322 opts = pycompat.byteskwargs(opts)
2322 2323 if not pats:
2323 2324 raise error.Abort(_('no files specified'))
2324 2325
2325 2326 m = scmutil.match(repo[None], pats, opts)
2326 2327 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2327 2328 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2328 2329 rejected = cmdutil.forget(ui, repo, m, prefix="", uipathfn=uipathfn,
2329 2330 explicitonly=False, dryrun=dryrun,
2330 2331 interactive=interactive)[0]
2331 2332 return rejected and 1 or 0
2332 2333
2333 2334 @command(
2334 2335 'graft',
2335 2336 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2336 2337 ('', 'base', '',
2337 2338 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2338 2339 ('c', 'continue', False, _('resume interrupted graft')),
2339 2340 ('', 'stop', False, _('stop interrupted graft')),
2340 2341 ('', 'abort', False, _('abort interrupted graft')),
2341 2342 ('e', 'edit', False, _('invoke editor on commit messages')),
2342 2343 ('', 'log', None, _('append graft info to log message')),
2343 2344 ('', 'no-commit', None,
2344 2345 _("don't commit, just apply the changes in working directory")),
2345 2346 ('f', 'force', False, _('force graft')),
2346 2347 ('D', 'currentdate', False,
2347 2348 _('record the current date as commit date')),
2348 2349 ('U', 'currentuser', False,
2349 2350 _('record the current user as committer'))]
2350 2351 + commitopts2 + mergetoolopts + dryrunopts,
2351 2352 _('[OPTION]... [-r REV]... REV...'),
2352 2353 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2353 2354 def graft(ui, repo, *revs, **opts):
2354 2355 '''copy changes from other branches onto the current branch
2355 2356
2356 2357 This command uses Mercurial's merge logic to copy individual
2357 2358 changes from other branches without merging branches in the
2358 2359 history graph. This is sometimes known as 'backporting' or
2359 2360 'cherry-picking'. By default, graft will copy user, date, and
2360 2361 description from the source changesets.
2361 2362
2362 2363 Changesets that are ancestors of the current revision, that have
2363 2364 already been grafted, or that are merges will be skipped.
2364 2365
2365 2366 If --log is specified, log messages will have a comment appended
2366 2367 of the form::
2367 2368
2368 2369 (grafted from CHANGESETHASH)
2369 2370
2370 2371 If --force is specified, revisions will be grafted even if they
2371 2372 are already ancestors of, or have been grafted to, the destination.
2372 2373 This is useful when the revisions have since been backed out.
2373 2374
2374 2375 If a graft merge results in conflicts, the graft process is
2375 2376 interrupted so that the current merge can be manually resolved.
2376 2377 Once all conflicts are addressed, the graft process can be
2377 2378 continued with the -c/--continue option.
2378 2379
2379 2380 The -c/--continue option reapplies all the earlier options.
2380 2381
2381 2382 .. container:: verbose
2382 2383
2383 2384 The --base option exposes more of how graft internally uses merge with a
2384 2385 custom base revision. --base can be used to specify another ancestor than
2385 2386 the first and only parent.
2386 2387
2387 2388 The command::
2388 2389
2389 2390 hg graft -r 345 --base 234
2390 2391
2391 2392 is thus pretty much the same as::
2392 2393
2393 2394 hg diff -r 234 -r 345 | hg import
2394 2395
2395 2396 but using merge to resolve conflicts and track moved files.
2396 2397
2397 2398 The result of a merge can thus be backported as a single commit by
2398 2399 specifying one of the merge parents as base, and thus effectively
2399 2400 grafting the changes from the other side.
2400 2401
2401 2402 It is also possible to collapse multiple changesets and clean up history
2402 2403 by specifying another ancestor as base, much like rebase --collapse
2403 2404 --keep.
2404 2405
2405 2406 The commit message can be tweaked after the fact using commit --amend .
2406 2407
2407 2408 For using non-ancestors as the base to backout changes, see the backout
2408 2409 command and the hidden --parent option.
2409 2410
2410 2411 .. container:: verbose
2411 2412
2412 2413 Examples:
2413 2414
2414 2415 - copy a single change to the stable branch and edit its description::
2415 2416
2416 2417 hg update stable
2417 2418 hg graft --edit 9393
2418 2419
2419 2420 - graft a range of changesets with one exception, updating dates::
2420 2421
2421 2422 hg graft -D "2085::2093 and not 2091"
2422 2423
2423 2424 - continue a graft after resolving conflicts::
2424 2425
2425 2426 hg graft -c
2426 2427
2427 2428 - show the source of a grafted changeset::
2428 2429
2429 2430 hg log --debug -r .
2430 2431
2431 2432 - show revisions sorted by date::
2432 2433
2433 2434 hg log -r "sort(all(), date)"
2434 2435
2435 2436 - backport the result of a merge as a single commit::
2436 2437
2437 2438 hg graft -r 123 --base 123^
2438 2439
2439 2440 - land a feature branch as one changeset::
2440 2441
2441 2442 hg up -cr default
2442 2443 hg graft -r featureX --base "ancestor('featureX', 'default')"
2443 2444
2444 2445 See :hg:`help revisions` for more about specifying revisions.
2445 2446
2446 2447 Returns 0 on successful completion.
2447 2448 '''
2448 2449 with repo.wlock():
2449 2450 return _dograft(ui, repo, *revs, **opts)
2450 2451
2451 2452 def _dograft(ui, repo, *revs, **opts):
2452 2453 opts = pycompat.byteskwargs(opts)
2453 2454 if revs and opts.get('rev'):
2454 2455 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2455 2456 'revision ordering!\n'))
2456 2457
2457 2458 revs = list(revs)
2458 2459 revs.extend(opts.get('rev'))
2459 2460 basectx = None
2460 2461 if opts.get('base'):
2461 2462 basectx = scmutil.revsingle(repo, opts['base'], None)
2462 2463 # a dict of data to be stored in state file
2463 2464 statedata = {}
2464 2465 # list of new nodes created by ongoing graft
2465 2466 statedata['newnodes'] = []
2466 2467
2467 2468 if opts.get('user') and opts.get('currentuser'):
2468 2469 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2469 2470 if opts.get('date') and opts.get('currentdate'):
2470 2471 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2471 2472 if not opts.get('user') and opts.get('currentuser'):
2472 2473 opts['user'] = ui.username()
2473 2474 if not opts.get('date') and opts.get('currentdate'):
2474 2475 opts['date'] = "%d %d" % dateutil.makedate()
2475 2476
2476 2477 editor = cmdutil.getcommiteditor(editform='graft',
2477 2478 **pycompat.strkwargs(opts))
2478 2479
2479 2480 cont = False
2480 2481 if opts.get('no_commit'):
2481 2482 if opts.get('edit'):
2482 2483 raise error.Abort(_("cannot specify --no-commit and "
2483 2484 "--edit together"))
2484 2485 if opts.get('currentuser'):
2485 2486 raise error.Abort(_("cannot specify --no-commit and "
2486 2487 "--currentuser together"))
2487 2488 if opts.get('currentdate'):
2488 2489 raise error.Abort(_("cannot specify --no-commit and "
2489 2490 "--currentdate together"))
2490 2491 if opts.get('log'):
2491 2492 raise error.Abort(_("cannot specify --no-commit and "
2492 2493 "--log together"))
2493 2494
2494 2495 graftstate = statemod.cmdstate(repo, 'graftstate')
2495 2496
2496 2497 if opts.get('stop'):
2497 2498 if opts.get('continue'):
2498 2499 raise error.Abort(_("cannot use '--continue' and "
2499 2500 "'--stop' together"))
2500 2501 if opts.get('abort'):
2501 2502 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2502 2503
2503 2504 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2504 2505 opts.get('date'), opts.get('currentdate'),
2505 2506 opts.get('currentuser'), opts.get('rev'))):
2506 2507 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2507 2508 return _stopgraft(ui, repo, graftstate)
2508 2509 elif opts.get('abort'):
2509 2510 if opts.get('continue'):
2510 2511 raise error.Abort(_("cannot use '--continue' and "
2511 2512 "'--abort' together"))
2512 2513 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2513 2514 opts.get('date'), opts.get('currentdate'),
2514 2515 opts.get('currentuser'), opts.get('rev'))):
2515 2516 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2516 2517
2517 2518 return cmdutil.abortgraft(ui, repo, graftstate)
2518 2519 elif opts.get('continue'):
2519 2520 cont = True
2520 2521 if revs:
2521 2522 raise error.Abort(_("can't specify --continue and revisions"))
2522 2523 # read in unfinished revisions
2523 2524 if graftstate.exists():
2524 2525 statedata = cmdutil.readgraftstate(repo, graftstate)
2525 2526 if statedata.get('date'):
2526 2527 opts['date'] = statedata['date']
2527 2528 if statedata.get('user'):
2528 2529 opts['user'] = statedata['user']
2529 2530 if statedata.get('log'):
2530 2531 opts['log'] = True
2531 2532 if statedata.get('no_commit'):
2532 2533 opts['no_commit'] = statedata.get('no_commit')
2533 2534 nodes = statedata['nodes']
2534 2535 revs = [repo[node].rev() for node in nodes]
2535 2536 else:
2536 2537 cmdutil.wrongtooltocontinue(repo, _('graft'))
2537 2538 else:
2538 2539 if not revs:
2539 2540 raise error.Abort(_('no revisions specified'))
2540 2541 cmdutil.checkunfinished(repo)
2541 2542 cmdutil.bailifchanged(repo)
2542 2543 revs = scmutil.revrange(repo, revs)
2543 2544
2544 2545 skipped = set()
2545 2546 if basectx is None:
2546 2547 # check for merges
2547 2548 for rev in repo.revs('%ld and merge()', revs):
2548 2549 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2549 2550 skipped.add(rev)
2550 2551 revs = [r for r in revs if r not in skipped]
2551 2552 if not revs:
2552 2553 return -1
2553 2554 if basectx is not None and len(revs) != 1:
2554 2555 raise error.Abort(_('only one revision allowed with --base '))
2555 2556
2556 2557 # Don't check in the --continue case, in effect retaining --force across
2557 2558 # --continues. That's because without --force, any revisions we decided to
2558 2559 # skip would have been filtered out here, so they wouldn't have made their
2559 2560 # way to the graftstate. With --force, any revisions we would have otherwise
2560 2561 # skipped would not have been filtered out, and if they hadn't been applied
2561 2562 # already, they'd have been in the graftstate.
2562 2563 if not (cont or opts.get('force')) and basectx is None:
2563 2564 # check for ancestors of dest branch
2564 2565 crev = repo['.'].rev()
2565 2566 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2566 2567 # XXX make this lazy in the future
2567 2568 # don't mutate while iterating, create a copy
2568 2569 for rev in list(revs):
2569 2570 if rev in ancestors:
2570 2571 ui.warn(_('skipping ancestor revision %d:%s\n') %
2571 2572 (rev, repo[rev]))
2572 2573 # XXX remove on list is slow
2573 2574 revs.remove(rev)
2574 2575 if not revs:
2575 2576 return -1
2576 2577
2577 2578 # analyze revs for earlier grafts
2578 2579 ids = {}
2579 2580 for ctx in repo.set("%ld", revs):
2580 2581 ids[ctx.hex()] = ctx.rev()
2581 2582 n = ctx.extra().get('source')
2582 2583 if n:
2583 2584 ids[n] = ctx.rev()
2584 2585
2585 2586 # check ancestors for earlier grafts
2586 2587 ui.debug('scanning for duplicate grafts\n')
2587 2588
2588 2589 # The only changesets we can be sure doesn't contain grafts of any
2589 2590 # revs, are the ones that are common ancestors of *all* revs:
2590 2591 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2591 2592 ctx = repo[rev]
2592 2593 n = ctx.extra().get('source')
2593 2594 if n in ids:
2594 2595 try:
2595 2596 r = repo[n].rev()
2596 2597 except error.RepoLookupError:
2597 2598 r = None
2598 2599 if r in revs:
2599 2600 ui.warn(_('skipping revision %d:%s '
2600 2601 '(already grafted to %d:%s)\n')
2601 2602 % (r, repo[r], rev, ctx))
2602 2603 revs.remove(r)
2603 2604 elif ids[n] in revs:
2604 2605 if r is None:
2605 2606 ui.warn(_('skipping already grafted revision %d:%s '
2606 2607 '(%d:%s also has unknown origin %s)\n')
2607 2608 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2608 2609 else:
2609 2610 ui.warn(_('skipping already grafted revision %d:%s '
2610 2611 '(%d:%s also has origin %d:%s)\n')
2611 2612 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2612 2613 revs.remove(ids[n])
2613 2614 elif ctx.hex() in ids:
2614 2615 r = ids[ctx.hex()]
2615 2616 if r in revs:
2616 2617 ui.warn(_('skipping already grafted revision %d:%s '
2617 2618 '(was grafted from %d:%s)\n') %
2618 2619 (r, repo[r], rev, ctx))
2619 2620 revs.remove(r)
2620 2621 if not revs:
2621 2622 return -1
2622 2623
2623 2624 if opts.get('no_commit'):
2624 2625 statedata['no_commit'] = True
2625 2626 for pos, ctx in enumerate(repo.set("%ld", revs)):
2626 2627 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2627 2628 ctx.description().split('\n', 1)[0])
2628 2629 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2629 2630 if names:
2630 2631 desc += ' (%s)' % ' '.join(names)
2631 2632 ui.status(_('grafting %s\n') % desc)
2632 2633 if opts.get('dry_run'):
2633 2634 continue
2634 2635
2635 2636 source = ctx.extra().get('source')
2636 2637 extra = {}
2637 2638 if source:
2638 2639 extra['source'] = source
2639 2640 extra['intermediate-source'] = ctx.hex()
2640 2641 else:
2641 2642 extra['source'] = ctx.hex()
2642 2643 user = ctx.user()
2643 2644 if opts.get('user'):
2644 2645 user = opts['user']
2645 2646 statedata['user'] = user
2646 2647 date = ctx.date()
2647 2648 if opts.get('date'):
2648 2649 date = opts['date']
2649 2650 statedata['date'] = date
2650 2651 message = ctx.description()
2651 2652 if opts.get('log'):
2652 2653 message += '\n(grafted from %s)' % ctx.hex()
2653 2654 statedata['log'] = True
2654 2655
2655 2656 # we don't merge the first commit when continuing
2656 2657 if not cont:
2657 2658 # perform the graft merge with p1(rev) as 'ancestor'
2658 2659 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2659 2660 base = ctx.p1() if basectx is None else basectx
2660 2661 with ui.configoverride(overrides, 'graft'):
2661 2662 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2662 2663 # report any conflicts
2663 2664 if stats.unresolvedcount > 0:
2664 2665 # write out state for --continue
2665 2666 nodes = [repo[rev].hex() for rev in revs[pos:]]
2666 2667 statedata['nodes'] = nodes
2667 2668 stateversion = 1
2668 2669 graftstate.save(stateversion, statedata)
2669 2670 hint = _("use 'hg resolve' and 'hg graft --continue'")
2670 2671 raise error.Abort(
2671 2672 _("unresolved conflicts, can't continue"),
2672 2673 hint=hint)
2673 2674 else:
2674 2675 cont = False
2675 2676
2676 2677 # commit if --no-commit is false
2677 2678 if not opts.get('no_commit'):
2678 2679 node = repo.commit(text=message, user=user, date=date, extra=extra,
2679 2680 editor=editor)
2680 2681 if node is None:
2681 2682 ui.warn(
2682 2683 _('note: graft of %d:%s created no changes to commit\n') %
2683 2684 (ctx.rev(), ctx))
2684 2685 # checking that newnodes exist because old state files won't have it
2685 2686 elif statedata.get('newnodes') is not None:
2686 2687 statedata['newnodes'].append(node)
2687 2688
2688 2689 # remove state when we complete successfully
2689 2690 if not opts.get('dry_run'):
2690 2691 graftstate.delete()
2691 2692
2692 2693 return 0
2693 2694
2694 2695 def _stopgraft(ui, repo, graftstate):
2695 2696 """stop the interrupted graft"""
2696 2697 if not graftstate.exists():
2697 2698 raise error.Abort(_("no interrupted graft found"))
2698 2699 pctx = repo['.']
2699 2700 hg.updaterepo(repo, pctx.node(), overwrite=True)
2700 2701 graftstate.delete()
2701 2702 ui.status(_("stopped the interrupted graft\n"))
2702 2703 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2703 2704 return 0
2704 2705
2705 2706 statemod.addunfinished(
2706 2707 'graft', fname='graftstate', clearable=True, stopflag=True,
2707 2708 continueflag=True, abortfunc=cmdutil.hgabortgraft,
2708 2709 cmdhint=_("use 'hg graft --continue' or 'hg graft --stop' to stop")
2709 2710 )
2710 2711
2711 2712 @command('grep',
2712 2713 [('0', 'print0', None, _('end fields with NUL')),
2713 2714 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2714 2715 ('', 'diff', None, _('print all revisions when the term was introduced '
2715 2716 'or removed')),
2716 2717 ('a', 'text', None, _('treat all files as text')),
2717 2718 ('f', 'follow', None,
2718 2719 _('follow changeset history,'
2719 2720 ' or file history across copies and renames')),
2720 2721 ('i', 'ignore-case', None, _('ignore case when matching')),
2721 2722 ('l', 'files-with-matches', None,
2722 2723 _('print only filenames and revisions that match')),
2723 2724 ('n', 'line-number', None, _('print matching line numbers')),
2724 2725 ('r', 'rev', [],
2725 2726 _('only search files changed within revision range'), _('REV')),
2726 2727 ('', 'all-files', None,
2727 2728 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2728 2729 ('u', 'user', None, _('list the author (long with -v)')),
2729 2730 ('d', 'date', None, _('list the date (short with -q)')),
2730 2731 ] + formatteropts + walkopts,
2731 2732 _('[OPTION]... PATTERN [FILE]...'),
2732 2733 helpcategory=command.CATEGORY_FILE_CONTENTS,
2733 2734 inferrepo=True,
2734 2735 intents={INTENT_READONLY})
2735 2736 def grep(ui, repo, pattern, *pats, **opts):
2736 2737 """search revision history for a pattern in specified files
2737 2738
2738 2739 Search revision history for a regular expression in the specified
2739 2740 files or the entire project.
2740 2741
2741 2742 By default, grep prints the most recent revision number for each
2742 2743 file in which it finds a match. To get it to print every revision
2743 2744 that contains a change in match status ("-" for a match that becomes
2744 2745 a non-match, or "+" for a non-match that becomes a match), use the
2745 2746 --diff flag.
2746 2747
2747 2748 PATTERN can be any Python (roughly Perl-compatible) regular
2748 2749 expression.
2749 2750
2750 2751 If no FILEs are specified (and -f/--follow isn't set), all files in
2751 2752 the repository are searched, including those that don't exist in the
2752 2753 current branch or have been deleted in a prior changeset.
2753 2754
2754 2755 .. container:: verbose
2755 2756
2756 2757 Template:
2757 2758
2758 2759 The following keywords are supported in addition to the common template
2759 2760 keywords and functions. See also :hg:`help templates`.
2760 2761
2761 2762 :change: String. Character denoting insertion ``+`` or removal ``-``.
2762 2763 Available if ``--diff`` is specified.
2763 2764 :lineno: Integer. Line number of the match.
2764 2765 :path: String. Repository-absolute path of the file.
2765 2766 :texts: List of text chunks.
2766 2767
2767 2768 And each entry of ``{texts}`` provides the following sub-keywords.
2768 2769
2769 2770 :matched: Boolean. True if the chunk matches the specified pattern.
2770 2771 :text: String. Chunk content.
2771 2772
2772 2773 See :hg:`help templates.operators` for the list expansion syntax.
2773 2774
2774 2775 Returns 0 if a match is found, 1 otherwise.
2775 2776 """
2776 2777 opts = pycompat.byteskwargs(opts)
2777 2778 diff = opts.get('all') or opts.get('diff')
2778 2779 all_files = opts.get('all_files')
2779 2780 if diff and opts.get('all_files'):
2780 2781 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2781 2782 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2782 2783 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2783 2784 # experimental config: commands.grep.all-files
2784 2785 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2785 2786 plaingrep = opts.get('all_files') and not opts.get('rev')
2786 2787 if plaingrep:
2787 2788 opts['rev'] = ['wdir()']
2788 2789
2789 2790 reflags = re.M
2790 2791 if opts.get('ignore_case'):
2791 2792 reflags |= re.I
2792 2793 try:
2793 2794 regexp = util.re.compile(pattern, reflags)
2794 2795 except re.error as inst:
2795 2796 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2796 2797 return 1
2797 2798 sep, eol = ':', '\n'
2798 2799 if opts.get('print0'):
2799 2800 sep = eol = '\0'
2800 2801
2801 2802 getfile = util.lrucachefunc(repo.file)
2802 2803
2803 2804 def matchlines(body):
2804 2805 begin = 0
2805 2806 linenum = 0
2806 2807 while begin < len(body):
2807 2808 match = regexp.search(body, begin)
2808 2809 if not match:
2809 2810 break
2810 2811 mstart, mend = match.span()
2811 2812 linenum += body.count('\n', begin, mstart) + 1
2812 2813 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2813 2814 begin = body.find('\n', mend) + 1 or len(body) + 1
2814 2815 lend = begin - 1
2815 2816 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2816 2817
2817 2818 class linestate(object):
2818 2819 def __init__(self, line, linenum, colstart, colend):
2819 2820 self.line = line
2820 2821 self.linenum = linenum
2821 2822 self.colstart = colstart
2822 2823 self.colend = colend
2823 2824
2824 2825 def __hash__(self):
2825 2826 return hash((self.linenum, self.line))
2826 2827
2827 2828 def __eq__(self, other):
2828 2829 return self.line == other.line
2829 2830
2830 2831 def findpos(self):
2831 2832 """Iterate all (start, end) indices of matches"""
2832 2833 yield self.colstart, self.colend
2833 2834 p = self.colend
2834 2835 while p < len(self.line):
2835 2836 m = regexp.search(self.line, p)
2836 2837 if not m:
2837 2838 break
2838 2839 yield m.span()
2839 2840 p = m.end()
2840 2841
2841 2842 matches = {}
2842 2843 copies = {}
2843 2844 def grepbody(fn, rev, body):
2844 2845 matches[rev].setdefault(fn, [])
2845 2846 m = matches[rev][fn]
2846 2847 for lnum, cstart, cend, line in matchlines(body):
2847 2848 s = linestate(line, lnum, cstart, cend)
2848 2849 m.append(s)
2849 2850
2850 2851 def difflinestates(a, b):
2851 2852 sm = difflib.SequenceMatcher(None, a, b)
2852 2853 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2853 2854 if tag == r'insert':
2854 2855 for i in pycompat.xrange(blo, bhi):
2855 2856 yield ('+', b[i])
2856 2857 elif tag == r'delete':
2857 2858 for i in pycompat.xrange(alo, ahi):
2858 2859 yield ('-', a[i])
2859 2860 elif tag == r'replace':
2860 2861 for i in pycompat.xrange(alo, ahi):
2861 2862 yield ('-', a[i])
2862 2863 for i in pycompat.xrange(blo, bhi):
2863 2864 yield ('+', b[i])
2864 2865
2865 2866 uipathfn = scmutil.getuipathfn(repo)
2866 2867 def display(fm, fn, ctx, pstates, states):
2867 2868 rev = scmutil.intrev(ctx)
2868 2869 if fm.isplain():
2869 2870 formatuser = ui.shortuser
2870 2871 else:
2871 2872 formatuser = pycompat.bytestr
2872 2873 if ui.quiet:
2873 2874 datefmt = '%Y-%m-%d'
2874 2875 else:
2875 2876 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2876 2877 found = False
2877 2878 @util.cachefunc
2878 2879 def binary():
2879 2880 flog = getfile(fn)
2880 2881 try:
2881 2882 return stringutil.binary(flog.read(ctx.filenode(fn)))
2882 2883 except error.WdirUnsupported:
2883 2884 return ctx[fn].isbinary()
2884 2885
2885 2886 fieldnamemap = {'linenumber': 'lineno'}
2886 2887 if diff:
2887 2888 iter = difflinestates(pstates, states)
2888 2889 else:
2889 2890 iter = [('', l) for l in states]
2890 2891 for change, l in iter:
2891 2892 fm.startitem()
2892 2893 fm.context(ctx=ctx)
2893 2894 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2894 2895 fm.plain(uipathfn(fn), label='grep.filename')
2895 2896
2896 2897 cols = [
2897 2898 ('rev', '%d', rev, not plaingrep, ''),
2898 2899 ('linenumber', '%d', l.linenum, opts.get('line_number'), ''),
2899 2900 ]
2900 2901 if diff:
2901 2902 cols.append(
2902 2903 ('change', '%s', change, True,
2903 2904 'grep.inserted ' if change == '+' else 'grep.deleted ')
2904 2905 )
2905 2906 cols.extend([
2906 2907 ('user', '%s', formatuser(ctx.user()), opts.get('user'), ''),
2907 2908 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2908 2909 opts.get('date'), ''),
2909 2910 ])
2910 2911 for name, fmt, data, cond, extra_label in cols:
2911 2912 if cond:
2912 2913 fm.plain(sep, label='grep.sep')
2913 2914 field = fieldnamemap.get(name, name)
2914 2915 label = extra_label + ('grep.%s' % name)
2915 2916 fm.condwrite(cond, field, fmt, data, label=label)
2916 2917 if not opts.get('files_with_matches'):
2917 2918 fm.plain(sep, label='grep.sep')
2918 2919 if not opts.get('text') and binary():
2919 2920 fm.plain(_(" Binary file matches"))
2920 2921 else:
2921 2922 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2922 2923 fm.plain(eol)
2923 2924 found = True
2924 2925 if opts.get('files_with_matches'):
2925 2926 break
2926 2927 return found
2927 2928
2928 2929 def displaymatches(fm, l):
2929 2930 p = 0
2930 2931 for s, e in l.findpos():
2931 2932 if p < s:
2932 2933 fm.startitem()
2933 2934 fm.write('text', '%s', l.line[p:s])
2934 2935 fm.data(matched=False)
2935 2936 fm.startitem()
2936 2937 fm.write('text', '%s', l.line[s:e], label='grep.match')
2937 2938 fm.data(matched=True)
2938 2939 p = e
2939 2940 if p < len(l.line):
2940 2941 fm.startitem()
2941 2942 fm.write('text', '%s', l.line[p:])
2942 2943 fm.data(matched=False)
2943 2944 fm.end()
2944 2945
2945 2946 skip = set()
2946 2947 revfiles = {}
2947 2948 match = scmutil.match(repo[None], pats, opts)
2948 2949 found = False
2949 2950 follow = opts.get('follow')
2950 2951
2951 2952 getrenamed = scmutil.getrenamedfn(repo)
2952 2953 def prep(ctx, fns):
2953 2954 rev = ctx.rev()
2954 2955 pctx = ctx.p1()
2955 2956 parent = pctx.rev()
2956 2957 matches.setdefault(rev, {})
2957 2958 matches.setdefault(parent, {})
2958 2959 files = revfiles.setdefault(rev, [])
2959 2960 for fn in fns:
2960 2961 flog = getfile(fn)
2961 2962 try:
2962 2963 fnode = ctx.filenode(fn)
2963 2964 except error.LookupError:
2964 2965 continue
2965 2966
2966 2967 copy = None
2967 2968 if follow:
2968 2969 copy = getrenamed(fn, rev)
2969 2970 if copy:
2970 2971 copies.setdefault(rev, {})[fn] = copy
2971 2972 if fn in skip:
2972 2973 skip.add(copy)
2973 2974 if fn in skip:
2974 2975 continue
2975 2976 files.append(fn)
2976 2977
2977 2978 if fn not in matches[rev]:
2978 2979 try:
2979 2980 content = flog.read(fnode)
2980 2981 except error.WdirUnsupported:
2981 2982 content = ctx[fn].data()
2982 2983 grepbody(fn, rev, content)
2983 2984
2984 2985 pfn = copy or fn
2985 2986 if pfn not in matches[parent]:
2986 2987 try:
2987 2988 fnode = pctx.filenode(pfn)
2988 2989 grepbody(pfn, parent, flog.read(fnode))
2989 2990 except error.LookupError:
2990 2991 pass
2991 2992
2992 2993 ui.pager('grep')
2993 2994 fm = ui.formatter('grep', opts)
2994 2995 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2995 2996 rev = ctx.rev()
2996 2997 parent = ctx.p1().rev()
2997 2998 for fn in sorted(revfiles.get(rev, [])):
2998 2999 states = matches[rev][fn]
2999 3000 copy = copies.get(rev, {}).get(fn)
3000 3001 if fn in skip:
3001 3002 if copy:
3002 3003 skip.add(copy)
3003 3004 continue
3004 3005 pstates = matches.get(parent, {}).get(copy or fn, [])
3005 3006 if pstates or states:
3006 3007 r = display(fm, fn, ctx, pstates, states)
3007 3008 found = found or r
3008 3009 if r and not diff and not all_files:
3009 3010 skip.add(fn)
3010 3011 if copy:
3011 3012 skip.add(copy)
3012 3013 del revfiles[rev]
3013 3014 # We will keep the matches dict for the duration of the window
3014 3015 # clear the matches dict once the window is over
3015 3016 if not revfiles:
3016 3017 matches.clear()
3017 3018 fm.end()
3018 3019
3019 3020 return not found
3020 3021
3021 3022 @command('heads',
3022 3023 [('r', 'rev', '',
3023 3024 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3024 3025 ('t', 'topo', False, _('show topological heads only')),
3025 3026 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3026 3027 ('c', 'closed', False, _('show normal and closed branch heads')),
3027 3028 ] + templateopts,
3028 3029 _('[-ct] [-r STARTREV] [REV]...'),
3029 3030 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3030 3031 intents={INTENT_READONLY})
3031 3032 def heads(ui, repo, *branchrevs, **opts):
3032 3033 """show branch heads
3033 3034
3034 3035 With no arguments, show all open branch heads in the repository.
3035 3036 Branch heads are changesets that have no descendants on the
3036 3037 same branch. They are where development generally takes place and
3037 3038 are the usual targets for update and merge operations.
3038 3039
3039 3040 If one or more REVs are given, only open branch heads on the
3040 3041 branches associated with the specified changesets are shown. This
3041 3042 means that you can use :hg:`heads .` to see the heads on the
3042 3043 currently checked-out branch.
3043 3044
3044 3045 If -c/--closed is specified, also show branch heads marked closed
3045 3046 (see :hg:`commit --close-branch`).
3046 3047
3047 3048 If STARTREV is specified, only those heads that are descendants of
3048 3049 STARTREV will be displayed.
3049 3050
3050 3051 If -t/--topo is specified, named branch mechanics will be ignored and only
3051 3052 topological heads (changesets with no children) will be shown.
3052 3053
3053 3054 Returns 0 if matching heads are found, 1 if not.
3054 3055 """
3055 3056
3056 3057 opts = pycompat.byteskwargs(opts)
3057 3058 start = None
3058 3059 rev = opts.get('rev')
3059 3060 if rev:
3060 3061 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3061 3062 start = scmutil.revsingle(repo, rev, None).node()
3062 3063
3063 3064 if opts.get('topo'):
3064 3065 heads = [repo[h] for h in repo.heads(start)]
3065 3066 else:
3066 3067 heads = []
3067 3068 for branch in repo.branchmap():
3068 3069 heads += repo.branchheads(branch, start, opts.get('closed'))
3069 3070 heads = [repo[h] for h in heads]
3070 3071
3071 3072 if branchrevs:
3072 3073 branches = set(repo[r].branch()
3073 3074 for r in scmutil.revrange(repo, branchrevs))
3074 3075 heads = [h for h in heads if h.branch() in branches]
3075 3076
3076 3077 if opts.get('active') and branchrevs:
3077 3078 dagheads = repo.heads(start)
3078 3079 heads = [h for h in heads if h.node() in dagheads]
3079 3080
3080 3081 if branchrevs:
3081 3082 haveheads = set(h.branch() for h in heads)
3082 3083 if branches - haveheads:
3083 3084 headless = ', '.join(b for b in branches - haveheads)
3084 3085 msg = _('no open branch heads found on branches %s')
3085 3086 if opts.get('rev'):
3086 3087 msg += _(' (started at %s)') % opts['rev']
3087 3088 ui.warn((msg + '\n') % headless)
3088 3089
3089 3090 if not heads:
3090 3091 return 1
3091 3092
3092 3093 ui.pager('heads')
3093 3094 heads = sorted(heads, key=lambda x: -x.rev())
3094 3095 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3095 3096 for ctx in heads:
3096 3097 displayer.show(ctx)
3097 3098 displayer.close()
3098 3099
3099 3100 @command('help',
3100 3101 [('e', 'extension', None, _('show only help for extensions')),
3101 3102 ('c', 'command', None, _('show only help for commands')),
3102 3103 ('k', 'keyword', None, _('show topics matching keyword')),
3103 3104 ('s', 'system', [],
3104 3105 _('show help for specific platform(s)'), _('PLATFORM')),
3105 3106 ],
3106 3107 _('[-eck] [-s PLATFORM] [TOPIC]'),
3107 3108 helpcategory=command.CATEGORY_HELP,
3108 3109 norepo=True,
3109 3110 intents={INTENT_READONLY})
3110 3111 def help_(ui, name=None, **opts):
3111 3112 """show help for a given topic or a help overview
3112 3113
3113 3114 With no arguments, print a list of commands with short help messages.
3114 3115
3115 3116 Given a topic, extension, or command name, print help for that
3116 3117 topic.
3117 3118
3118 3119 Returns 0 if successful.
3119 3120 """
3120 3121
3121 3122 keep = opts.get(r'system') or []
3122 3123 if len(keep) == 0:
3123 3124 if pycompat.sysplatform.startswith('win'):
3124 3125 keep.append('windows')
3125 3126 elif pycompat.sysplatform == 'OpenVMS':
3126 3127 keep.append('vms')
3127 3128 elif pycompat.sysplatform == 'plan9':
3128 3129 keep.append('plan9')
3129 3130 else:
3130 3131 keep.append('unix')
3131 3132 keep.append(pycompat.sysplatform.lower())
3132 3133 if ui.verbose:
3133 3134 keep.append('verbose')
3134 3135
3135 3136 commands = sys.modules[__name__]
3136 3137 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3137 3138 ui.pager('help')
3138 3139 ui.write(formatted)
3139 3140
3140 3141
3141 3142 @command('identify|id',
3142 3143 [('r', 'rev', '',
3143 3144 _('identify the specified revision'), _('REV')),
3144 3145 ('n', 'num', None, _('show local revision number')),
3145 3146 ('i', 'id', None, _('show global revision id')),
3146 3147 ('b', 'branch', None, _('show branch')),
3147 3148 ('t', 'tags', None, _('show tags')),
3148 3149 ('B', 'bookmarks', None, _('show bookmarks')),
3149 3150 ] + remoteopts + formatteropts,
3150 3151 _('[-nibtB] [-r REV] [SOURCE]'),
3151 3152 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3152 3153 optionalrepo=True,
3153 3154 intents={INTENT_READONLY})
3154 3155 def identify(ui, repo, source=None, rev=None,
3155 3156 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3156 3157 """identify the working directory or specified revision
3157 3158
3158 3159 Print a summary identifying the repository state at REV using one or
3159 3160 two parent hash identifiers, followed by a "+" if the working
3160 3161 directory has uncommitted changes, the branch name (if not default),
3161 3162 a list of tags, and a list of bookmarks.
3162 3163
3163 3164 When REV is not given, print a summary of the current state of the
3164 3165 repository including the working directory. Specify -r. to get information
3165 3166 of the working directory parent without scanning uncommitted changes.
3166 3167
3167 3168 Specifying a path to a repository root or Mercurial bundle will
3168 3169 cause lookup to operate on that repository/bundle.
3169 3170
3170 3171 .. container:: verbose
3171 3172
3172 3173 Template:
3173 3174
3174 3175 The following keywords are supported in addition to the common template
3175 3176 keywords and functions. See also :hg:`help templates`.
3176 3177
3177 3178 :dirty: String. Character ``+`` denoting if the working directory has
3178 3179 uncommitted changes.
3179 3180 :id: String. One or two nodes, optionally followed by ``+``.
3180 3181 :parents: List of strings. Parent nodes of the changeset.
3181 3182
3182 3183 Examples:
3183 3184
3184 3185 - generate a build identifier for the working directory::
3185 3186
3186 3187 hg id --id > build-id.dat
3187 3188
3188 3189 - find the revision corresponding to a tag::
3189 3190
3190 3191 hg id -n -r 1.3
3191 3192
3192 3193 - check the most recent revision of a remote repository::
3193 3194
3194 3195 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3195 3196
3196 3197 See :hg:`log` for generating more information about specific revisions,
3197 3198 including full hash identifiers.
3198 3199
3199 3200 Returns 0 if successful.
3200 3201 """
3201 3202
3202 3203 opts = pycompat.byteskwargs(opts)
3203 3204 if not repo and not source:
3204 3205 raise error.Abort(_("there is no Mercurial repository here "
3205 3206 "(.hg not found)"))
3206 3207
3207 3208 default = not (num or id or branch or tags or bookmarks)
3208 3209 output = []
3209 3210 revs = []
3210 3211
3211 3212 if source:
3212 3213 source, branches = hg.parseurl(ui.expandpath(source))
3213 3214 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3214 3215 repo = peer.local()
3215 3216 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3216 3217
3217 3218 fm = ui.formatter('identify', opts)
3218 3219 fm.startitem()
3219 3220
3220 3221 if not repo:
3221 3222 if num or branch or tags:
3222 3223 raise error.Abort(
3223 3224 _("can't query remote revision number, branch, or tags"))
3224 3225 if not rev and revs:
3225 3226 rev = revs[0]
3226 3227 if not rev:
3227 3228 rev = "tip"
3228 3229
3229 3230 remoterev = peer.lookup(rev)
3230 3231 hexrev = fm.hexfunc(remoterev)
3231 3232 if default or id:
3232 3233 output = [hexrev]
3233 3234 fm.data(id=hexrev)
3234 3235
3235 3236 @util.cachefunc
3236 3237 def getbms():
3237 3238 bms = []
3238 3239
3239 3240 if 'bookmarks' in peer.listkeys('namespaces'):
3240 3241 hexremoterev = hex(remoterev)
3241 3242 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3242 3243 if bmr == hexremoterev]
3243 3244
3244 3245 return sorted(bms)
3245 3246
3246 3247 if fm.isplain():
3247 3248 if bookmarks:
3248 3249 output.extend(getbms())
3249 3250 elif default and not ui.quiet:
3250 3251 # multiple bookmarks for a single parent separated by '/'
3251 3252 bm = '/'.join(getbms())
3252 3253 if bm:
3253 3254 output.append(bm)
3254 3255 else:
3255 3256 fm.data(node=hex(remoterev))
3256 3257 if bookmarks or 'bookmarks' in fm.datahint():
3257 3258 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3258 3259 else:
3259 3260 if rev:
3260 3261 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3261 3262 ctx = scmutil.revsingle(repo, rev, None)
3262 3263
3263 3264 if ctx.rev() is None:
3264 3265 ctx = repo[None]
3265 3266 parents = ctx.parents()
3266 3267 taglist = []
3267 3268 for p in parents:
3268 3269 taglist.extend(p.tags())
3269 3270
3270 3271 dirty = ""
3271 3272 if ctx.dirty(missing=True, merge=False, branch=False):
3272 3273 dirty = '+'
3273 3274 fm.data(dirty=dirty)
3274 3275
3275 3276 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3276 3277 if default or id:
3277 3278 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3278 3279 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3279 3280
3280 3281 if num:
3281 3282 numoutput = ["%d" % p.rev() for p in parents]
3282 3283 output.append("%s%s" % ('+'.join(numoutput), dirty))
3283 3284
3284 3285 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3285 3286 for p in parents], name='node'))
3286 3287 else:
3287 3288 hexoutput = fm.hexfunc(ctx.node())
3288 3289 if default or id:
3289 3290 output = [hexoutput]
3290 3291 fm.data(id=hexoutput)
3291 3292
3292 3293 if num:
3293 3294 output.append(pycompat.bytestr(ctx.rev()))
3294 3295 taglist = ctx.tags()
3295 3296
3296 3297 if default and not ui.quiet:
3297 3298 b = ctx.branch()
3298 3299 if b != 'default':
3299 3300 output.append("(%s)" % b)
3300 3301
3301 3302 # multiple tags for a single parent separated by '/'
3302 3303 t = '/'.join(taglist)
3303 3304 if t:
3304 3305 output.append(t)
3305 3306
3306 3307 # multiple bookmarks for a single parent separated by '/'
3307 3308 bm = '/'.join(ctx.bookmarks())
3308 3309 if bm:
3309 3310 output.append(bm)
3310 3311 else:
3311 3312 if branch:
3312 3313 output.append(ctx.branch())
3313 3314
3314 3315 if tags:
3315 3316 output.extend(taglist)
3316 3317
3317 3318 if bookmarks:
3318 3319 output.extend(ctx.bookmarks())
3319 3320
3320 3321 fm.data(node=ctx.hex())
3321 3322 fm.data(branch=ctx.branch())
3322 3323 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3323 3324 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3324 3325 fm.context(ctx=ctx)
3325 3326
3326 3327 fm.plain("%s\n" % ' '.join(output))
3327 3328 fm.end()
3328 3329
3329 3330 @command('import|patch',
3330 3331 [('p', 'strip', 1,
3331 3332 _('directory strip option for patch. This has the same '
3332 3333 'meaning as the corresponding patch option'), _('NUM')),
3333 3334 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3334 3335 ('e', 'edit', False, _('invoke editor on commit messages')),
3335 3336 ('f', 'force', None,
3336 3337 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3337 3338 ('', 'no-commit', None,
3338 3339 _("don't commit, just update the working directory")),
3339 3340 ('', 'bypass', None,
3340 3341 _("apply patch without touching the working directory")),
3341 3342 ('', 'partial', None,
3342 3343 _('commit even if some hunks fail')),
3343 3344 ('', 'exact', None,
3344 3345 _('abort if patch would apply lossily')),
3345 3346 ('', 'prefix', '',
3346 3347 _('apply patch to subdirectory'), _('DIR')),
3347 3348 ('', 'import-branch', None,
3348 3349 _('use any branch information in patch (implied by --exact)'))] +
3349 3350 commitopts + commitopts2 + similarityopts,
3350 3351 _('[OPTION]... PATCH...'),
3351 3352 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3352 3353 def import_(ui, repo, patch1=None, *patches, **opts):
3353 3354 """import an ordered set of patches
3354 3355
3355 3356 Import a list of patches and commit them individually (unless
3356 3357 --no-commit is specified).
3357 3358
3358 3359 To read a patch from standard input (stdin), use "-" as the patch
3359 3360 name. If a URL is specified, the patch will be downloaded from
3360 3361 there.
3361 3362
3362 3363 Import first applies changes to the working directory (unless
3363 3364 --bypass is specified), import will abort if there are outstanding
3364 3365 changes.
3365 3366
3366 3367 Use --bypass to apply and commit patches directly to the
3367 3368 repository, without affecting the working directory. Without
3368 3369 --exact, patches will be applied on top of the working directory
3369 3370 parent revision.
3370 3371
3371 3372 You can import a patch straight from a mail message. Even patches
3372 3373 as attachments work (to use the body part, it must have type
3373 3374 text/plain or text/x-patch). From and Subject headers of email
3374 3375 message are used as default committer and commit message. All
3375 3376 text/plain body parts before first diff are added to the commit
3376 3377 message.
3377 3378
3378 3379 If the imported patch was generated by :hg:`export`, user and
3379 3380 description from patch override values from message headers and
3380 3381 body. Values given on command line with -m/--message and -u/--user
3381 3382 override these.
3382 3383
3383 3384 If --exact is specified, import will set the working directory to
3384 3385 the parent of each patch before applying it, and will abort if the
3385 3386 resulting changeset has a different ID than the one recorded in
3386 3387 the patch. This will guard against various ways that portable
3387 3388 patch formats and mail systems might fail to transfer Mercurial
3388 3389 data or metadata. See :hg:`bundle` for lossless transmission.
3389 3390
3390 3391 Use --partial to ensure a changeset will be created from the patch
3391 3392 even if some hunks fail to apply. Hunks that fail to apply will be
3392 3393 written to a <target-file>.rej file. Conflicts can then be resolved
3393 3394 by hand before :hg:`commit --amend` is run to update the created
3394 3395 changeset. This flag exists to let people import patches that
3395 3396 partially apply without losing the associated metadata (author,
3396 3397 date, description, ...).
3397 3398
3398 3399 .. note::
3399 3400
3400 3401 When no hunks apply cleanly, :hg:`import --partial` will create
3401 3402 an empty changeset, importing only the patch metadata.
3402 3403
3403 3404 With -s/--similarity, hg will attempt to discover renames and
3404 3405 copies in the patch in the same way as :hg:`addremove`.
3405 3406
3406 3407 It is possible to use external patch programs to perform the patch
3407 3408 by setting the ``ui.patch`` configuration option. For the default
3408 3409 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3409 3410 See :hg:`help config` for more information about configuration
3410 3411 files and how to use these options.
3411 3412
3412 3413 See :hg:`help dates` for a list of formats valid for -d/--date.
3413 3414
3414 3415 .. container:: verbose
3415 3416
3416 3417 Examples:
3417 3418
3418 3419 - import a traditional patch from a website and detect renames::
3419 3420
3420 3421 hg import -s 80 http://example.com/bugfix.patch
3421 3422
3422 3423 - import a changeset from an hgweb server::
3423 3424
3424 3425 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3425 3426
3426 3427 - import all the patches in an Unix-style mbox::
3427 3428
3428 3429 hg import incoming-patches.mbox
3429 3430
3430 3431 - import patches from stdin::
3431 3432
3432 3433 hg import -
3433 3434
3434 3435 - attempt to exactly restore an exported changeset (not always
3435 3436 possible)::
3436 3437
3437 3438 hg import --exact proposed-fix.patch
3438 3439
3439 3440 - use an external tool to apply a patch which is too fuzzy for
3440 3441 the default internal tool.
3441 3442
3442 3443 hg import --config ui.patch="patch --merge" fuzzy.patch
3443 3444
3444 3445 - change the default fuzzing from 2 to a less strict 7
3445 3446
3446 3447 hg import --config ui.fuzz=7 fuzz.patch
3447 3448
3448 3449 Returns 0 on success, 1 on partial success (see --partial).
3449 3450 """
3450 3451
3451 3452 opts = pycompat.byteskwargs(opts)
3452 3453 if not patch1:
3453 3454 raise error.Abort(_('need at least one patch to import'))
3454 3455
3455 3456 patches = (patch1,) + patches
3456 3457
3457 3458 date = opts.get('date')
3458 3459 if date:
3459 3460 opts['date'] = dateutil.parsedate(date)
3460 3461
3461 3462 exact = opts.get('exact')
3462 3463 update = not opts.get('bypass')
3463 3464 if not update and opts.get('no_commit'):
3464 3465 raise error.Abort(_('cannot use --no-commit with --bypass'))
3465 3466 try:
3466 3467 sim = float(opts.get('similarity') or 0)
3467 3468 except ValueError:
3468 3469 raise error.Abort(_('similarity must be a number'))
3469 3470 if sim < 0 or sim > 100:
3470 3471 raise error.Abort(_('similarity must be between 0 and 100'))
3471 3472 if sim and not update:
3472 3473 raise error.Abort(_('cannot use --similarity with --bypass'))
3473 3474 if exact:
3474 3475 if opts.get('edit'):
3475 3476 raise error.Abort(_('cannot use --exact with --edit'))
3476 3477 if opts.get('prefix'):
3477 3478 raise error.Abort(_('cannot use --exact with --prefix'))
3478 3479
3479 3480 base = opts["base"]
3480 3481 msgs = []
3481 3482 ret = 0
3482 3483
3483 3484 with repo.wlock():
3484 3485 if update:
3485 3486 cmdutil.checkunfinished(repo)
3486 3487 if (exact or not opts.get('force')):
3487 3488 cmdutil.bailifchanged(repo)
3488 3489
3489 3490 if not opts.get('no_commit'):
3490 3491 lock = repo.lock
3491 3492 tr = lambda: repo.transaction('import')
3492 3493 dsguard = util.nullcontextmanager
3493 3494 else:
3494 3495 lock = util.nullcontextmanager
3495 3496 tr = util.nullcontextmanager
3496 3497 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3497 3498 with lock(), tr(), dsguard():
3498 3499 parents = repo[None].parents()
3499 3500 for patchurl in patches:
3500 3501 if patchurl == '-':
3501 3502 ui.status(_('applying patch from stdin\n'))
3502 3503 patchfile = ui.fin
3503 3504 patchurl = 'stdin' # for error message
3504 3505 else:
3505 3506 patchurl = os.path.join(base, patchurl)
3506 3507 ui.status(_('applying %s\n') % patchurl)
3507 3508 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
3508 3509
3509 3510 haspatch = False
3510 3511 for hunk in patch.split(patchfile):
3511 3512 with patch.extract(ui, hunk) as patchdata:
3512 3513 msg, node, rej = cmdutil.tryimportone(ui, repo,
3513 3514 patchdata,
3514 3515 parents, opts,
3515 3516 msgs, hg.clean)
3516 3517 if msg:
3517 3518 haspatch = True
3518 3519 ui.note(msg + '\n')
3519 3520 if update or exact:
3520 3521 parents = repo[None].parents()
3521 3522 else:
3522 3523 parents = [repo[node]]
3523 3524 if rej:
3524 3525 ui.write_err(_("patch applied partially\n"))
3525 3526 ui.write_err(_("(fix the .rej files and run "
3526 3527 "`hg commit --amend`)\n"))
3527 3528 ret = 1
3528 3529 break
3529 3530
3530 3531 if not haspatch:
3531 3532 raise error.Abort(_('%s: no diffs found') % patchurl)
3532 3533
3533 3534 if msgs:
3534 3535 repo.savecommitmessage('\n* * *\n'.join(msgs))
3535 3536 return ret
3536 3537
3537 3538 @command('incoming|in',
3538 3539 [('f', 'force', None,
3539 3540 _('run even if remote repository is unrelated')),
3540 3541 ('n', 'newest-first', None, _('show newest record first')),
3541 3542 ('', 'bundle', '',
3542 3543 _('file to store the bundles into'), _('FILE')),
3543 3544 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3544 3545 ('B', 'bookmarks', False, _("compare bookmarks")),
3545 3546 ('b', 'branch', [],
3546 3547 _('a specific branch you would like to pull'), _('BRANCH')),
3547 3548 ] + logopts + remoteopts + subrepoopts,
3548 3549 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3549 3550 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3550 3551 def incoming(ui, repo, source="default", **opts):
3551 3552 """show new changesets found in source
3552 3553
3553 3554 Show new changesets found in the specified path/URL or the default
3554 3555 pull location. These are the changesets that would have been pulled
3555 3556 by :hg:`pull` at the time you issued this command.
3556 3557
3557 3558 See pull for valid source format details.
3558 3559
3559 3560 .. container:: verbose
3560 3561
3561 3562 With -B/--bookmarks, the result of bookmark comparison between
3562 3563 local and remote repositories is displayed. With -v/--verbose,
3563 3564 status is also displayed for each bookmark like below::
3564 3565
3565 3566 BM1 01234567890a added
3566 3567 BM2 1234567890ab advanced
3567 3568 BM3 234567890abc diverged
3568 3569 BM4 34567890abcd changed
3569 3570
3570 3571 The action taken locally when pulling depends on the
3571 3572 status of each bookmark:
3572 3573
3573 3574 :``added``: pull will create it
3574 3575 :``advanced``: pull will update it
3575 3576 :``diverged``: pull will create a divergent bookmark
3576 3577 :``changed``: result depends on remote changesets
3577 3578
3578 3579 From the point of view of pulling behavior, bookmark
3579 3580 existing only in the remote repository are treated as ``added``,
3580 3581 even if it is in fact locally deleted.
3581 3582
3582 3583 .. container:: verbose
3583 3584
3584 3585 For remote repository, using --bundle avoids downloading the
3585 3586 changesets twice if the incoming is followed by a pull.
3586 3587
3587 3588 Examples:
3588 3589
3589 3590 - show incoming changes with patches and full description::
3590 3591
3591 3592 hg incoming -vp
3592 3593
3593 3594 - show incoming changes excluding merges, store a bundle::
3594 3595
3595 3596 hg in -vpM --bundle incoming.hg
3596 3597 hg pull incoming.hg
3597 3598
3598 3599 - briefly list changes inside a bundle::
3599 3600
3600 3601 hg in changes.hg -T "{desc|firstline}\\n"
3601 3602
3602 3603 Returns 0 if there are incoming changes, 1 otherwise.
3603 3604 """
3604 3605 opts = pycompat.byteskwargs(opts)
3605 3606 if opts.get('graph'):
3606 3607 logcmdutil.checkunsupportedgraphflags([], opts)
3607 3608 def display(other, chlist, displayer):
3608 3609 revdag = logcmdutil.graphrevs(other, chlist, opts)
3609 3610 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3610 3611 graphmod.asciiedges)
3611 3612
3612 3613 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3613 3614 return 0
3614 3615
3615 3616 if opts.get('bundle') and opts.get('subrepos'):
3616 3617 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3617 3618
3618 3619 if opts.get('bookmarks'):
3619 3620 source, branches = hg.parseurl(ui.expandpath(source),
3620 3621 opts.get('branch'))
3621 3622 other = hg.peer(repo, opts, source)
3622 3623 if 'bookmarks' not in other.listkeys('namespaces'):
3623 3624 ui.warn(_("remote doesn't support bookmarks\n"))
3624 3625 return 0
3625 3626 ui.pager('incoming')
3626 3627 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3627 3628 return bookmarks.incoming(ui, repo, other)
3628 3629
3629 3630 repo._subtoppath = ui.expandpath(source)
3630 3631 try:
3631 3632 return hg.incoming(ui, repo, source, opts)
3632 3633 finally:
3633 3634 del repo._subtoppath
3634 3635
3635 3636
3636 3637 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3637 3638 helpcategory=command.CATEGORY_REPO_CREATION,
3638 3639 helpbasic=True, norepo=True)
3639 3640 def init(ui, dest=".", **opts):
3640 3641 """create a new repository in the given directory
3641 3642
3642 3643 Initialize a new repository in the given directory. If the given
3643 3644 directory does not exist, it will be created.
3644 3645
3645 3646 If no directory is given, the current directory is used.
3646 3647
3647 3648 It is possible to specify an ``ssh://`` URL as the destination.
3648 3649 See :hg:`help urls` for more information.
3649 3650
3650 3651 Returns 0 on success.
3651 3652 """
3652 3653 opts = pycompat.byteskwargs(opts)
3653 3654 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3654 3655
3655 3656 @command('locate',
3656 3657 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3657 3658 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3658 3659 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3659 3660 ] + walkopts,
3660 3661 _('[OPTION]... [PATTERN]...'),
3661 3662 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3662 3663 def locate(ui, repo, *pats, **opts):
3663 3664 """locate files matching specific patterns (DEPRECATED)
3664 3665
3665 3666 Print files under Mercurial control in the working directory whose
3666 3667 names match the given patterns.
3667 3668
3668 3669 By default, this command searches all directories in the working
3669 3670 directory. To search just the current directory and its
3670 3671 subdirectories, use "--include .".
3671 3672
3672 3673 If no patterns are given to match, this command prints the names
3673 3674 of all files under Mercurial control in the working directory.
3674 3675
3675 3676 If you want to feed the output of this command into the "xargs"
3676 3677 command, use the -0 option to both this command and "xargs". This
3677 3678 will avoid the problem of "xargs" treating single filenames that
3678 3679 contain whitespace as multiple filenames.
3679 3680
3680 3681 See :hg:`help files` for a more versatile command.
3681 3682
3682 3683 Returns 0 if a match is found, 1 otherwise.
3683 3684 """
3684 3685 opts = pycompat.byteskwargs(opts)
3685 3686 if opts.get('print0'):
3686 3687 end = '\0'
3687 3688 else:
3688 3689 end = '\n'
3689 3690 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3690 3691
3691 3692 ret = 1
3692 3693 m = scmutil.match(ctx, pats, opts, default='relglob',
3693 3694 badfn=lambda x, y: False)
3694 3695
3695 3696 ui.pager('locate')
3696 3697 if ctx.rev() is None:
3697 3698 # When run on the working copy, "locate" includes removed files, so
3698 3699 # we get the list of files from the dirstate.
3699 3700 filesgen = sorted(repo.dirstate.matches(m))
3700 3701 else:
3701 3702 filesgen = ctx.matches(m)
3702 3703 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3703 3704 for abs in filesgen:
3704 3705 if opts.get('fullpath'):
3705 3706 ui.write(repo.wjoin(abs), end)
3706 3707 else:
3707 3708 ui.write(uipathfn(abs), end)
3708 3709 ret = 0
3709 3710
3710 3711 return ret
3711 3712
3712 3713 @command('log|history',
3713 3714 [('f', 'follow', None,
3714 3715 _('follow changeset history, or file history across copies and renames')),
3715 3716 ('', 'follow-first', None,
3716 3717 _('only follow the first parent of merge changesets (DEPRECATED)')),
3717 3718 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3718 3719 ('C', 'copies', None, _('show copied files')),
3719 3720 ('k', 'keyword', [],
3720 3721 _('do case-insensitive search for a given text'), _('TEXT')),
3721 3722 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3722 3723 ('L', 'line-range', [],
3723 3724 _('follow line range of specified file (EXPERIMENTAL)'),
3724 3725 _('FILE,RANGE')),
3725 3726 ('', 'removed', None, _('include revisions where files were removed')),
3726 3727 ('m', 'only-merges', None,
3727 3728 _('show only merges (DEPRECATED) (use -r "merge()" instead)')),
3728 3729 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3729 3730 ('', 'only-branch', [],
3730 3731 _('show only changesets within the given named branch (DEPRECATED)'),
3731 3732 _('BRANCH')),
3732 3733 ('b', 'branch', [],
3733 3734 _('show changesets within the given named branch'), _('BRANCH')),
3734 3735 ('P', 'prune', [],
3735 3736 _('do not display revision or any of its ancestors'), _('REV')),
3736 3737 ] + logopts + walkopts,
3737 3738 _('[OPTION]... [FILE]'),
3738 3739 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3739 3740 helpbasic=True, inferrepo=True,
3740 3741 intents={INTENT_READONLY})
3741 3742 def log(ui, repo, *pats, **opts):
3742 3743 """show revision history of entire repository or files
3743 3744
3744 3745 Print the revision history of the specified files or the entire
3745 3746 project.
3746 3747
3747 3748 If no revision range is specified, the default is ``tip:0`` unless
3748 3749 --follow is set, in which case the working directory parent is
3749 3750 used as the starting revision.
3750 3751
3751 3752 File history is shown without following rename or copy history of
3752 3753 files. Use -f/--follow with a filename to follow history across
3753 3754 renames and copies. --follow without a filename will only show
3754 3755 ancestors of the starting revision.
3755 3756
3756 3757 By default this command prints revision number and changeset id,
3757 3758 tags, non-trivial parents, user, date and time, and a summary for
3758 3759 each commit. When the -v/--verbose switch is used, the list of
3759 3760 changed files and full commit message are shown.
3760 3761
3761 3762 With --graph the revisions are shown as an ASCII art DAG with the most
3762 3763 recent changeset at the top.
3763 3764 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3764 3765 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3765 3766 changeset from the lines below is a parent of the 'o' merge on the same
3766 3767 line.
3767 3768 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3768 3769 of a '|' indicates one or more revisions in a path are omitted.
3769 3770
3770 3771 .. container:: verbose
3771 3772
3772 3773 Use -L/--line-range FILE,M:N options to follow the history of lines
3773 3774 from M to N in FILE. With -p/--patch only diff hunks affecting
3774 3775 specified line range will be shown. This option requires --follow;
3775 3776 it can be specified multiple times. Currently, this option is not
3776 3777 compatible with --graph. This option is experimental.
3777 3778
3778 3779 .. note::
3779 3780
3780 3781 :hg:`log --patch` may generate unexpected diff output for merge
3781 3782 changesets, as it will only compare the merge changeset against
3782 3783 its first parent. Also, only files different from BOTH parents
3783 3784 will appear in files:.
3784 3785
3785 3786 .. note::
3786 3787
3787 3788 For performance reasons, :hg:`log FILE` may omit duplicate changes
3788 3789 made on branches and will not show removals or mode changes. To
3789 3790 see all such changes, use the --removed switch.
3790 3791
3791 3792 .. container:: verbose
3792 3793
3793 3794 .. note::
3794 3795
3795 3796 The history resulting from -L/--line-range options depends on diff
3796 3797 options; for instance if white-spaces are ignored, respective changes
3797 3798 with only white-spaces in specified line range will not be listed.
3798 3799
3799 3800 .. container:: verbose
3800 3801
3801 3802 Some examples:
3802 3803
3803 3804 - changesets with full descriptions and file lists::
3804 3805
3805 3806 hg log -v
3806 3807
3807 3808 - changesets ancestral to the working directory::
3808 3809
3809 3810 hg log -f
3810 3811
3811 3812 - last 10 commits on the current branch::
3812 3813
3813 3814 hg log -l 10 -b .
3814 3815
3815 3816 - changesets showing all modifications of a file, including removals::
3816 3817
3817 3818 hg log --removed file.c
3818 3819
3819 3820 - all changesets that touch a directory, with diffs, excluding merges::
3820 3821
3821 3822 hg log -Mp lib/
3822 3823
3823 3824 - all revision numbers that match a keyword::
3824 3825
3825 3826 hg log -k bug --template "{rev}\\n"
3826 3827
3827 3828 - the full hash identifier of the working directory parent::
3828 3829
3829 3830 hg log -r . --template "{node}\\n"
3830 3831
3831 3832 - list available log templates::
3832 3833
3833 3834 hg log -T list
3834 3835
3835 3836 - check if a given changeset is included in a tagged release::
3836 3837
3837 3838 hg log -r "a21ccf and ancestor(1.9)"
3838 3839
3839 3840 - find all changesets by some user in a date range::
3840 3841
3841 3842 hg log -k alice -d "may 2008 to jul 2008"
3842 3843
3843 3844 - summary of all changesets after the last tag::
3844 3845
3845 3846 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3846 3847
3847 3848 - changesets touching lines 13 to 23 for file.c::
3848 3849
3849 3850 hg log -L file.c,13:23
3850 3851
3851 3852 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3852 3853 main.c with patch::
3853 3854
3854 3855 hg log -L file.c,13:23 -L main.c,2:6 -p
3855 3856
3856 3857 See :hg:`help dates` for a list of formats valid for -d/--date.
3857 3858
3858 3859 See :hg:`help revisions` for more about specifying and ordering
3859 3860 revisions.
3860 3861
3861 3862 See :hg:`help templates` for more about pre-packaged styles and
3862 3863 specifying custom templates. The default template used by the log
3863 3864 command can be customized via the ``ui.logtemplate`` configuration
3864 3865 setting.
3865 3866
3866 3867 Returns 0 on success.
3867 3868
3868 3869 """
3869 3870 opts = pycompat.byteskwargs(opts)
3870 3871 linerange = opts.get('line_range')
3871 3872
3872 3873 if linerange and not opts.get('follow'):
3873 3874 raise error.Abort(_('--line-range requires --follow'))
3874 3875
3875 3876 if linerange and pats:
3876 3877 # TODO: take pats as patterns with no line-range filter
3877 3878 raise error.Abort(
3878 3879 _('FILE arguments are not compatible with --line-range option')
3879 3880 )
3880 3881
3881 3882 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3882 3883 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3883 3884 if linerange:
3884 3885 # TODO: should follow file history from logcmdutil._initialrevs(),
3885 3886 # then filter the result by logcmdutil._makerevset() and --limit
3886 3887 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3887 3888
3888 3889 getcopies = None
3889 3890 if opts.get('copies'):
3890 3891 endrev = None
3891 3892 if revs:
3892 3893 endrev = revs.max() + 1
3893 3894 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
3894 3895
3895 3896 ui.pager('log')
3896 3897 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3897 3898 buffered=True)
3898 3899 if opts.get('graph'):
3899 3900 displayfn = logcmdutil.displaygraphrevs
3900 3901 else:
3901 3902 displayfn = logcmdutil.displayrevs
3902 3903 displayfn(ui, repo, revs, displayer, getcopies)
3903 3904
3904 3905 @command('manifest',
3905 3906 [('r', 'rev', '', _('revision to display'), _('REV')),
3906 3907 ('', 'all', False, _("list files from all revisions"))]
3907 3908 + formatteropts,
3908 3909 _('[-r REV]'),
3909 3910 helpcategory=command.CATEGORY_MAINTENANCE,
3910 3911 intents={INTENT_READONLY})
3911 3912 def manifest(ui, repo, node=None, rev=None, **opts):
3912 3913 """output the current or given revision of the project manifest
3913 3914
3914 3915 Print a list of version controlled files for the given revision.
3915 3916 If no revision is given, the first parent of the working directory
3916 3917 is used, or the null revision if no revision is checked out.
3917 3918
3918 3919 With -v, print file permissions, symlink and executable bits.
3919 3920 With --debug, print file revision hashes.
3920 3921
3921 3922 If option --all is specified, the list of all files from all revisions
3922 3923 is printed. This includes deleted and renamed files.
3923 3924
3924 3925 Returns 0 on success.
3925 3926 """
3926 3927 opts = pycompat.byteskwargs(opts)
3927 3928 fm = ui.formatter('manifest', opts)
3928 3929
3929 3930 if opts.get('all'):
3930 3931 if rev or node:
3931 3932 raise error.Abort(_("can't specify a revision with --all"))
3932 3933
3933 3934 res = set()
3934 3935 for rev in repo:
3935 3936 ctx = repo[rev]
3936 3937 res |= set(ctx.files())
3937 3938
3938 3939 ui.pager('manifest')
3939 3940 for f in sorted(res):
3940 3941 fm.startitem()
3941 3942 fm.write("path", '%s\n', f)
3942 3943 fm.end()
3943 3944 return
3944 3945
3945 3946 if rev and node:
3946 3947 raise error.Abort(_("please specify just one revision"))
3947 3948
3948 3949 if not node:
3949 3950 node = rev
3950 3951
3951 3952 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3952 3953 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3953 3954 if node:
3954 3955 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3955 3956 ctx = scmutil.revsingle(repo, node)
3956 3957 mf = ctx.manifest()
3957 3958 ui.pager('manifest')
3958 3959 for f in ctx:
3959 3960 fm.startitem()
3960 3961 fm.context(ctx=ctx)
3961 3962 fl = ctx[f].flags()
3962 3963 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3963 3964 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3964 3965 fm.write('path', '%s\n', f)
3965 3966 fm.end()
3966 3967
3967 3968 @command('merge',
3968 3969 [('f', 'force', None,
3969 3970 _('force a merge including outstanding changes (DEPRECATED)')),
3970 3971 ('r', 'rev', '', _('revision to merge'), _('REV')),
3971 3972 ('P', 'preview', None,
3972 3973 _('review revisions to merge (no merge is performed)')),
3973 3974 ('', 'abort', None, _('abort the ongoing merge')),
3974 3975 ] + mergetoolopts,
3975 3976 _('[-P] [[-r] REV]'),
3976 3977 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3977 3978 def merge(ui, repo, node=None, **opts):
3978 3979 """merge another revision into working directory
3979 3980
3980 3981 The current working directory is updated with all changes made in
3981 3982 the requested revision since the last common predecessor revision.
3982 3983
3983 3984 Files that changed between either parent are marked as changed for
3984 3985 the next commit and a commit must be performed before any further
3985 3986 updates to the repository are allowed. The next commit will have
3986 3987 two parents.
3987 3988
3988 3989 ``--tool`` can be used to specify the merge tool used for file
3989 3990 merges. It overrides the HGMERGE environment variable and your
3990 3991 configuration files. See :hg:`help merge-tools` for options.
3991 3992
3992 3993 If no revision is specified, the working directory's parent is a
3993 3994 head revision, and the current branch contains exactly one other
3994 3995 head, the other head is merged with by default. Otherwise, an
3995 3996 explicit revision with which to merge must be provided.
3996 3997
3997 3998 See :hg:`help resolve` for information on handling file conflicts.
3998 3999
3999 4000 To undo an uncommitted merge, use :hg:`merge --abort` which
4000 4001 will check out a clean copy of the original merge parent, losing
4001 4002 all changes.
4002 4003
4003 4004 Returns 0 on success, 1 if there are unresolved files.
4004 4005 """
4005 4006
4006 4007 opts = pycompat.byteskwargs(opts)
4007 4008 abort = opts.get('abort')
4008 4009 if abort and repo.dirstate.p2() == nullid:
4009 4010 cmdutil.wrongtooltocontinue(repo, _('merge'))
4010 4011 if abort:
4011 4012 state = cmdutil.getunfinishedstate(repo)
4012 4013 if state and state._opname != 'merge':
4013 4014 raise error.Abort(_('cannot abort merge with %s in progress') %
4014 4015 (state._opname), hint=state.hint())
4015 4016 if node:
4016 4017 raise error.Abort(_("cannot specify a node with --abort"))
4017 4018 if opts.get('rev'):
4018 4019 raise error.Abort(_("cannot specify both --rev and --abort"))
4019 4020 if opts.get('preview'):
4020 4021 raise error.Abort(_("cannot specify --preview with --abort"))
4021 4022 if opts.get('rev') and node:
4022 4023 raise error.Abort(_("please specify just one revision"))
4023 4024 if not node:
4024 4025 node = opts.get('rev')
4025 4026
4026 4027 if node:
4027 4028 node = scmutil.revsingle(repo, node).node()
4028 4029
4029 4030 if not node and not abort:
4030 4031 node = repo[destutil.destmerge(repo)].node()
4031 4032
4032 4033 if opts.get('preview'):
4033 4034 # find nodes that are ancestors of p2 but not of p1
4034 4035 p1 = repo.lookup('.')
4035 4036 p2 = node
4036 4037 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4037 4038
4038 4039 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4039 4040 for node in nodes:
4040 4041 displayer.show(repo[node])
4041 4042 displayer.close()
4042 4043 return 0
4043 4044
4044 4045 # ui.forcemerge is an internal variable, do not document
4045 4046 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4046 4047 with ui.configoverride(overrides, 'merge'):
4047 4048 force = opts.get('force')
4048 4049 labels = ['working copy', 'merge rev']
4049 4050 return hg.merge(repo, node, force=force, mergeforce=force,
4050 4051 labels=labels, abort=abort)
4051 4052
4052 4053 statemod.addunfinished(
4053 4054 'merge', fname=None, clearable=True, allowcommit=True,
4054 4055 cmdmsg=_('outstanding uncommitted merge'), abortfunc=hg.abortmerge,
4055 4056 statushint=_('To continue: hg commit\n'
4056 4057 'To abort: hg merge --abort'),
4057 4058 cmdhint=_("use 'hg commit' or 'hg merge --abort'")
4058 4059 )
4059 4060
4060 4061 @command('outgoing|out',
4061 4062 [('f', 'force', None, _('run even when the destination is unrelated')),
4062 4063 ('r', 'rev', [],
4063 4064 _('a changeset intended to be included in the destination'), _('REV')),
4064 4065 ('n', 'newest-first', None, _('show newest record first')),
4065 4066 ('B', 'bookmarks', False, _('compare bookmarks')),
4066 4067 ('b', 'branch', [], _('a specific branch you would like to push'),
4067 4068 _('BRANCH')),
4068 4069 ] + logopts + remoteopts + subrepoopts,
4069 4070 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4070 4071 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4071 4072 def outgoing(ui, repo, dest=None, **opts):
4072 4073 """show changesets not found in the destination
4073 4074
4074 4075 Show changesets not found in the specified destination repository
4075 4076 or the default push location. These are the changesets that would
4076 4077 be pushed if a push was requested.
4077 4078
4078 4079 See pull for details of valid destination formats.
4079 4080
4080 4081 .. container:: verbose
4081 4082
4082 4083 With -B/--bookmarks, the result of bookmark comparison between
4083 4084 local and remote repositories is displayed. With -v/--verbose,
4084 4085 status is also displayed for each bookmark like below::
4085 4086
4086 4087 BM1 01234567890a added
4087 4088 BM2 deleted
4088 4089 BM3 234567890abc advanced
4089 4090 BM4 34567890abcd diverged
4090 4091 BM5 4567890abcde changed
4091 4092
4092 4093 The action taken when pushing depends on the
4093 4094 status of each bookmark:
4094 4095
4095 4096 :``added``: push with ``-B`` will create it
4096 4097 :``deleted``: push with ``-B`` will delete it
4097 4098 :``advanced``: push will update it
4098 4099 :``diverged``: push with ``-B`` will update it
4099 4100 :``changed``: push with ``-B`` will update it
4100 4101
4101 4102 From the point of view of pushing behavior, bookmarks
4102 4103 existing only in the remote repository are treated as
4103 4104 ``deleted``, even if it is in fact added remotely.
4104 4105
4105 4106 Returns 0 if there are outgoing changes, 1 otherwise.
4106 4107 """
4107 4108 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4108 4109 # style URLs, so don't overwrite dest.
4109 4110 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4110 4111 if not path:
4111 4112 raise error.Abort(_('default repository not configured!'),
4112 4113 hint=_("see 'hg help config.paths'"))
4113 4114
4114 4115 opts = pycompat.byteskwargs(opts)
4115 4116 if opts.get('graph'):
4116 4117 logcmdutil.checkunsupportedgraphflags([], opts)
4117 4118 o, other = hg._outgoing(ui, repo, dest, opts)
4118 4119 if not o:
4119 4120 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4120 4121 return
4121 4122
4122 4123 revdag = logcmdutil.graphrevs(repo, o, opts)
4123 4124 ui.pager('outgoing')
4124 4125 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4125 4126 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4126 4127 graphmod.asciiedges)
4127 4128 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4128 4129 return 0
4129 4130
4130 4131 if opts.get('bookmarks'):
4131 4132 dest = path.pushloc or path.loc
4132 4133 other = hg.peer(repo, opts, dest)
4133 4134 if 'bookmarks' not in other.listkeys('namespaces'):
4134 4135 ui.warn(_("remote doesn't support bookmarks\n"))
4135 4136 return 0
4136 4137 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4137 4138 ui.pager('outgoing')
4138 4139 return bookmarks.outgoing(ui, repo, other)
4139 4140
4140 4141 repo._subtoppath = path.pushloc or path.loc
4141 4142 try:
4142 4143 return hg.outgoing(ui, repo, dest, opts)
4143 4144 finally:
4144 4145 del repo._subtoppath
4145 4146
4146 4147 @command('parents',
4147 4148 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4148 4149 ] + templateopts,
4149 4150 _('[-r REV] [FILE]'),
4150 4151 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4151 4152 inferrepo=True)
4152 4153 def parents(ui, repo, file_=None, **opts):
4153 4154 """show the parents of the working directory or revision (DEPRECATED)
4154 4155
4155 4156 Print the working directory's parent revisions. If a revision is
4156 4157 given via -r/--rev, the parent of that revision will be printed.
4157 4158 If a file argument is given, the revision in which the file was
4158 4159 last changed (before the working directory revision or the
4159 4160 argument to --rev if given) is printed.
4160 4161
4161 4162 This command is equivalent to::
4162 4163
4163 4164 hg log -r "p1()+p2()" or
4164 4165 hg log -r "p1(REV)+p2(REV)" or
4165 4166 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4166 4167 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4167 4168
4168 4169 See :hg:`summary` and :hg:`help revsets` for related information.
4169 4170
4170 4171 Returns 0 on success.
4171 4172 """
4172 4173
4173 4174 opts = pycompat.byteskwargs(opts)
4174 4175 rev = opts.get('rev')
4175 4176 if rev:
4176 4177 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4177 4178 ctx = scmutil.revsingle(repo, rev, None)
4178 4179
4179 4180 if file_:
4180 4181 m = scmutil.match(ctx, (file_,), opts)
4181 4182 if m.anypats() or len(m.files()) != 1:
4182 4183 raise error.Abort(_('can only specify an explicit filename'))
4183 4184 file_ = m.files()[0]
4184 4185 filenodes = []
4185 4186 for cp in ctx.parents():
4186 4187 if not cp:
4187 4188 continue
4188 4189 try:
4189 4190 filenodes.append(cp.filenode(file_))
4190 4191 except error.LookupError:
4191 4192 pass
4192 4193 if not filenodes:
4193 4194 raise error.Abort(_("'%s' not found in manifest!") % file_)
4194 4195 p = []
4195 4196 for fn in filenodes:
4196 4197 fctx = repo.filectx(file_, fileid=fn)
4197 4198 p.append(fctx.node())
4198 4199 else:
4199 4200 p = [cp.node() for cp in ctx.parents()]
4200 4201
4201 4202 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4202 4203 for n in p:
4203 4204 if n != nullid:
4204 4205 displayer.show(repo[n])
4205 4206 displayer.close()
4206 4207
4207 4208 @command('paths', formatteropts, _('[NAME]'),
4208 4209 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4209 4210 optionalrepo=True, intents={INTENT_READONLY})
4210 4211 def paths(ui, repo, search=None, **opts):
4211 4212 """show aliases for remote repositories
4212 4213
4213 4214 Show definition of symbolic path name NAME. If no name is given,
4214 4215 show definition of all available names.
4215 4216
4216 4217 Option -q/--quiet suppresses all output when searching for NAME
4217 4218 and shows only the path names when listing all definitions.
4218 4219
4219 4220 Path names are defined in the [paths] section of your
4220 4221 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4221 4222 repository, ``.hg/hgrc`` is used, too.
4222 4223
4223 4224 The path names ``default`` and ``default-push`` have a special
4224 4225 meaning. When performing a push or pull operation, they are used
4225 4226 as fallbacks if no location is specified on the command-line.
4226 4227 When ``default-push`` is set, it will be used for push and
4227 4228 ``default`` will be used for pull; otherwise ``default`` is used
4228 4229 as the fallback for both. When cloning a repository, the clone
4229 4230 source is written as ``default`` in ``.hg/hgrc``.
4230 4231
4231 4232 .. note::
4232 4233
4233 4234 ``default`` and ``default-push`` apply to all inbound (e.g.
4234 4235 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4235 4236 and :hg:`bundle`) operations.
4236 4237
4237 4238 See :hg:`help urls` for more information.
4238 4239
4239 4240 .. container:: verbose
4240 4241
4241 4242 Template:
4242 4243
4243 4244 The following keywords are supported. See also :hg:`help templates`.
4244 4245
4245 4246 :name: String. Symbolic name of the path alias.
4246 4247 :pushurl: String. URL for push operations.
4247 4248 :url: String. URL or directory path for the other operations.
4248 4249
4249 4250 Returns 0 on success.
4250 4251 """
4251 4252
4252 4253 opts = pycompat.byteskwargs(opts)
4253 4254 ui.pager('paths')
4254 4255 if search:
4255 4256 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4256 4257 if name == search]
4257 4258 else:
4258 4259 pathitems = sorted(ui.paths.iteritems())
4259 4260
4260 4261 fm = ui.formatter('paths', opts)
4261 4262 if fm.isplain():
4262 4263 hidepassword = util.hidepassword
4263 4264 else:
4264 4265 hidepassword = bytes
4265 4266 if ui.quiet:
4266 4267 namefmt = '%s\n'
4267 4268 else:
4268 4269 namefmt = '%s = '
4269 4270 showsubopts = not search and not ui.quiet
4270 4271
4271 4272 for name, path in pathitems:
4272 4273 fm.startitem()
4273 4274 fm.condwrite(not search, 'name', namefmt, name)
4274 4275 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4275 4276 for subopt, value in sorted(path.suboptions.items()):
4276 4277 assert subopt not in ('name', 'url')
4277 4278 if showsubopts:
4278 4279 fm.plain('%s:%s = ' % (name, subopt))
4279 4280 fm.condwrite(showsubopts, subopt, '%s\n', value)
4280 4281
4281 4282 fm.end()
4282 4283
4283 4284 if search and not pathitems:
4284 4285 if not ui.quiet:
4285 4286 ui.warn(_("not found!\n"))
4286 4287 return 1
4287 4288 else:
4288 4289 return 0
4289 4290
4290 4291 @command('phase',
4291 4292 [('p', 'public', False, _('set changeset phase to public')),
4292 4293 ('d', 'draft', False, _('set changeset phase to draft')),
4293 4294 ('s', 'secret', False, _('set changeset phase to secret')),
4294 4295 ('f', 'force', False, _('allow to move boundary backward')),
4295 4296 ('r', 'rev', [], _('target revision'), _('REV')),
4296 4297 ],
4297 4298 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4298 4299 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4299 4300 def phase(ui, repo, *revs, **opts):
4300 4301 """set or show the current phase name
4301 4302
4302 4303 With no argument, show the phase name of the current revision(s).
4303 4304
4304 4305 With one of -p/--public, -d/--draft or -s/--secret, change the
4305 4306 phase value of the specified revisions.
4306 4307
4307 4308 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4308 4309 lower phase to a higher phase. Phases are ordered as follows::
4309 4310
4310 4311 public < draft < secret
4311 4312
4312 4313 Returns 0 on success, 1 if some phases could not be changed.
4313 4314
4314 4315 (For more information about the phases concept, see :hg:`help phases`.)
4315 4316 """
4316 4317 opts = pycompat.byteskwargs(opts)
4317 4318 # search for a unique phase argument
4318 4319 targetphase = None
4319 4320 for idx, name in enumerate(phases.cmdphasenames):
4320 4321 if opts[name]:
4321 4322 if targetphase is not None:
4322 4323 raise error.Abort(_('only one phase can be specified'))
4323 4324 targetphase = idx
4324 4325
4325 4326 # look for specified revision
4326 4327 revs = list(revs)
4327 4328 revs.extend(opts['rev'])
4328 4329 if not revs:
4329 4330 # display both parents as the second parent phase can influence
4330 4331 # the phase of a merge commit
4331 4332 revs = [c.rev() for c in repo[None].parents()]
4332 4333
4333 4334 revs = scmutil.revrange(repo, revs)
4334 4335
4335 4336 ret = 0
4336 4337 if targetphase is None:
4337 4338 # display
4338 4339 for r in revs:
4339 4340 ctx = repo[r]
4340 4341 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4341 4342 else:
4342 4343 with repo.lock(), repo.transaction("phase") as tr:
4343 4344 # set phase
4344 4345 if not revs:
4345 4346 raise error.Abort(_('empty revision set'))
4346 4347 nodes = [repo[r].node() for r in revs]
4347 4348 # moving revision from public to draft may hide them
4348 4349 # We have to check result on an unfiltered repository
4349 4350 unfi = repo.unfiltered()
4350 4351 getphase = unfi._phasecache.phase
4351 4352 olddata = [getphase(unfi, r) for r in unfi]
4352 4353 phases.advanceboundary(repo, tr, targetphase, nodes)
4353 4354 if opts['force']:
4354 4355 phases.retractboundary(repo, tr, targetphase, nodes)
4355 4356 getphase = unfi._phasecache.phase
4356 4357 newdata = [getphase(unfi, r) for r in unfi]
4357 4358 changes = sum(newdata[r] != olddata[r] for r in unfi)
4358 4359 cl = unfi.changelog
4359 4360 rejected = [n for n in nodes
4360 4361 if newdata[cl.rev(n)] < targetphase]
4361 4362 if rejected:
4362 4363 ui.warn(_('cannot move %i changesets to a higher '
4363 4364 'phase, use --force\n') % len(rejected))
4364 4365 ret = 1
4365 4366 if changes:
4366 4367 msg = _('phase changed for %i changesets\n') % changes
4367 4368 if ret:
4368 4369 ui.status(msg)
4369 4370 else:
4370 4371 ui.note(msg)
4371 4372 else:
4372 4373 ui.warn(_('no phases changed\n'))
4373 4374 return ret
4374 4375
4375 4376 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4376 4377 """Run after a changegroup has been added via pull/unbundle
4377 4378
4378 4379 This takes arguments below:
4379 4380
4380 4381 :modheads: change of heads by pull/unbundle
4381 4382 :optupdate: updating working directory is needed or not
4382 4383 :checkout: update destination revision (or None to default destination)
4383 4384 :brev: a name, which might be a bookmark to be activated after updating
4384 4385 """
4385 4386 if modheads == 0:
4386 4387 return
4387 4388 if optupdate:
4388 4389 try:
4389 4390 return hg.updatetotally(ui, repo, checkout, brev)
4390 4391 except error.UpdateAbort as inst:
4391 4392 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4392 4393 hint = inst.hint
4393 4394 raise error.UpdateAbort(msg, hint=hint)
4394 4395 if modheads is not None and modheads > 1:
4395 4396 currentbranchheads = len(repo.branchheads())
4396 4397 if currentbranchheads == modheads:
4397 4398 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4398 4399 elif currentbranchheads > 1:
4399 4400 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4400 4401 "merge)\n"))
4401 4402 else:
4402 4403 ui.status(_("(run 'hg heads' to see heads)\n"))
4403 4404 elif not ui.configbool('commands', 'update.requiredest'):
4404 4405 ui.status(_("(run 'hg update' to get a working copy)\n"))
4405 4406
4406 4407 @command('pull',
4407 4408 [('u', 'update', None,
4408 4409 _('update to new branch head if new descendants were pulled')),
4409 4410 ('f', 'force', None, _('run even when remote repository is unrelated')),
4410 4411 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4411 4412 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4412 4413 ('b', 'branch', [], _('a specific branch you would like to pull'),
4413 4414 _('BRANCH')),
4414 4415 ] + remoteopts,
4415 4416 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4416 4417 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4417 4418 helpbasic=True)
4418 4419 def pull(ui, repo, source="default", **opts):
4419 4420 """pull changes from the specified source
4420 4421
4421 4422 Pull changes from a remote repository to a local one.
4422 4423
4423 4424 This finds all changes from the repository at the specified path
4424 4425 or URL and adds them to a local repository (the current one unless
4425 4426 -R is specified). By default, this does not update the copy of the
4426 4427 project in the working directory.
4427 4428
4428 4429 When cloning from servers that support it, Mercurial may fetch
4429 4430 pre-generated data. When this is done, hooks operating on incoming
4430 4431 changesets and changegroups may fire more than once, once for each
4431 4432 pre-generated bundle and as well as for any additional remaining
4432 4433 data. See :hg:`help -e clonebundles` for more.
4433 4434
4434 4435 Use :hg:`incoming` if you want to see what would have been added
4435 4436 by a pull at the time you issued this command. If you then decide
4436 4437 to add those changes to the repository, you should use :hg:`pull
4437 4438 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4438 4439
4439 4440 If SOURCE is omitted, the 'default' path will be used.
4440 4441 See :hg:`help urls` for more information.
4441 4442
4442 4443 Specifying bookmark as ``.`` is equivalent to specifying the active
4443 4444 bookmark's name.
4444 4445
4445 4446 Returns 0 on success, 1 if an update had unresolved files.
4446 4447 """
4447 4448
4448 4449 opts = pycompat.byteskwargs(opts)
4449 4450 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4450 4451 msg = _('update destination required by configuration')
4451 4452 hint = _('use hg pull followed by hg update DEST')
4452 4453 raise error.Abort(msg, hint=hint)
4453 4454
4454 4455 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4455 4456 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4456 4457 other = hg.peer(repo, opts, source)
4457 4458 try:
4458 4459 revs, checkout = hg.addbranchrevs(repo, other, branches,
4459 4460 opts.get('rev'))
4460 4461
4461 4462 pullopargs = {}
4462 4463
4463 4464 nodes = None
4464 4465 if opts.get('bookmark') or revs:
4465 4466 # The list of bookmark used here is the same used to actually update
4466 4467 # the bookmark names, to avoid the race from issue 4689 and we do
4467 4468 # all lookup and bookmark queries in one go so they see the same
4468 4469 # version of the server state (issue 4700).
4469 4470 nodes = []
4470 4471 fnodes = []
4471 4472 revs = revs or []
4472 4473 if revs and not other.capable('lookup'):
4473 4474 err = _("other repository doesn't support revision lookup, "
4474 4475 "so a rev cannot be specified.")
4475 4476 raise error.Abort(err)
4476 4477 with other.commandexecutor() as e:
4477 4478 fremotebookmarks = e.callcommand('listkeys', {
4478 4479 'namespace': 'bookmarks'
4479 4480 })
4480 4481 for r in revs:
4481 4482 fnodes.append(e.callcommand('lookup', {'key': r}))
4482 4483 remotebookmarks = fremotebookmarks.result()
4483 4484 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4484 4485 pullopargs['remotebookmarks'] = remotebookmarks
4485 4486 for b in opts.get('bookmark', []):
4486 4487 b = repo._bookmarks.expandname(b)
4487 4488 if b not in remotebookmarks:
4488 4489 raise error.Abort(_('remote bookmark %s not found!') % b)
4489 4490 nodes.append(remotebookmarks[b])
4490 4491 for i, rev in enumerate(revs):
4491 4492 node = fnodes[i].result()
4492 4493 nodes.append(node)
4493 4494 if rev == checkout:
4494 4495 checkout = node
4495 4496
4496 4497 wlock = util.nullcontextmanager()
4497 4498 if opts.get('update'):
4498 4499 wlock = repo.wlock()
4499 4500 with wlock:
4500 4501 pullopargs.update(opts.get('opargs', {}))
4501 4502 modheads = exchange.pull(repo, other, heads=nodes,
4502 4503 force=opts.get('force'),
4503 4504 bookmarks=opts.get('bookmark', ()),
4504 4505 opargs=pullopargs).cgresult
4505 4506
4506 4507 # brev is a name, which might be a bookmark to be activated at
4507 4508 # the end of the update. In other words, it is an explicit
4508 4509 # destination of the update
4509 4510 brev = None
4510 4511
4511 4512 if checkout:
4512 4513 checkout = repo.unfiltered().changelog.rev(checkout)
4513 4514
4514 4515 # order below depends on implementation of
4515 4516 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4516 4517 # because 'checkout' is determined without it.
4517 4518 if opts.get('rev'):
4518 4519 brev = opts['rev'][0]
4519 4520 elif opts.get('branch'):
4520 4521 brev = opts['branch'][0]
4521 4522 else:
4522 4523 brev = branches[0]
4523 4524 repo._subtoppath = source
4524 4525 try:
4525 4526 ret = postincoming(ui, repo, modheads, opts.get('update'),
4526 4527 checkout, brev)
4527 4528 except error.FilteredRepoLookupError as exc:
4528 4529 msg = _('cannot update to target: %s') % exc.args[0]
4529 4530 exc.args = (msg,) + exc.args[1:]
4530 4531 raise
4531 4532 finally:
4532 4533 del repo._subtoppath
4533 4534
4534 4535 finally:
4535 4536 other.close()
4536 4537 return ret
4537 4538
4538 4539 @command('push',
4539 4540 [('f', 'force', None, _('force push')),
4540 4541 ('r', 'rev', [],
4541 4542 _('a changeset intended to be included in the destination'),
4542 4543 _('REV')),
4543 4544 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4544 4545 ('b', 'branch', [],
4545 4546 _('a specific branch you would like to push'), _('BRANCH')),
4546 4547 ('', 'new-branch', False, _('allow pushing a new branch')),
4547 4548 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4548 4549 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4549 4550 ] + remoteopts,
4550 4551 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4551 4552 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4552 4553 helpbasic=True)
4553 4554 def push(ui, repo, dest=None, **opts):
4554 4555 """push changes to the specified destination
4555 4556
4556 4557 Push changesets from the local repository to the specified
4557 4558 destination.
4558 4559
4559 4560 This operation is symmetrical to pull: it is identical to a pull
4560 4561 in the destination repository from the current one.
4561 4562
4562 4563 By default, push will not allow creation of new heads at the
4563 4564 destination, since multiple heads would make it unclear which head
4564 4565 to use. In this situation, it is recommended to pull and merge
4565 4566 before pushing.
4566 4567
4567 4568 Use --new-branch if you want to allow push to create a new named
4568 4569 branch that is not present at the destination. This allows you to
4569 4570 only create a new branch without forcing other changes.
4570 4571
4571 4572 .. note::
4572 4573
4573 4574 Extra care should be taken with the -f/--force option,
4574 4575 which will push all new heads on all branches, an action which will
4575 4576 almost always cause confusion for collaborators.
4576 4577
4577 4578 If -r/--rev is used, the specified revision and all its ancestors
4578 4579 will be pushed to the remote repository.
4579 4580
4580 4581 If -B/--bookmark is used, the specified bookmarked revision, its
4581 4582 ancestors, and the bookmark will be pushed to the remote
4582 4583 repository. Specifying ``.`` is equivalent to specifying the active
4583 4584 bookmark's name.
4584 4585
4585 4586 Please see :hg:`help urls` for important details about ``ssh://``
4586 4587 URLs. If DESTINATION is omitted, a default path will be used.
4587 4588
4588 4589 .. container:: verbose
4589 4590
4590 4591 The --pushvars option sends strings to the server that become
4591 4592 environment variables prepended with ``HG_USERVAR_``. For example,
4592 4593 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4593 4594 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4594 4595
4595 4596 pushvars can provide for user-overridable hooks as well as set debug
4596 4597 levels. One example is having a hook that blocks commits containing
4597 4598 conflict markers, but enables the user to override the hook if the file
4598 4599 is using conflict markers for testing purposes or the file format has
4599 4600 strings that look like conflict markers.
4600 4601
4601 4602 By default, servers will ignore `--pushvars`. To enable it add the
4602 4603 following to your configuration file::
4603 4604
4604 4605 [push]
4605 4606 pushvars.server = true
4606 4607
4607 4608 Returns 0 if push was successful, 1 if nothing to push.
4608 4609 """
4609 4610
4610 4611 opts = pycompat.byteskwargs(opts)
4611 4612 if opts.get('bookmark'):
4612 4613 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4613 4614 for b in opts['bookmark']:
4614 4615 # translate -B options to -r so changesets get pushed
4615 4616 b = repo._bookmarks.expandname(b)
4616 4617 if b in repo._bookmarks:
4617 4618 opts.setdefault('rev', []).append(b)
4618 4619 else:
4619 4620 # if we try to push a deleted bookmark, translate it to null
4620 4621 # this lets simultaneous -r, -b options continue working
4621 4622 opts.setdefault('rev', []).append("null")
4622 4623
4623 4624 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4624 4625 if not path:
4625 4626 raise error.Abort(_('default repository not configured!'),
4626 4627 hint=_("see 'hg help config.paths'"))
4627 4628 dest = path.pushloc or path.loc
4628 4629 branches = (path.branch, opts.get('branch') or [])
4629 4630 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4630 4631 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4631 4632 other = hg.peer(repo, opts, dest)
4632 4633
4633 4634 if revs:
4634 4635 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4635 4636 if not revs:
4636 4637 raise error.Abort(_("specified revisions evaluate to an empty set"),
4637 4638 hint=_("use different revision arguments"))
4638 4639 elif path.pushrev:
4639 4640 # It doesn't make any sense to specify ancestor revisions. So limit
4640 4641 # to DAG heads to make discovery simpler.
4641 4642 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4642 4643 revs = scmutil.revrange(repo, [expr])
4643 4644 revs = [repo[rev].node() for rev in revs]
4644 4645 if not revs:
4645 4646 raise error.Abort(_('default push revset for path evaluates to an '
4646 4647 'empty set'))
4647 4648
4648 4649 repo._subtoppath = dest
4649 4650 try:
4650 4651 # push subrepos depth-first for coherent ordering
4651 4652 c = repo['.']
4652 4653 subs = c.substate # only repos that are committed
4653 4654 for s in sorted(subs):
4654 4655 result = c.sub(s).push(opts)
4655 4656 if result == 0:
4656 4657 return not result
4657 4658 finally:
4658 4659 del repo._subtoppath
4659 4660
4660 4661 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4661 4662 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4662 4663
4663 4664 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4664 4665 newbranch=opts.get('new_branch'),
4665 4666 bookmarks=opts.get('bookmark', ()),
4666 4667 publish=opts.get('publish'),
4667 4668 opargs=opargs)
4668 4669
4669 4670 result = not pushop.cgresult
4670 4671
4671 4672 if pushop.bkresult is not None:
4672 4673 if pushop.bkresult == 2:
4673 4674 result = 2
4674 4675 elif not result and pushop.bkresult:
4675 4676 result = 2
4676 4677
4677 4678 return result
4678 4679
4679 4680 @command('recover',
4680 4681 [('','verify', True, "run `hg verify` after succesful recover"),
4681 4682 ],
4682 4683 helpcategory=command.CATEGORY_MAINTENANCE)
4683 4684 def recover(ui, repo, **opts):
4684 4685 """roll back an interrupted transaction
4685 4686
4686 4687 Recover from an interrupted commit or pull.
4687 4688
4688 4689 This command tries to fix the repository status after an
4689 4690 interrupted operation. It should only be necessary when Mercurial
4690 4691 suggests it.
4691 4692
4692 4693 Returns 0 if successful, 1 if nothing to recover or verify fails.
4693 4694 """
4694 4695 ret = repo.recover()
4695 4696 if ret:
4696 4697 if opts[r'verify']:
4697 4698 return hg.verify(repo)
4698 4699 else:
4699 4700 msg = _("(verify step skipped, run `hg verify` to check your "
4700 4701 "repository content)\n")
4701 4702 ui.warn(msg)
4702 4703 return 0
4703 4704 return 1
4704 4705
4705 4706 @command('remove|rm',
4706 4707 [('A', 'after', None, _('record delete for missing files')),
4707 4708 ('f', 'force', None,
4708 4709 _('forget added files, delete modified files')),
4709 4710 ] + subrepoopts + walkopts + dryrunopts,
4710 4711 _('[OPTION]... FILE...'),
4711 4712 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4712 4713 helpbasic=True, inferrepo=True)
4713 4714 def remove(ui, repo, *pats, **opts):
4714 4715 """remove the specified files on the next commit
4715 4716
4716 4717 Schedule the indicated files for removal from the current branch.
4717 4718
4718 4719 This command schedules the files to be removed at the next commit.
4719 4720 To undo a remove before that, see :hg:`revert`. To undo added
4720 4721 files, see :hg:`forget`.
4721 4722
4722 4723 .. container:: verbose
4723 4724
4724 4725 -A/--after can be used to remove only files that have already
4725 4726 been deleted, -f/--force can be used to force deletion, and -Af
4726 4727 can be used to remove files from the next revision without
4727 4728 deleting them from the working directory.
4728 4729
4729 4730 The following table details the behavior of remove for different
4730 4731 file states (columns) and option combinations (rows). The file
4731 4732 states are Added [A], Clean [C], Modified [M] and Missing [!]
4732 4733 (as reported by :hg:`status`). The actions are Warn, Remove
4733 4734 (from branch) and Delete (from disk):
4734 4735
4735 4736 ========= == == == ==
4736 4737 opt/state A C M !
4737 4738 ========= == == == ==
4738 4739 none W RD W R
4739 4740 -f R RD RD R
4740 4741 -A W W W R
4741 4742 -Af R R R R
4742 4743 ========= == == == ==
4743 4744
4744 4745 .. note::
4745 4746
4746 4747 :hg:`remove` never deletes files in Added [A] state from the
4747 4748 working directory, not even if ``--force`` is specified.
4748 4749
4749 4750 Returns 0 on success, 1 if any warnings encountered.
4750 4751 """
4751 4752
4752 4753 opts = pycompat.byteskwargs(opts)
4753 4754 after, force = opts.get('after'), opts.get('force')
4754 4755 dryrun = opts.get('dry_run')
4755 4756 if not pats and not after:
4756 4757 raise error.Abort(_('no files specified'))
4757 4758
4758 4759 m = scmutil.match(repo[None], pats, opts)
4759 4760 subrepos = opts.get('subrepos')
4760 4761 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
4761 4762 return cmdutil.remove(ui, repo, m, "", uipathfn, after, force, subrepos,
4762 4763 dryrun=dryrun)
4763 4764
4764 4765 @command('rename|move|mv',
4765 4766 [('A', 'after', None, _('record a rename that has already occurred')),
4766 4767 ('f', 'force', None, _('forcibly move over an existing managed file')),
4767 4768 ] + walkopts + dryrunopts,
4768 4769 _('[OPTION]... SOURCE... DEST'),
4769 4770 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4770 4771 def rename(ui, repo, *pats, **opts):
4771 4772 """rename files; equivalent of copy + remove
4772 4773
4773 4774 Mark dest as copies of sources; mark sources for deletion. If dest
4774 4775 is a directory, copies are put in that directory. If dest is a
4775 4776 file, there can only be one source.
4776 4777
4777 4778 By default, this command copies the contents of files as they
4778 4779 exist in the working directory. If invoked with -A/--after, the
4779 4780 operation is recorded, but no copying is performed.
4780 4781
4781 4782 This command takes effect at the next commit. To undo a rename
4782 4783 before that, see :hg:`revert`.
4783 4784
4784 4785 Returns 0 on success, 1 if errors are encountered.
4785 4786 """
4786 4787 opts = pycompat.byteskwargs(opts)
4787 4788 with repo.wlock(False):
4788 4789 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4789 4790
4790 4791 @command('resolve',
4791 4792 [('a', 'all', None, _('select all unresolved files')),
4792 4793 ('l', 'list', None, _('list state of files needing merge')),
4793 4794 ('m', 'mark', None, _('mark files as resolved')),
4794 4795 ('u', 'unmark', None, _('mark files as unresolved')),
4795 4796 ('n', 'no-status', None, _('hide status prefix')),
4796 4797 ('', 're-merge', None, _('re-merge files'))]
4797 4798 + mergetoolopts + walkopts + formatteropts,
4798 4799 _('[OPTION]... [FILE]...'),
4799 4800 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4800 4801 inferrepo=True)
4801 4802 def resolve(ui, repo, *pats, **opts):
4802 4803 """redo merges or set/view the merge status of files
4803 4804
4804 4805 Merges with unresolved conflicts are often the result of
4805 4806 non-interactive merging using the ``internal:merge`` configuration
4806 4807 setting, or a command-line merge tool like ``diff3``. The resolve
4807 4808 command is used to manage the files involved in a merge, after
4808 4809 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4809 4810 working directory must have two parents). See :hg:`help
4810 4811 merge-tools` for information on configuring merge tools.
4811 4812
4812 4813 The resolve command can be used in the following ways:
4813 4814
4814 4815 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4815 4816 the specified files, discarding any previous merge attempts. Re-merging
4816 4817 is not performed for files already marked as resolved. Use ``--all/-a``
4817 4818 to select all unresolved files. ``--tool`` can be used to specify
4818 4819 the merge tool used for the given files. It overrides the HGMERGE
4819 4820 environment variable and your configuration files. Previous file
4820 4821 contents are saved with a ``.orig`` suffix.
4821 4822
4822 4823 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4823 4824 (e.g. after having manually fixed-up the files). The default is
4824 4825 to mark all unresolved files.
4825 4826
4826 4827 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4827 4828 default is to mark all resolved files.
4828 4829
4829 4830 - :hg:`resolve -l`: list files which had or still have conflicts.
4830 4831 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4831 4832 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4832 4833 the list. See :hg:`help filesets` for details.
4833 4834
4834 4835 .. note::
4835 4836
4836 4837 Mercurial will not let you commit files with unresolved merge
4837 4838 conflicts. You must use :hg:`resolve -m ...` before you can
4838 4839 commit after a conflicting merge.
4839 4840
4840 4841 .. container:: verbose
4841 4842
4842 4843 Template:
4843 4844
4844 4845 The following keywords are supported in addition to the common template
4845 4846 keywords and functions. See also :hg:`help templates`.
4846 4847
4847 4848 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4848 4849 :path: String. Repository-absolute path of the file.
4849 4850
4850 4851 Returns 0 on success, 1 if any files fail a resolve attempt.
4851 4852 """
4852 4853
4853 4854 opts = pycompat.byteskwargs(opts)
4854 4855 confirm = ui.configbool('commands', 'resolve.confirm')
4855 4856 flaglist = 'all mark unmark list no_status re_merge'.split()
4856 4857 all, mark, unmark, show, nostatus, remerge = [
4857 4858 opts.get(o) for o in flaglist]
4858 4859
4859 4860 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4860 4861 if actioncount > 1:
4861 4862 raise error.Abort(_("too many actions specified"))
4862 4863 elif (actioncount == 0
4863 4864 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4864 4865 hint = _('use --mark, --unmark, --list or --re-merge')
4865 4866 raise error.Abort(_('no action specified'), hint=hint)
4866 4867 if pats and all:
4867 4868 raise error.Abort(_("can't specify --all and patterns"))
4868 4869 if not (all or pats or show or mark or unmark):
4869 4870 raise error.Abort(_('no files or directories specified'),
4870 4871 hint=('use --all to re-merge all unresolved files'))
4871 4872
4872 4873 if confirm:
4873 4874 if all:
4874 4875 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4875 4876 b'$$ &Yes $$ &No')):
4876 4877 raise error.Abort(_('user quit'))
4877 4878 if mark and not pats:
4878 4879 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4879 4880 b'$$ &Yes $$ &No')):
4880 4881 raise error.Abort(_('user quit'))
4881 4882 if unmark and not pats:
4882 4883 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4883 4884 b'$$ &Yes $$ &No')):
4884 4885 raise error.Abort(_('user quit'))
4885 4886
4886 4887 uipathfn = scmutil.getuipathfn(repo)
4887 4888
4888 4889 if show:
4889 4890 ui.pager('resolve')
4890 4891 fm = ui.formatter('resolve', opts)
4891 4892 ms = mergemod.mergestate.read(repo)
4892 4893 wctx = repo[None]
4893 4894 m = scmutil.match(wctx, pats, opts)
4894 4895
4895 4896 # Labels and keys based on merge state. Unresolved path conflicts show
4896 4897 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4897 4898 # resolved conflicts.
4898 4899 mergestateinfo = {
4899 4900 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4900 4901 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4901 4902 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4902 4903 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4903 4904 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4904 4905 'D'),
4905 4906 }
4906 4907
4907 4908 for f in ms:
4908 4909 if not m(f):
4909 4910 continue
4910 4911
4911 4912 label, key = mergestateinfo[ms[f]]
4912 4913 fm.startitem()
4913 4914 fm.context(ctx=wctx)
4914 4915 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4915 4916 fm.data(path=f)
4916 4917 fm.plain('%s\n' % uipathfn(f), label=label)
4917 4918 fm.end()
4918 4919 return 0
4919 4920
4920 4921 with repo.wlock():
4921 4922 ms = mergemod.mergestate.read(repo)
4922 4923
4923 4924 if not (ms.active() or repo.dirstate.p2() != nullid):
4924 4925 raise error.Abort(
4925 4926 _('resolve command not applicable when not merging'))
4926 4927
4927 4928 wctx = repo[None]
4928 4929
4929 4930 if (ms.mergedriver
4930 4931 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4931 4932 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4932 4933 ms.commit()
4933 4934 # allow mark and unmark to go through
4934 4935 if not mark and not unmark and not proceed:
4935 4936 return 1
4936 4937
4937 4938 m = scmutil.match(wctx, pats, opts)
4938 4939 ret = 0
4939 4940 didwork = False
4940 4941 runconclude = False
4941 4942
4942 4943 tocomplete = []
4943 4944 hasconflictmarkers = []
4944 4945 if mark:
4945 4946 markcheck = ui.config('commands', 'resolve.mark-check')
4946 4947 if markcheck not in ['warn', 'abort']:
4947 4948 # Treat all invalid / unrecognized values as 'none'.
4948 4949 markcheck = False
4949 4950 for f in ms:
4950 4951 if not m(f):
4951 4952 continue
4952 4953
4953 4954 didwork = True
4954 4955
4955 4956 # don't let driver-resolved files be marked, and run the conclude
4956 4957 # step if asked to resolve
4957 4958 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4958 4959 exact = m.exact(f)
4959 4960 if mark:
4960 4961 if exact:
4961 4962 ui.warn(_('not marking %s as it is driver-resolved\n')
4962 4963 % uipathfn(f))
4963 4964 elif unmark:
4964 4965 if exact:
4965 4966 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4966 4967 % uipathfn(f))
4967 4968 else:
4968 4969 runconclude = True
4969 4970 continue
4970 4971
4971 4972 # path conflicts must be resolved manually
4972 4973 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4973 4974 mergemod.MERGE_RECORD_RESOLVED_PATH):
4974 4975 if mark:
4975 4976 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4976 4977 elif unmark:
4977 4978 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4978 4979 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4979 4980 ui.warn(_('%s: path conflict must be resolved manually\n')
4980 4981 % uipathfn(f))
4981 4982 continue
4982 4983
4983 4984 if mark:
4984 4985 if markcheck:
4985 4986 fdata = repo.wvfs.tryread(f)
4986 4987 if (filemerge.hasconflictmarkers(fdata) and
4987 4988 ms[f] != mergemod.MERGE_RECORD_RESOLVED):
4988 4989 hasconflictmarkers.append(f)
4989 4990 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4990 4991 elif unmark:
4991 4992 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4992 4993 else:
4993 4994 # backup pre-resolve (merge uses .orig for its own purposes)
4994 4995 a = repo.wjoin(f)
4995 4996 try:
4996 4997 util.copyfile(a, a + ".resolve")
4997 4998 except (IOError, OSError) as inst:
4998 4999 if inst.errno != errno.ENOENT:
4999 5000 raise
5000 5001
5001 5002 try:
5002 5003 # preresolve file
5003 5004 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5004 5005 with ui.configoverride(overrides, 'resolve'):
5005 5006 complete, r = ms.preresolve(f, wctx)
5006 5007 if not complete:
5007 5008 tocomplete.append(f)
5008 5009 elif r:
5009 5010 ret = 1
5010 5011 finally:
5011 5012 ms.commit()
5012 5013
5013 5014 # replace filemerge's .orig file with our resolve file, but only
5014 5015 # for merges that are complete
5015 5016 if complete:
5016 5017 try:
5017 5018 util.rename(a + ".resolve",
5018 5019 scmutil.backuppath(ui, repo, f))
5019 5020 except OSError as inst:
5020 5021 if inst.errno != errno.ENOENT:
5021 5022 raise
5022 5023
5023 5024 if hasconflictmarkers:
5024 5025 ui.warn(_('warning: the following files still have conflict '
5025 5026 'markers:\n') + ''.join(' ' + uipathfn(f) + '\n'
5026 5027 for f in hasconflictmarkers))
5027 5028 if markcheck == 'abort' and not all and not pats:
5028 5029 raise error.Abort(_('conflict markers detected'),
5029 5030 hint=_('use --all to mark anyway'))
5030 5031
5031 5032 for f in tocomplete:
5032 5033 try:
5033 5034 # resolve file
5034 5035 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5035 5036 with ui.configoverride(overrides, 'resolve'):
5036 5037 r = ms.resolve(f, wctx)
5037 5038 if r:
5038 5039 ret = 1
5039 5040 finally:
5040 5041 ms.commit()
5041 5042
5042 5043 # replace filemerge's .orig file with our resolve file
5043 5044 a = repo.wjoin(f)
5044 5045 try:
5045 5046 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
5046 5047 except OSError as inst:
5047 5048 if inst.errno != errno.ENOENT:
5048 5049 raise
5049 5050
5050 5051 ms.commit()
5051 5052 ms.recordactions()
5052 5053
5053 5054 if not didwork and pats:
5054 5055 hint = None
5055 5056 if not any([p for p in pats if p.find(':') >= 0]):
5056 5057 pats = ['path:%s' % p for p in pats]
5057 5058 m = scmutil.match(wctx, pats, opts)
5058 5059 for f in ms:
5059 5060 if not m(f):
5060 5061 continue
5061 5062 def flag(o):
5062 5063 if o == 're_merge':
5063 5064 return '--re-merge '
5064 5065 return '-%s ' % o[0:1]
5065 5066 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5066 5067 hint = _("(try: hg resolve %s%s)\n") % (
5067 5068 flags,
5068 5069 ' '.join(pats))
5069 5070 break
5070 5071 ui.warn(_("arguments do not match paths that need resolving\n"))
5071 5072 if hint:
5072 5073 ui.warn(hint)
5073 5074 elif ms.mergedriver and ms.mdstate() != 's':
5074 5075 # run conclude step when either a driver-resolved file is requested
5075 5076 # or there are no driver-resolved files
5076 5077 # we can't use 'ret' to determine whether any files are unresolved
5077 5078 # because we might not have tried to resolve some
5078 5079 if ((runconclude or not list(ms.driverresolved()))
5079 5080 and not list(ms.unresolved())):
5080 5081 proceed = mergemod.driverconclude(repo, ms, wctx)
5081 5082 ms.commit()
5082 5083 if not proceed:
5083 5084 return 1
5084 5085
5085 5086 # Nudge users into finishing an unfinished operation
5086 5087 unresolvedf = list(ms.unresolved())
5087 5088 driverresolvedf = list(ms.driverresolved())
5088 5089 if not unresolvedf and not driverresolvedf:
5089 5090 ui.status(_('(no more unresolved files)\n'))
5090 5091 cmdutil.checkafterresolved(repo)
5091 5092 elif not unresolvedf:
5092 5093 ui.status(_('(no more unresolved files -- '
5093 5094 'run "hg resolve --all" to conclude)\n'))
5094 5095
5095 5096 return ret
5096 5097
5097 5098 @command('revert',
5098 5099 [('a', 'all', None, _('revert all changes when no arguments given')),
5099 5100 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5100 5101 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5101 5102 ('C', 'no-backup', None, _('do not save backup copies of files')),
5102 5103 ('i', 'interactive', None, _('interactively select the changes')),
5103 5104 ] + walkopts + dryrunopts,
5104 5105 _('[OPTION]... [-r REV] [NAME]...'),
5105 5106 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5106 5107 def revert(ui, repo, *pats, **opts):
5107 5108 """restore files to their checkout state
5108 5109
5109 5110 .. note::
5110 5111
5111 5112 To check out earlier revisions, you should use :hg:`update REV`.
5112 5113 To cancel an uncommitted merge (and lose your changes),
5113 5114 use :hg:`merge --abort`.
5114 5115
5115 5116 With no revision specified, revert the specified files or directories
5116 5117 to the contents they had in the parent of the working directory.
5117 5118 This restores the contents of files to an unmodified
5118 5119 state and unschedules adds, removes, copies, and renames. If the
5119 5120 working directory has two parents, you must explicitly specify a
5120 5121 revision.
5121 5122
5122 5123 Using the -r/--rev or -d/--date options, revert the given files or
5123 5124 directories to their states as of a specific revision. Because
5124 5125 revert does not change the working directory parents, this will
5125 5126 cause these files to appear modified. This can be helpful to "back
5126 5127 out" some or all of an earlier change. See :hg:`backout` for a
5127 5128 related method.
5128 5129
5129 5130 Modified files are saved with a .orig suffix before reverting.
5130 5131 To disable these backups, use --no-backup. It is possible to store
5131 5132 the backup files in a custom directory relative to the root of the
5132 5133 repository by setting the ``ui.origbackuppath`` configuration
5133 5134 option.
5134 5135
5135 5136 See :hg:`help dates` for a list of formats valid for -d/--date.
5136 5137
5137 5138 See :hg:`help backout` for a way to reverse the effect of an
5138 5139 earlier changeset.
5139 5140
5140 5141 Returns 0 on success.
5141 5142 """
5142 5143
5143 5144 opts = pycompat.byteskwargs(opts)
5144 5145 if opts.get("date"):
5145 5146 if opts.get("rev"):
5146 5147 raise error.Abort(_("you can't specify a revision and a date"))
5147 5148 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5148 5149
5149 5150 parent, p2 = repo.dirstate.parents()
5150 5151 if not opts.get('rev') and p2 != nullid:
5151 5152 # revert after merge is a trap for new users (issue2915)
5152 5153 raise error.Abort(_('uncommitted merge with no revision specified'),
5153 5154 hint=_("use 'hg update' or see 'hg help revert'"))
5154 5155
5155 5156 rev = opts.get('rev')
5156 5157 if rev:
5157 5158 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5158 5159 ctx = scmutil.revsingle(repo, rev)
5159 5160
5160 5161 if (not (pats or opts.get('include') or opts.get('exclude') or
5161 5162 opts.get('all') or opts.get('interactive'))):
5162 5163 msg = _("no files or directories specified")
5163 5164 if p2 != nullid:
5164 5165 hint = _("uncommitted merge, use --all to discard all changes,"
5165 5166 " or 'hg update -C .' to abort the merge")
5166 5167 raise error.Abort(msg, hint=hint)
5167 5168 dirty = any(repo.status())
5168 5169 node = ctx.node()
5169 5170 if node != parent:
5170 5171 if dirty:
5171 5172 hint = _("uncommitted changes, use --all to discard all"
5172 5173 " changes, or 'hg update %d' to update") % ctx.rev()
5173 5174 else:
5174 5175 hint = _("use --all to revert all files,"
5175 5176 " or 'hg update %d' to update") % ctx.rev()
5176 5177 elif dirty:
5177 5178 hint = _("uncommitted changes, use --all to discard all changes")
5178 5179 else:
5179 5180 hint = _("use --all to revert all files")
5180 5181 raise error.Abort(msg, hint=hint)
5181 5182
5182 5183 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5183 5184 **pycompat.strkwargs(opts))
5184 5185
5185 5186 @command(
5186 5187 'rollback',
5187 5188 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5188 5189 helpcategory=command.CATEGORY_MAINTENANCE)
5189 5190 def rollback(ui, repo, **opts):
5190 5191 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5191 5192
5192 5193 Please use :hg:`commit --amend` instead of rollback to correct
5193 5194 mistakes in the last commit.
5194 5195
5195 5196 This command should be used with care. There is only one level of
5196 5197 rollback, and there is no way to undo a rollback. It will also
5197 5198 restore the dirstate at the time of the last transaction, losing
5198 5199 any dirstate changes since that time. This command does not alter
5199 5200 the working directory.
5200 5201
5201 5202 Transactions are used to encapsulate the effects of all commands
5202 5203 that create new changesets or propagate existing changesets into a
5203 5204 repository.
5204 5205
5205 5206 .. container:: verbose
5206 5207
5207 5208 For example, the following commands are transactional, and their
5208 5209 effects can be rolled back:
5209 5210
5210 5211 - commit
5211 5212 - import
5212 5213 - pull
5213 5214 - push (with this repository as the destination)
5214 5215 - unbundle
5215 5216
5216 5217 To avoid permanent data loss, rollback will refuse to rollback a
5217 5218 commit transaction if it isn't checked out. Use --force to
5218 5219 override this protection.
5219 5220
5220 5221 The rollback command can be entirely disabled by setting the
5221 5222 ``ui.rollback`` configuration setting to false. If you're here
5222 5223 because you want to use rollback and it's disabled, you can
5223 5224 re-enable the command by setting ``ui.rollback`` to true.
5224 5225
5225 5226 This command is not intended for use on public repositories. Once
5226 5227 changes are visible for pull by other users, rolling a transaction
5227 5228 back locally is ineffective (someone else may already have pulled
5228 5229 the changes). Furthermore, a race is possible with readers of the
5229 5230 repository; for example an in-progress pull from the repository
5230 5231 may fail if a rollback is performed.
5231 5232
5232 5233 Returns 0 on success, 1 if no rollback data is available.
5233 5234 """
5234 5235 if not ui.configbool('ui', 'rollback'):
5235 5236 raise error.Abort(_('rollback is disabled because it is unsafe'),
5236 5237 hint=('see `hg help -v rollback` for information'))
5237 5238 return repo.rollback(dryrun=opts.get(r'dry_run'),
5238 5239 force=opts.get(r'force'))
5239 5240
5240 5241 @command(
5241 5242 'root', [] + formatteropts, intents={INTENT_READONLY},
5242 5243 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5243 5244 def root(ui, repo, **opts):
5244 5245 """print the root (top) of the current working directory
5245 5246
5246 5247 Print the root directory of the current repository.
5247 5248
5248 5249 .. container:: verbose
5249 5250
5250 5251 Template:
5251 5252
5252 5253 The following keywords are supported in addition to the common template
5253 5254 keywords and functions. See also :hg:`help templates`.
5254 5255
5255 5256 :hgpath: String. Path to the .hg directory.
5256 5257 :storepath: String. Path to the directory holding versioned data.
5257 5258
5258 5259 Returns 0 on success.
5259 5260 """
5260 5261 opts = pycompat.byteskwargs(opts)
5261 5262 with ui.formatter('root', opts) as fm:
5262 5263 fm.startitem()
5263 5264 fm.write('reporoot', '%s\n', repo.root)
5264 5265 fm.data(hgpath=repo.path, storepath=repo.spath)
5265 5266
5266 5267 @command('serve',
5267 5268 [('A', 'accesslog', '', _('name of access log file to write to'),
5268 5269 _('FILE')),
5269 5270 ('d', 'daemon', None, _('run server in background')),
5270 5271 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5271 5272 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5272 5273 # use string type, then we can check if something was passed
5273 5274 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5274 5275 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5275 5276 _('ADDR')),
5276 5277 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5277 5278 _('PREFIX')),
5278 5279 ('n', 'name', '',
5279 5280 _('name to show in web pages (default: working directory)'), _('NAME')),
5280 5281 ('', 'web-conf', '',
5281 5282 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5282 5283 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5283 5284 _('FILE')),
5284 5285 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5285 5286 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5286 5287 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5287 5288 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5288 5289 ('', 'style', '', _('template style to use'), _('STYLE')),
5289 5290 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5290 5291 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5291 5292 ('', 'print-url', None, _('start and print only the URL'))]
5292 5293 + subrepoopts,
5293 5294 _('[OPTION]...'),
5294 5295 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5295 5296 helpbasic=True, optionalrepo=True)
5296 5297 def serve(ui, repo, **opts):
5297 5298 """start stand-alone webserver
5298 5299
5299 5300 Start a local HTTP repository browser and pull server. You can use
5300 5301 this for ad-hoc sharing and browsing of repositories. It is
5301 5302 recommended to use a real web server to serve a repository for
5302 5303 longer periods of time.
5303 5304
5304 5305 Please note that the server does not implement access control.
5305 5306 This means that, by default, anybody can read from the server and
5306 5307 nobody can write to it by default. Set the ``web.allow-push``
5307 5308 option to ``*`` to allow everybody to push to the server. You
5308 5309 should use a real web server if you need to authenticate users.
5309 5310
5310 5311 By default, the server logs accesses to stdout and errors to
5311 5312 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5312 5313 files.
5313 5314
5314 5315 To have the server choose a free port number to listen on, specify
5315 5316 a port number of 0; in this case, the server will print the port
5316 5317 number it uses.
5317 5318
5318 5319 Returns 0 on success.
5319 5320 """
5320 5321
5321 5322 opts = pycompat.byteskwargs(opts)
5322 5323 if opts["stdio"] and opts["cmdserver"]:
5323 5324 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5324 5325 if opts["print_url"] and ui.verbose:
5325 5326 raise error.Abort(_("cannot use --print-url with --verbose"))
5326 5327
5327 5328 if opts["stdio"]:
5328 5329 if repo is None:
5329 5330 raise error.RepoError(_("there is no Mercurial repository here"
5330 5331 " (.hg not found)"))
5331 5332 s = wireprotoserver.sshserver(ui, repo)
5332 5333 s.serve_forever()
5333 5334
5334 5335 service = server.createservice(ui, repo, opts)
5335 5336 return server.runservice(opts, initfn=service.init, runfn=service.run)
5336 5337
5337 5338 @command('shelve',
5338 5339 [('A', 'addremove', None,
5339 5340 _('mark new/missing files as added/removed before shelving')),
5340 5341 ('u', 'unknown', None,
5341 5342 _('store unknown files in the shelve')),
5342 5343 ('', 'cleanup', None,
5343 5344 _('delete all shelved changes')),
5344 5345 ('', 'date', '',
5345 5346 _('shelve with the specified commit date'), _('DATE')),
5346 5347 ('d', 'delete', None,
5347 5348 _('delete the named shelved change(s)')),
5348 5349 ('e', 'edit', False,
5349 5350 _('invoke editor on commit messages')),
5350 5351 ('k', 'keep', False,
5351 5352 _('shelve, but keep changes in the working directory')),
5352 5353 ('l', 'list', None,
5353 5354 _('list current shelves')),
5354 5355 ('m', 'message', '',
5355 5356 _('use text as shelve message'), _('TEXT')),
5356 5357 ('n', 'name', '',
5357 5358 _('use the given name for the shelved commit'), _('NAME')),
5358 5359 ('p', 'patch', None,
5359 5360 _('output patches for changes (provide the names of the shelved '
5360 5361 'changes as positional arguments)')),
5361 5362 ('i', 'interactive', None,
5362 5363 _('interactive mode')),
5363 5364 ('', 'stat', None,
5364 5365 _('output diffstat-style summary of changes (provide the names of '
5365 5366 'the shelved changes as positional arguments)')
5366 5367 )] + cmdutil.walkopts,
5367 5368 _('hg shelve [OPTION]... [FILE]...'),
5368 5369 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5369 5370 def shelve(ui, repo, *pats, **opts):
5370 5371 '''save and set aside changes from the working directory
5371 5372
5372 5373 Shelving takes files that "hg status" reports as not clean, saves
5373 5374 the modifications to a bundle (a shelved change), and reverts the
5374 5375 files so that their state in the working directory becomes clean.
5375 5376
5376 5377 To restore these changes to the working directory, using "hg
5377 5378 unshelve"; this will work even if you switch to a different
5378 5379 commit.
5379 5380
5380 5381 When no files are specified, "hg shelve" saves all not-clean
5381 5382 files. If specific files or directories are named, only changes to
5382 5383 those files are shelved.
5383 5384
5384 5385 In bare shelve (when no files are specified, without interactive,
5385 5386 include and exclude option), shelving remembers information if the
5386 5387 working directory was on newly created branch, in other words working
5387 5388 directory was on different branch than its first parent. In this
5388 5389 situation unshelving restores branch information to the working directory.
5389 5390
5390 5391 Each shelved change has a name that makes it easier to find later.
5391 5392 The name of a shelved change defaults to being based on the active
5392 5393 bookmark, or if there is no active bookmark, the current named
5393 5394 branch. To specify a different name, use ``--name``.
5394 5395
5395 5396 To see a list of existing shelved changes, use the ``--list``
5396 5397 option. For each shelved change, this will print its name, age,
5397 5398 and description; use ``--patch`` or ``--stat`` for more details.
5398 5399
5399 5400 To delete specific shelved changes, use ``--delete``. To delete
5400 5401 all shelved changes, use ``--cleanup``.
5401 5402 '''
5402 5403 opts = pycompat.byteskwargs(opts)
5403 5404 allowables = [
5404 5405 ('addremove', {'create'}), # 'create' is pseudo action
5405 5406 ('unknown', {'create'}),
5406 5407 ('cleanup', {'cleanup'}),
5407 5408 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
5408 5409 ('delete', {'delete'}),
5409 5410 ('edit', {'create'}),
5410 5411 ('keep', {'create'}),
5411 5412 ('list', {'list'}),
5412 5413 ('message', {'create'}),
5413 5414 ('name', {'create'}),
5414 5415 ('patch', {'patch', 'list'}),
5415 5416 ('stat', {'stat', 'list'}),
5416 5417 ]
5417 5418 def checkopt(opt):
5418 5419 if opts.get(opt):
5419 5420 for i, allowable in allowables:
5420 5421 if opts[i] and opt not in allowable:
5421 5422 raise error.Abort(_("options '--%s' and '--%s' may not be "
5422 5423 "used together") % (opt, i))
5423 5424 return True
5424 5425 if checkopt('cleanup'):
5425 5426 if pats:
5426 5427 raise error.Abort(_("cannot specify names when using '--cleanup'"))
5427 5428 return shelvemod.cleanupcmd(ui, repo)
5428 5429 elif checkopt('delete'):
5429 5430 return shelvemod.deletecmd(ui, repo, pats)
5430 5431 elif checkopt('list'):
5431 5432 return shelvemod.listcmd(ui, repo, pats, opts)
5432 5433 elif checkopt('patch') or checkopt('stat'):
5433 5434 return shelvemod.patchcmds(ui, repo, pats, opts)
5434 5435 else:
5435 5436 return shelvemod.createcmd(ui, repo, pats, opts)
5436 5437
5437 5438 _NOTTERSE = 'nothing'
5438 5439
5439 5440 @command('status|st',
5440 5441 [('A', 'all', None, _('show status of all files')),
5441 5442 ('m', 'modified', None, _('show only modified files')),
5442 5443 ('a', 'added', None, _('show only added files')),
5443 5444 ('r', 'removed', None, _('show only removed files')),
5444 5445 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5445 5446 ('c', 'clean', None, _('show only files without changes')),
5446 5447 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5447 5448 ('i', 'ignored', None, _('show only ignored files')),
5448 5449 ('n', 'no-status', None, _('hide status prefix')),
5449 5450 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5450 5451 ('C', 'copies', None, _('show source of copied files')),
5451 5452 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5452 5453 ('', 'rev', [], _('show difference from revision'), _('REV')),
5453 5454 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5454 5455 ] + walkopts + subrepoopts + formatteropts,
5455 5456 _('[OPTION]... [FILE]...'),
5456 5457 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5457 5458 helpbasic=True, inferrepo=True,
5458 5459 intents={INTENT_READONLY})
5459 5460 def status(ui, repo, *pats, **opts):
5460 5461 """show changed files in the working directory
5461 5462
5462 5463 Show status of files in the repository. If names are given, only
5463 5464 files that match are shown. Files that are clean or ignored or
5464 5465 the source of a copy/move operation, are not listed unless
5465 5466 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5466 5467 Unless options described with "show only ..." are given, the
5467 5468 options -mardu are used.
5468 5469
5469 5470 Option -q/--quiet hides untracked (unknown and ignored) files
5470 5471 unless explicitly requested with -u/--unknown or -i/--ignored.
5471 5472
5472 5473 .. note::
5473 5474
5474 5475 :hg:`status` may appear to disagree with diff if permissions have
5475 5476 changed or a merge has occurred. The standard diff format does
5476 5477 not report permission changes and diff only reports changes
5477 5478 relative to one merge parent.
5478 5479
5479 5480 If one revision is given, it is used as the base revision.
5480 5481 If two revisions are given, the differences between them are
5481 5482 shown. The --change option can also be used as a shortcut to list
5482 5483 the changed files of a revision from its first parent.
5483 5484
5484 5485 The codes used to show the status of files are::
5485 5486
5486 5487 M = modified
5487 5488 A = added
5488 5489 R = removed
5489 5490 C = clean
5490 5491 ! = missing (deleted by non-hg command, but still tracked)
5491 5492 ? = not tracked
5492 5493 I = ignored
5493 5494 = origin of the previous file (with --copies)
5494 5495
5495 5496 .. container:: verbose
5496 5497
5497 5498 The -t/--terse option abbreviates the output by showing only the directory
5498 5499 name if all the files in it share the same status. The option takes an
5499 5500 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5500 5501 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5501 5502 for 'ignored' and 'c' for clean.
5502 5503
5503 5504 It abbreviates only those statuses which are passed. Note that clean and
5504 5505 ignored files are not displayed with '--terse ic' unless the -c/--clean
5505 5506 and -i/--ignored options are also used.
5506 5507
5507 5508 The -v/--verbose option shows information when the repository is in an
5508 5509 unfinished merge, shelve, rebase state etc. You can have this behavior
5509 5510 turned on by default by enabling the ``commands.status.verbose`` option.
5510 5511
5511 5512 You can skip displaying some of these states by setting
5512 5513 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5513 5514 'histedit', 'merge', 'rebase', or 'unshelve'.
5514 5515
5515 5516 Template:
5516 5517
5517 5518 The following keywords are supported in addition to the common template
5518 5519 keywords and functions. See also :hg:`help templates`.
5519 5520
5520 5521 :path: String. Repository-absolute path of the file.
5521 5522 :source: String. Repository-absolute path of the file originated from.
5522 5523 Available if ``--copies`` is specified.
5523 5524 :status: String. Character denoting file's status.
5524 5525
5525 5526 Examples:
5526 5527
5527 5528 - show changes in the working directory relative to a
5528 5529 changeset::
5529 5530
5530 5531 hg status --rev 9353
5531 5532
5532 5533 - show changes in the working directory relative to the
5533 5534 current directory (see :hg:`help patterns` for more information)::
5534 5535
5535 5536 hg status re:
5536 5537
5537 5538 - show all changes including copies in an existing changeset::
5538 5539
5539 5540 hg status --copies --change 9353
5540 5541
5541 5542 - get a NUL separated list of added files, suitable for xargs::
5542 5543
5543 5544 hg status -an0
5544 5545
5545 5546 - show more information about the repository status, abbreviating
5546 5547 added, removed, modified, deleted, and untracked paths::
5547 5548
5548 5549 hg status -v -t mardu
5549 5550
5550 5551 Returns 0 on success.
5551 5552
5552 5553 """
5553 5554
5554 5555 opts = pycompat.byteskwargs(opts)
5555 5556 revs = opts.get('rev')
5556 5557 change = opts.get('change')
5557 5558 terse = opts.get('terse')
5558 5559 if terse is _NOTTERSE:
5559 5560 if revs:
5560 5561 terse = ''
5561 5562 else:
5562 5563 terse = ui.config('commands', 'status.terse')
5563 5564
5564 5565 if revs and change:
5565 5566 msg = _('cannot specify --rev and --change at the same time')
5566 5567 raise error.Abort(msg)
5567 5568 elif revs and terse:
5568 5569 msg = _('cannot use --terse with --rev')
5569 5570 raise error.Abort(msg)
5570 5571 elif change:
5571 5572 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5572 5573 ctx2 = scmutil.revsingle(repo, change, None)
5573 5574 ctx1 = ctx2.p1()
5574 5575 else:
5575 5576 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5576 5577 ctx1, ctx2 = scmutil.revpair(repo, revs)
5577 5578
5578 5579 forcerelativevalue = None
5579 5580 if ui.hasconfig('commands', 'status.relative'):
5580 5581 forcerelativevalue = ui.configbool('commands', 'status.relative')
5581 5582 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5582 5583 forcerelativevalue=forcerelativevalue)
5583 5584
5584 5585 if opts.get('print0'):
5585 5586 end = '\0'
5586 5587 else:
5587 5588 end = '\n'
5588 5589 copy = {}
5589 5590 states = 'modified added removed deleted unknown ignored clean'.split()
5590 5591 show = [k for k in states if opts.get(k)]
5591 5592 if opts.get('all'):
5592 5593 show += ui.quiet and (states[:4] + ['clean']) or states
5593 5594
5594 5595 if not show:
5595 5596 if ui.quiet:
5596 5597 show = states[:4]
5597 5598 else:
5598 5599 show = states[:5]
5599 5600
5600 5601 m = scmutil.match(ctx2, pats, opts)
5601 5602 if terse:
5602 5603 # we need to compute clean and unknown to terse
5603 5604 stat = repo.status(ctx1.node(), ctx2.node(), m,
5604 5605 'ignored' in show or 'i' in terse,
5605 5606 clean=True, unknown=True,
5606 5607 listsubrepos=opts.get('subrepos'))
5607 5608
5608 5609 stat = cmdutil.tersedir(stat, terse)
5609 5610 else:
5610 5611 stat = repo.status(ctx1.node(), ctx2.node(), m,
5611 5612 'ignored' in show, 'clean' in show,
5612 5613 'unknown' in show, opts.get('subrepos'))
5613 5614
5614 5615 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5615 5616
5616 5617 if (opts.get('all') or opts.get('copies')
5617 5618 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5618 5619 copy = copies.pathcopies(ctx1, ctx2, m)
5619 5620
5620 5621 ui.pager('status')
5621 5622 fm = ui.formatter('status', opts)
5622 5623 fmt = '%s' + end
5623 5624 showchar = not opts.get('no_status')
5624 5625
5625 5626 for state, char, files in changestates:
5626 5627 if state in show:
5627 5628 label = 'status.' + state
5628 5629 for f in files:
5629 5630 fm.startitem()
5630 5631 fm.context(ctx=ctx2)
5631 5632 fm.data(path=f)
5632 5633 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5633 5634 fm.plain(fmt % uipathfn(f), label=label)
5634 5635 if f in copy:
5635 5636 fm.data(source=copy[f])
5636 5637 fm.plain((' %s' + end) % uipathfn(copy[f]),
5637 5638 label='status.copied')
5638 5639
5639 5640 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5640 5641 and not ui.plain()):
5641 5642 cmdutil.morestatus(repo, fm)
5642 5643 fm.end()
5643 5644
5644 5645 @command('summary|sum',
5645 5646 [('', 'remote', None, _('check for push and pull'))],
5646 5647 '[--remote]',
5647 5648 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5648 5649 helpbasic=True,
5649 5650 intents={INTENT_READONLY})
5650 5651 def summary(ui, repo, **opts):
5651 5652 """summarize working directory state
5652 5653
5653 5654 This generates a brief summary of the working directory state,
5654 5655 including parents, branch, commit status, phase and available updates.
5655 5656
5656 5657 With the --remote option, this will check the default paths for
5657 5658 incoming and outgoing changes. This can be time-consuming.
5658 5659
5659 5660 Returns 0 on success.
5660 5661 """
5661 5662
5662 5663 opts = pycompat.byteskwargs(opts)
5663 5664 ui.pager('summary')
5664 5665 ctx = repo[None]
5665 5666 parents = ctx.parents()
5666 5667 pnode = parents[0].node()
5667 5668 marks = []
5668 5669
5669 5670 try:
5670 5671 ms = mergemod.mergestate.read(repo)
5671 5672 except error.UnsupportedMergeRecords as e:
5672 5673 s = ' '.join(e.recordtypes)
5673 5674 ui.warn(
5674 5675 _('warning: merge state has unsupported record types: %s\n') % s)
5675 5676 unresolved = []
5676 5677 else:
5677 5678 unresolved = list(ms.unresolved())
5678 5679
5679 5680 for p in parents:
5680 5681 # label with log.changeset (instead of log.parent) since this
5681 5682 # shows a working directory parent *changeset*:
5682 5683 # i18n: column positioning for "hg summary"
5683 5684 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5684 5685 label=logcmdutil.changesetlabels(p))
5685 5686 ui.write(' '.join(p.tags()), label='log.tag')
5686 5687 if p.bookmarks():
5687 5688 marks.extend(p.bookmarks())
5688 5689 if p.rev() == -1:
5689 5690 if not len(repo):
5690 5691 ui.write(_(' (empty repository)'))
5691 5692 else:
5692 5693 ui.write(_(' (no revision checked out)'))
5693 5694 if p.obsolete():
5694 5695 ui.write(_(' (obsolete)'))
5695 5696 if p.isunstable():
5696 5697 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5697 5698 for instability in p.instabilities())
5698 5699 ui.write(' ('
5699 5700 + ', '.join(instabilities)
5700 5701 + ')')
5701 5702 ui.write('\n')
5702 5703 if p.description():
5703 5704 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5704 5705 label='log.summary')
5705 5706
5706 5707 branch = ctx.branch()
5707 5708 bheads = repo.branchheads(branch)
5708 5709 # i18n: column positioning for "hg summary"
5709 5710 m = _('branch: %s\n') % branch
5710 5711 if branch != 'default':
5711 5712 ui.write(m, label='log.branch')
5712 5713 else:
5713 5714 ui.status(m, label='log.branch')
5714 5715
5715 5716 if marks:
5716 5717 active = repo._activebookmark
5717 5718 # i18n: column positioning for "hg summary"
5718 5719 ui.write(_('bookmarks:'), label='log.bookmark')
5719 5720 if active is not None:
5720 5721 if active in marks:
5721 5722 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5722 5723 marks.remove(active)
5723 5724 else:
5724 5725 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5725 5726 for m in marks:
5726 5727 ui.write(' ' + m, label='log.bookmark')
5727 5728 ui.write('\n', label='log.bookmark')
5728 5729
5729 5730 status = repo.status(unknown=True)
5730 5731
5731 5732 c = repo.dirstate.copies()
5732 5733 copied, renamed = [], []
5733 5734 for d, s in c.iteritems():
5734 5735 if s in status.removed:
5735 5736 status.removed.remove(s)
5736 5737 renamed.append(d)
5737 5738 else:
5738 5739 copied.append(d)
5739 5740 if d in status.added:
5740 5741 status.added.remove(d)
5741 5742
5742 5743 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5743 5744
5744 5745 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5745 5746 (ui.label(_('%d added'), 'status.added'), status.added),
5746 5747 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5747 5748 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5748 5749 (ui.label(_('%d copied'), 'status.copied'), copied),
5749 5750 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5750 5751 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5751 5752 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5752 5753 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5753 5754 t = []
5754 5755 for l, s in labels:
5755 5756 if s:
5756 5757 t.append(l % len(s))
5757 5758
5758 5759 t = ', '.join(t)
5759 5760 cleanworkdir = False
5760 5761
5761 5762 if repo.vfs.exists('graftstate'):
5762 5763 t += _(' (graft in progress)')
5763 5764 if repo.vfs.exists('updatestate'):
5764 5765 t += _(' (interrupted update)')
5765 5766 elif len(parents) > 1:
5766 5767 t += _(' (merge)')
5767 5768 elif branch != parents[0].branch():
5768 5769 t += _(' (new branch)')
5769 5770 elif (parents[0].closesbranch() and
5770 5771 pnode in repo.branchheads(branch, closed=True)):
5771 5772 t += _(' (head closed)')
5772 5773 elif not (status.modified or status.added or status.removed or renamed or
5773 5774 copied or subs):
5774 5775 t += _(' (clean)')
5775 5776 cleanworkdir = True
5776 5777 elif pnode not in bheads:
5777 5778 t += _(' (new branch head)')
5778 5779
5779 5780 if parents:
5780 5781 pendingphase = max(p.phase() for p in parents)
5781 5782 else:
5782 5783 pendingphase = phases.public
5783 5784
5784 5785 if pendingphase > phases.newcommitphase(ui):
5785 5786 t += ' (%s)' % phases.phasenames[pendingphase]
5786 5787
5787 5788 if cleanworkdir:
5788 5789 # i18n: column positioning for "hg summary"
5789 5790 ui.status(_('commit: %s\n') % t.strip())
5790 5791 else:
5791 5792 # i18n: column positioning for "hg summary"
5792 5793 ui.write(_('commit: %s\n') % t.strip())
5793 5794
5794 5795 # all ancestors of branch heads - all ancestors of parent = new csets
5795 5796 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5796 5797 bheads))
5797 5798
5798 5799 if new == 0:
5799 5800 # i18n: column positioning for "hg summary"
5800 5801 ui.status(_('update: (current)\n'))
5801 5802 elif pnode not in bheads:
5802 5803 # i18n: column positioning for "hg summary"
5803 5804 ui.write(_('update: %d new changesets (update)\n') % new)
5804 5805 else:
5805 5806 # i18n: column positioning for "hg summary"
5806 5807 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5807 5808 (new, len(bheads)))
5808 5809
5809 5810 t = []
5810 5811 draft = len(repo.revs('draft()'))
5811 5812 if draft:
5812 5813 t.append(_('%d draft') % draft)
5813 5814 secret = len(repo.revs('secret()'))
5814 5815 if secret:
5815 5816 t.append(_('%d secret') % secret)
5816 5817
5817 5818 if draft or secret:
5818 5819 ui.status(_('phases: %s\n') % ', '.join(t))
5819 5820
5820 5821 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5821 5822 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5822 5823 numtrouble = len(repo.revs(trouble + "()"))
5823 5824 # We write all the possibilities to ease translation
5824 5825 troublemsg = {
5825 5826 "orphan": _("orphan: %d changesets"),
5826 5827 "contentdivergent": _("content-divergent: %d changesets"),
5827 5828 "phasedivergent": _("phase-divergent: %d changesets"),
5828 5829 }
5829 5830 if numtrouble > 0:
5830 5831 ui.status(troublemsg[trouble] % numtrouble + "\n")
5831 5832
5832 5833 cmdutil.summaryhooks(ui, repo)
5833 5834
5834 5835 if opts.get('remote'):
5835 5836 needsincoming, needsoutgoing = True, True
5836 5837 else:
5837 5838 needsincoming, needsoutgoing = False, False
5838 5839 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5839 5840 if i:
5840 5841 needsincoming = True
5841 5842 if o:
5842 5843 needsoutgoing = True
5843 5844 if not needsincoming and not needsoutgoing:
5844 5845 return
5845 5846
5846 5847 def getincoming():
5847 5848 source, branches = hg.parseurl(ui.expandpath('default'))
5848 5849 sbranch = branches[0]
5849 5850 try:
5850 5851 other = hg.peer(repo, {}, source)
5851 5852 except error.RepoError:
5852 5853 if opts.get('remote'):
5853 5854 raise
5854 5855 return source, sbranch, None, None, None
5855 5856 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5856 5857 if revs:
5857 5858 revs = [other.lookup(rev) for rev in revs]
5858 5859 ui.debug('comparing with %s\n' % util.hidepassword(source))
5859 5860 repo.ui.pushbuffer()
5860 5861 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5861 5862 repo.ui.popbuffer()
5862 5863 return source, sbranch, other, commoninc, commoninc[1]
5863 5864
5864 5865 if needsincoming:
5865 5866 source, sbranch, sother, commoninc, incoming = getincoming()
5866 5867 else:
5867 5868 source = sbranch = sother = commoninc = incoming = None
5868 5869
5869 5870 def getoutgoing():
5870 5871 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5871 5872 dbranch = branches[0]
5872 5873 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5873 5874 if source != dest:
5874 5875 try:
5875 5876 dother = hg.peer(repo, {}, dest)
5876 5877 except error.RepoError:
5877 5878 if opts.get('remote'):
5878 5879 raise
5879 5880 return dest, dbranch, None, None
5880 5881 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5881 5882 elif sother is None:
5882 5883 # there is no explicit destination peer, but source one is invalid
5883 5884 return dest, dbranch, None, None
5884 5885 else:
5885 5886 dother = sother
5886 5887 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5887 5888 common = None
5888 5889 else:
5889 5890 common = commoninc
5890 5891 if revs:
5891 5892 revs = [repo.lookup(rev) for rev in revs]
5892 5893 repo.ui.pushbuffer()
5893 5894 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5894 5895 commoninc=common)
5895 5896 repo.ui.popbuffer()
5896 5897 return dest, dbranch, dother, outgoing
5897 5898
5898 5899 if needsoutgoing:
5899 5900 dest, dbranch, dother, outgoing = getoutgoing()
5900 5901 else:
5901 5902 dest = dbranch = dother = outgoing = None
5902 5903
5903 5904 if opts.get('remote'):
5904 5905 t = []
5905 5906 if incoming:
5906 5907 t.append(_('1 or more incoming'))
5907 5908 o = outgoing.missing
5908 5909 if o:
5909 5910 t.append(_('%d outgoing') % len(o))
5910 5911 other = dother or sother
5911 5912 if 'bookmarks' in other.listkeys('namespaces'):
5912 5913 counts = bookmarks.summary(repo, other)
5913 5914 if counts[0] > 0:
5914 5915 t.append(_('%d incoming bookmarks') % counts[0])
5915 5916 if counts[1] > 0:
5916 5917 t.append(_('%d outgoing bookmarks') % counts[1])
5917 5918
5918 5919 if t:
5919 5920 # i18n: column positioning for "hg summary"
5920 5921 ui.write(_('remote: %s\n') % (', '.join(t)))
5921 5922 else:
5922 5923 # i18n: column positioning for "hg summary"
5923 5924 ui.status(_('remote: (synced)\n'))
5924 5925
5925 5926 cmdutil.summaryremotehooks(ui, repo, opts,
5926 5927 ((source, sbranch, sother, commoninc),
5927 5928 (dest, dbranch, dother, outgoing)))
5928 5929
5929 5930 @command('tag',
5930 5931 [('f', 'force', None, _('force tag')),
5931 5932 ('l', 'local', None, _('make the tag local')),
5932 5933 ('r', 'rev', '', _('revision to tag'), _('REV')),
5933 5934 ('', 'remove', None, _('remove a tag')),
5934 5935 # -l/--local is already there, commitopts cannot be used
5935 5936 ('e', 'edit', None, _('invoke editor on commit messages')),
5936 5937 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5937 5938 ] + commitopts2,
5938 5939 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5939 5940 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5940 5941 def tag(ui, repo, name1, *names, **opts):
5941 5942 """add one or more tags for the current or given revision
5942 5943
5943 5944 Name a particular revision using <name>.
5944 5945
5945 5946 Tags are used to name particular revisions of the repository and are
5946 5947 very useful to compare different revisions, to go back to significant
5947 5948 earlier versions or to mark branch points as releases, etc. Changing
5948 5949 an existing tag is normally disallowed; use -f/--force to override.
5949 5950
5950 5951 If no revision is given, the parent of the working directory is
5951 5952 used.
5952 5953
5953 5954 To facilitate version control, distribution, and merging of tags,
5954 5955 they are stored as a file named ".hgtags" which is managed similarly
5955 5956 to other project files and can be hand-edited if necessary. This
5956 5957 also means that tagging creates a new commit. The file
5957 5958 ".hg/localtags" is used for local tags (not shared among
5958 5959 repositories).
5959 5960
5960 5961 Tag commits are usually made at the head of a branch. If the parent
5961 5962 of the working directory is not a branch head, :hg:`tag` aborts; use
5962 5963 -f/--force to force the tag commit to be based on a non-head
5963 5964 changeset.
5964 5965
5965 5966 See :hg:`help dates` for a list of formats valid for -d/--date.
5966 5967
5967 5968 Since tag names have priority over branch names during revision
5968 5969 lookup, using an existing branch name as a tag name is discouraged.
5969 5970
5970 5971 Returns 0 on success.
5971 5972 """
5972 5973 opts = pycompat.byteskwargs(opts)
5973 5974 with repo.wlock(), repo.lock():
5974 5975 rev_ = "."
5975 5976 names = [t.strip() for t in (name1,) + names]
5976 5977 if len(names) != len(set(names)):
5977 5978 raise error.Abort(_('tag names must be unique'))
5978 5979 for n in names:
5979 5980 scmutil.checknewlabel(repo, n, 'tag')
5980 5981 if not n:
5981 5982 raise error.Abort(_('tag names cannot consist entirely of '
5982 5983 'whitespace'))
5983 5984 if opts.get('rev') and opts.get('remove'):
5984 5985 raise error.Abort(_("--rev and --remove are incompatible"))
5985 5986 if opts.get('rev'):
5986 5987 rev_ = opts['rev']
5987 5988 message = opts.get('message')
5988 5989 if opts.get('remove'):
5989 5990 if opts.get('local'):
5990 5991 expectedtype = 'local'
5991 5992 else:
5992 5993 expectedtype = 'global'
5993 5994
5994 5995 for n in names:
5995 5996 if repo.tagtype(n) == 'global':
5996 5997 alltags = tagsmod.findglobaltags(ui, repo)
5997 5998 if alltags[n][0] == nullid:
5998 5999 raise error.Abort(_("tag '%s' is already removed") % n)
5999 6000 if not repo.tagtype(n):
6000 6001 raise error.Abort(_("tag '%s' does not exist") % n)
6001 6002 if repo.tagtype(n) != expectedtype:
6002 6003 if expectedtype == 'global':
6003 6004 raise error.Abort(_("tag '%s' is not a global tag") % n)
6004 6005 else:
6005 6006 raise error.Abort(_("tag '%s' is not a local tag") % n)
6006 6007 rev_ = 'null'
6007 6008 if not message:
6008 6009 # we don't translate commit messages
6009 6010 message = 'Removed tag %s' % ', '.join(names)
6010 6011 elif not opts.get('force'):
6011 6012 for n in names:
6012 6013 if n in repo.tags():
6013 6014 raise error.Abort(_("tag '%s' already exists "
6014 6015 "(use -f to force)") % n)
6015 6016 if not opts.get('local'):
6016 6017 p1, p2 = repo.dirstate.parents()
6017 6018 if p2 != nullid:
6018 6019 raise error.Abort(_('uncommitted merge'))
6019 6020 bheads = repo.branchheads()
6020 6021 if not opts.get('force') and bheads and p1 not in bheads:
6021 6022 raise error.Abort(_('working directory is not at a branch head '
6022 6023 '(use -f to force)'))
6023 6024 node = scmutil.revsingle(repo, rev_).node()
6024 6025
6025 6026 if not message:
6026 6027 # we don't translate commit messages
6027 6028 message = ('Added tag %s for changeset %s' %
6028 6029 (', '.join(names), short(node)))
6029 6030
6030 6031 date = opts.get('date')
6031 6032 if date:
6032 6033 date = dateutil.parsedate(date)
6033 6034
6034 6035 if opts.get('remove'):
6035 6036 editform = 'tag.remove'
6036 6037 else:
6037 6038 editform = 'tag.add'
6038 6039 editor = cmdutil.getcommiteditor(editform=editform,
6039 6040 **pycompat.strkwargs(opts))
6040 6041
6041 6042 # don't allow tagging the null rev
6042 6043 if (not opts.get('remove') and
6043 6044 scmutil.revsingle(repo, rev_).rev() == nullrev):
6044 6045 raise error.Abort(_("cannot tag null revision"))
6045 6046
6046 6047 tagsmod.tag(repo, names, node, message, opts.get('local'),
6047 6048 opts.get('user'), date, editor=editor)
6048 6049
6049 6050 @command(
6050 6051 'tags', formatteropts, '',
6051 6052 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
6052 6053 intents={INTENT_READONLY})
6053 6054 def tags(ui, repo, **opts):
6054 6055 """list repository tags
6055 6056
6056 6057 This lists both regular and local tags. When the -v/--verbose
6057 6058 switch is used, a third column "local" is printed for local tags.
6058 6059 When the -q/--quiet switch is used, only the tag name is printed.
6059 6060
6060 6061 .. container:: verbose
6061 6062
6062 6063 Template:
6063 6064
6064 6065 The following keywords are supported in addition to the common template
6065 6066 keywords and functions such as ``{tag}``. See also
6066 6067 :hg:`help templates`.
6067 6068
6068 6069 :type: String. ``local`` for local tags.
6069 6070
6070 6071 Returns 0 on success.
6071 6072 """
6072 6073
6073 6074 opts = pycompat.byteskwargs(opts)
6074 6075 ui.pager('tags')
6075 6076 fm = ui.formatter('tags', opts)
6076 6077 hexfunc = fm.hexfunc
6077 6078
6078 6079 for t, n in reversed(repo.tagslist()):
6079 6080 hn = hexfunc(n)
6080 6081 label = 'tags.normal'
6081 6082 tagtype = ''
6082 6083 if repo.tagtype(t) == 'local':
6083 6084 label = 'tags.local'
6084 6085 tagtype = 'local'
6085 6086
6086 6087 fm.startitem()
6087 6088 fm.context(repo=repo)
6088 6089 fm.write('tag', '%s', t, label=label)
6089 6090 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
6090 6091 fm.condwrite(not ui.quiet, 'rev node', fmt,
6091 6092 repo.changelog.rev(n), hn, label=label)
6092 6093 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
6093 6094 tagtype, label=label)
6094 6095 fm.plain('\n')
6095 6096 fm.end()
6096 6097
6097 6098 @command('tip',
6098 6099 [('p', 'patch', None, _('show patch')),
6099 6100 ('g', 'git', None, _('use git extended diff format')),
6100 6101 ] + templateopts,
6101 6102 _('[-p] [-g]'),
6102 6103 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
6103 6104 def tip(ui, repo, **opts):
6104 6105 """show the tip revision (DEPRECATED)
6105 6106
6106 6107 The tip revision (usually just called the tip) is the changeset
6107 6108 most recently added to the repository (and therefore the most
6108 6109 recently changed head).
6109 6110
6110 6111 If you have just made a commit, that commit will be the tip. If
6111 6112 you have just pulled changes from another repository, the tip of
6112 6113 that repository becomes the current tip. The "tip" tag is special
6113 6114 and cannot be renamed or assigned to a different changeset.
6114 6115
6115 6116 This command is deprecated, please use :hg:`heads` instead.
6116 6117
6117 6118 Returns 0 on success.
6118 6119 """
6119 6120 opts = pycompat.byteskwargs(opts)
6120 6121 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
6121 6122 displayer.show(repo['tip'])
6122 6123 displayer.close()
6123 6124
6124 6125 @command('unbundle',
6125 6126 [('u', 'update', None,
6126 6127 _('update to new branch head if changesets were unbundled'))],
6127 6128 _('[-u] FILE...'),
6128 6129 helpcategory=command.CATEGORY_IMPORT_EXPORT)
6129 6130 def unbundle(ui, repo, fname1, *fnames, **opts):
6130 6131 """apply one or more bundle files
6131 6132
6132 6133 Apply one or more bundle files generated by :hg:`bundle`.
6133 6134
6134 6135 Returns 0 on success, 1 if an update has unresolved files.
6135 6136 """
6136 6137 fnames = (fname1,) + fnames
6137 6138
6138 6139 with repo.lock():
6139 6140 for fname in fnames:
6140 6141 f = hg.openpath(ui, fname)
6141 6142 gen = exchange.readbundle(ui, f, fname)
6142 6143 if isinstance(gen, streamclone.streamcloneapplier):
6143 6144 raise error.Abort(
6144 6145 _('packed bundles cannot be applied with '
6145 6146 '"hg unbundle"'),
6146 6147 hint=_('use "hg debugapplystreamclonebundle"'))
6147 6148 url = 'bundle:' + fname
6148 6149 try:
6149 6150 txnname = 'unbundle'
6150 6151 if not isinstance(gen, bundle2.unbundle20):
6151 6152 txnname = 'unbundle\n%s' % util.hidepassword(url)
6152 6153 with repo.transaction(txnname) as tr:
6153 6154 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6154 6155 url=url)
6155 6156 except error.BundleUnknownFeatureError as exc:
6156 6157 raise error.Abort(
6157 6158 _('%s: unknown bundle feature, %s') % (fname, exc),
6158 6159 hint=_("see https://mercurial-scm.org/"
6159 6160 "wiki/BundleFeature for more "
6160 6161 "information"))
6161 6162 modheads = bundle2.combinechangegroupresults(op)
6162 6163
6163 6164 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6164 6165
6165 6166 @command('unshelve',
6166 6167 [('a', 'abort', None,
6167 6168 _('abort an incomplete unshelve operation')),
6168 6169 ('c', 'continue', None,
6169 6170 _('continue an incomplete unshelve operation')),
6170 6171 ('i', 'interactive', None,
6171 6172 _('use interactive mode (EXPERIMENTAL)')),
6172 6173 ('k', 'keep', None,
6173 6174 _('keep shelve after unshelving')),
6174 6175 ('n', 'name', '',
6175 6176 _('restore shelved change with given name'), _('NAME')),
6176 6177 ('t', 'tool', '', _('specify merge tool')),
6177 6178 ('', 'date', '',
6178 6179 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
6179 6180 _('hg unshelve [OPTION]... [FILE]... [-n SHELVED]'),
6180 6181 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
6181 6182 def unshelve(ui, repo, *shelved, **opts):
6182 6183 """restore a shelved change to the working directory
6183 6184
6184 6185 This command accepts an optional name of a shelved change to
6185 6186 restore. If none is given, the most recent shelved change is used.
6186 6187
6187 6188 If a shelved change is applied successfully, the bundle that
6188 6189 contains the shelved changes is moved to a backup location
6189 6190 (.hg/shelve-backup).
6190 6191
6191 6192 Since you can restore a shelved change on top of an arbitrary
6192 6193 commit, it is possible that unshelving will result in a conflict
6193 6194 between your changes and the commits you are unshelving onto. If
6194 6195 this occurs, you must resolve the conflict, then use
6195 6196 ``--continue`` to complete the unshelve operation. (The bundle
6196 6197 will not be moved until you successfully complete the unshelve.)
6197 6198
6198 6199 (Alternatively, you can use ``--abort`` to abandon an unshelve
6199 6200 that causes a conflict. This reverts the unshelved changes, and
6200 6201 leaves the bundle in place.)
6201 6202
6202 6203 If bare shelved change (when no files are specified, without interactive,
6203 6204 include and exclude option) was done on newly created branch it would
6204 6205 restore branch information to the working directory.
6205 6206
6206 6207 After a successful unshelve, the shelved changes are stored in a
6207 6208 backup directory. Only the N most recent backups are kept. N
6208 6209 defaults to 10 but can be overridden using the ``shelve.maxbackups``
6209 6210 configuration option.
6210 6211
6211 6212 .. container:: verbose
6212 6213
6213 6214 Timestamp in seconds is used to decide order of backups. More
6214 6215 than ``maxbackups`` backups are kept, if same timestamp
6215 6216 prevents from deciding exact order of them, for safety.
6216 6217
6217 6218 Selected changes can be unshelved with ``--interactive`` flag.
6218 6219 The working directory is updated with the selected changes, and
6219 6220 only the unselected changes remain shelved.
6220 6221 Note: The whole shelve is applied to working directory first before
6221 6222 running interactively. So, this will bring up all the conflicts between
6222 6223 working directory and the shelve, irrespective of which changes will be
6223 6224 unshelved.
6224 6225 """
6225 6226 with repo.wlock():
6226 6227 return shelvemod.dounshelve(ui, repo, *shelved, **opts)
6227 6228
6228 6229 statemod.addunfinished(
6229 6230 'unshelve', fname='shelvedstate', continueflag=True,
6230 6231 abortfunc=shelvemod.hgabortunshelve,
6231 6232 continuefunc=shelvemod.hgcontinueunshelve,
6232 6233 cmdmsg=_('unshelve already in progress'),
6233 6234 )
6234 6235
6235 6236 @command('update|up|checkout|co',
6236 6237 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6237 6238 ('c', 'check', None, _('require clean working directory')),
6238 6239 ('m', 'merge', None, _('merge uncommitted changes')),
6239 6240 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6240 6241 ('r', 'rev', '', _('revision'), _('REV'))
6241 6242 ] + mergetoolopts,
6242 6243 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6243 6244 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6244 6245 helpbasic=True)
6245 6246 def update(ui, repo, node=None, **opts):
6246 6247 """update working directory (or switch revisions)
6247 6248
6248 6249 Update the repository's working directory to the specified
6249 6250 changeset. If no changeset is specified, update to the tip of the
6250 6251 current named branch and move the active bookmark (see :hg:`help
6251 6252 bookmarks`).
6252 6253
6253 6254 Update sets the working directory's parent revision to the specified
6254 6255 changeset (see :hg:`help parents`).
6255 6256
6256 6257 If the changeset is not a descendant or ancestor of the working
6257 6258 directory's parent and there are uncommitted changes, the update is
6258 6259 aborted. With the -c/--check option, the working directory is checked
6259 6260 for uncommitted changes; if none are found, the working directory is
6260 6261 updated to the specified changeset.
6261 6262
6262 6263 .. container:: verbose
6263 6264
6264 6265 The -C/--clean, -c/--check, and -m/--merge options control what
6265 6266 happens if the working directory contains uncommitted changes.
6266 6267 At most of one of them can be specified.
6267 6268
6268 6269 1. If no option is specified, and if
6269 6270 the requested changeset is an ancestor or descendant of
6270 6271 the working directory's parent, the uncommitted changes
6271 6272 are merged into the requested changeset and the merged
6272 6273 result is left uncommitted. If the requested changeset is
6273 6274 not an ancestor or descendant (that is, it is on another
6274 6275 branch), the update is aborted and the uncommitted changes
6275 6276 are preserved.
6276 6277
6277 6278 2. With the -m/--merge option, the update is allowed even if the
6278 6279 requested changeset is not an ancestor or descendant of
6279 6280 the working directory's parent.
6280 6281
6281 6282 3. With the -c/--check option, the update is aborted and the
6282 6283 uncommitted changes are preserved.
6283 6284
6284 6285 4. With the -C/--clean option, uncommitted changes are discarded and
6285 6286 the working directory is updated to the requested changeset.
6286 6287
6287 6288 To cancel an uncommitted merge (and lose your changes), use
6288 6289 :hg:`merge --abort`.
6289 6290
6290 6291 Use null as the changeset to remove the working directory (like
6291 6292 :hg:`clone -U`).
6292 6293
6293 6294 If you want to revert just one file to an older revision, use
6294 6295 :hg:`revert [-r REV] NAME`.
6295 6296
6296 6297 See :hg:`help dates` for a list of formats valid for -d/--date.
6297 6298
6298 6299 Returns 0 on success, 1 if there are unresolved files.
6299 6300 """
6300 6301 rev = opts.get(r'rev')
6301 6302 date = opts.get(r'date')
6302 6303 clean = opts.get(r'clean')
6303 6304 check = opts.get(r'check')
6304 6305 merge = opts.get(r'merge')
6305 6306 if rev and node:
6306 6307 raise error.Abort(_("please specify just one revision"))
6307 6308
6308 6309 if ui.configbool('commands', 'update.requiredest'):
6309 6310 if not node and not rev and not date:
6310 6311 raise error.Abort(_('you must specify a destination'),
6311 6312 hint=_('for example: hg update ".::"'))
6312 6313
6313 6314 if rev is None or rev == '':
6314 6315 rev = node
6315 6316
6316 6317 if date and rev is not None:
6317 6318 raise error.Abort(_("you can't specify a revision and a date"))
6318 6319
6319 6320 if len([x for x in (clean, check, merge) if x]) > 1:
6320 6321 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6321 6322 "or -m/--merge"))
6322 6323
6323 6324 updatecheck = None
6324 6325 if check:
6325 6326 updatecheck = 'abort'
6326 6327 elif merge:
6327 6328 updatecheck = 'none'
6328 6329
6329 6330 with repo.wlock():
6330 6331 cmdutil.clearunfinished(repo)
6331 6332 if date:
6332 6333 rev = cmdutil.finddate(ui, repo, date)
6333 6334
6334 6335 # if we defined a bookmark, we have to remember the original name
6335 6336 brev = rev
6336 6337 if rev:
6337 6338 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6338 6339 ctx = scmutil.revsingle(repo, rev, default=None)
6339 6340 rev = ctx.rev()
6340 6341 hidden = ctx.hidden()
6341 6342 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6342 6343 with ui.configoverride(overrides, 'update'):
6343 6344 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6344 6345 updatecheck=updatecheck)
6345 6346 if hidden:
6346 6347 ctxstr = ctx.hex()[:12]
6347 6348 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6348 6349
6349 6350 if ctx.obsolete():
6350 6351 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6351 6352 ui.warn("(%s)\n" % obsfatemsg)
6352 6353 return ret
6353 6354
6354 6355 @command('verify',
6355 6356 [('', 'full', False, 'perform more checks (EXPERIMENTAL)')],
6356 6357 helpcategory=command.CATEGORY_MAINTENANCE)
6357 6358 def verify(ui, repo, **opts):
6358 6359 """verify the integrity of the repository
6359 6360
6360 6361 Verify the integrity of the current repository.
6361 6362
6362 6363 This will perform an extensive check of the repository's
6363 6364 integrity, validating the hashes and checksums of each entry in
6364 6365 the changelog, manifest, and tracked files, as well as the
6365 6366 integrity of their crosslinks and indices.
6366 6367
6367 6368 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6368 6369 for more information about recovery from corruption of the
6369 6370 repository.
6370 6371
6371 6372 Returns 0 on success, 1 if errors are encountered.
6372 6373 """
6373 6374 opts = pycompat.byteskwargs(opts)
6374 6375
6375 6376 level = None
6376 6377 if opts['full']:
6377 6378 level = verifymod.VERIFY_FULL
6378 6379 return hg.verify(repo, level)
6379 6380
6380 6381 @command(
6381 6382 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6382 6383 norepo=True, intents={INTENT_READONLY})
6383 6384 def version_(ui, **opts):
6384 6385 """output version and copyright information
6385 6386
6386 6387 .. container:: verbose
6387 6388
6388 6389 Template:
6389 6390
6390 6391 The following keywords are supported. See also :hg:`help templates`.
6391 6392
6392 6393 :extensions: List of extensions.
6393 6394 :ver: String. Version number.
6394 6395
6395 6396 And each entry of ``{extensions}`` provides the following sub-keywords
6396 6397 in addition to ``{ver}``.
6397 6398
6398 6399 :bundled: Boolean. True if included in the release.
6399 6400 :name: String. Extension name.
6400 6401 """
6401 6402 opts = pycompat.byteskwargs(opts)
6402 6403 if ui.verbose:
6403 6404 ui.pager('version')
6404 6405 fm = ui.formatter("version", opts)
6405 6406 fm.startitem()
6406 6407 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6407 6408 util.version())
6408 6409 license = _(
6409 6410 "(see https://mercurial-scm.org for more information)\n"
6410 6411 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6411 6412 "This is free software; see the source for copying conditions. "
6412 6413 "There is NO\nwarranty; "
6413 6414 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6414 6415 )
6415 6416 if not ui.quiet:
6416 6417 fm.plain(license)
6417 6418
6418 6419 if ui.verbose:
6419 6420 fm.plain(_("\nEnabled extensions:\n\n"))
6420 6421 # format names and versions into columns
6421 6422 names = []
6422 6423 vers = []
6423 6424 isinternals = []
6424 6425 for name, module in extensions.extensions():
6425 6426 names.append(name)
6426 6427 vers.append(extensions.moduleversion(module) or None)
6427 6428 isinternals.append(extensions.ismoduleinternal(module))
6428 6429 fn = fm.nested("extensions", tmpl='{name}\n')
6429 6430 if names:
6430 6431 namefmt = " %%-%ds " % max(len(n) for n in names)
6431 6432 places = [_("external"), _("internal")]
6432 6433 for n, v, p in zip(names, vers, isinternals):
6433 6434 fn.startitem()
6434 6435 fn.condwrite(ui.verbose, "name", namefmt, n)
6435 6436 if ui.verbose:
6436 6437 fn.plain("%s " % places[p])
6437 6438 fn.data(bundled=p)
6438 6439 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6439 6440 if ui.verbose:
6440 6441 fn.plain("\n")
6441 6442 fn.end()
6442 6443 fm.end()
6443 6444
6444 6445 def loadcmdtable(ui, name, cmdtable):
6445 6446 """Load command functions from specified cmdtable
6446 6447 """
6447 6448 overrides = [cmd for cmd in cmdtable if cmd in table]
6448 6449 if overrides:
6449 6450 ui.warn(_("extension '%s' overrides commands: %s\n")
6450 6451 % (name, " ".join(overrides)))
6451 6452 table.update(cmdtable)
@@ -1,609 +1,622 b''
1 1 #require serve
2 2
3 3 $ hg init test
4 4 $ cd test
5 5 $ echo foo>foo
6 6 $ hg commit -Am 1 -d '1 0'
7 7 adding foo
8 8 $ echo bar>bar
9 9 $ hg commit -Am 2 -d '2 0'
10 10 adding bar
11 11 $ mkdir baz
12 12 $ echo bletch>baz/bletch
13 13 $ hg commit -Am 3 -d '1000000000 0'
14 14 adding baz/bletch
15 15 $ hg init subrepo
16 16 $ touch subrepo/sub
17 17 $ hg -q -R subrepo ci -Am "init subrepo"
18 18 $ echo "subrepo = subrepo" > .hgsub
19 19 $ hg add .hgsub
20 20 $ hg ci -m "add subrepo"
21 21
22 22 $ cat >> $HGRCPATH <<EOF
23 23 > [extensions]
24 24 > share =
25 25 > EOF
26 26
27 27 hg subrepos are shared when the parent repo is shared
28 28
29 29 $ cd ..
30 30 $ hg share test shared1
31 31 updating working directory
32 32 sharing subrepo subrepo from $TESTTMP/test/subrepo
33 33 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 34 $ cat shared1/subrepo/.hg/sharedpath
35 35 $TESTTMP/test/subrepo/.hg (no-eol)
36 36
37 37 hg subrepos are shared into existence on demand if the parent was shared
38 38
39 39 $ hg clone -qr 1 test clone1
40 40 $ hg share clone1 share2
41 41 updating working directory
42 42 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 43 $ hg -R clone1 -q pull
44 44 $ hg -R share2 update tip
45 45 sharing subrepo subrepo from $TESTTMP/test/subrepo
46 46 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
47 47 $ cat share2/subrepo/.hg/sharedpath
48 48 $TESTTMP/test/subrepo/.hg (no-eol)
49 49 $ echo 'mod' > share2/subrepo/sub
50 50 $ hg -R share2 ci -Sqm 'subrepo mod'
51 51 $ hg -R clone1 update -C tip
52 52 cloning subrepo subrepo from $TESTTMP/test/subrepo
53 53 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
54 54 $ find share2 | egrep 'sharedpath|00.+\.i' | sort
55 55 share2/.hg/sharedpath
56 56 share2/subrepo/.hg/sharedpath
57 57 $ hg -R share2 unshare
58 58 unsharing subrepo 'subrepo'
59 59 $ find share2 | egrep 'sharedpath|00.+\.i' | sort
60 60 share2/.hg/00changelog.i
61 61 share2/.hg/sharedpath.old
62 62 share2/.hg/store/00changelog.i
63 63 share2/.hg/store/00manifest.i
64 64 share2/subrepo/.hg/00changelog.i
65 65 share2/subrepo/.hg/sharedpath.old
66 66 share2/subrepo/.hg/store/00changelog.i
67 67 share2/subrepo/.hg/store/00manifest.i
68 68 $ hg -R share2/subrepo log -r tip -T compact
69 69 1[tip] 559dcc9bfa65 1970-01-01 00:00 +0000 test
70 70 subrepo mod
71 71
72 72 $ rm -rf clone1
73 73
74 74 $ hg clone -qr 1 test clone1
75 75 $ hg share clone1 shared3
76 76 updating working directory
77 77 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
78 78 $ hg -R clone1 -q pull
79 79 $ hg -R shared3 archive --config ui.archivemeta=False -r tip -S archive
80 80 sharing subrepo subrepo from $TESTTMP/test/subrepo
81 81 $ cat shared3/subrepo/.hg/sharedpath
82 82 $TESTTMP/test/subrepo/.hg (no-eol)
83 83 $ diff -r archive test
84 84 Only in test: .hg
85 85 Common subdirectories: archive/baz and test/baz (?)
86 86 Common subdirectories: archive/subrepo and test/subrepo (?)
87 87 Only in test/subrepo: .hg
88 88 [1]
89 89 $ rm -rf archive
90 90
91 91 $ cd test
92 92 $ echo "[web]" >> .hg/hgrc
93 93 $ echo "name = test-archive" >> .hg/hgrc
94 94 $ echo "archivesubrepos = True" >> .hg/hgrc
95 95 $ cp .hg/hgrc .hg/hgrc-base
96 96 > test_archtype() {
97 97 > echo "allow-archive = $1" >> .hg/hgrc
98 98 > test_archtype_run "$@"
99 99 > }
100 100 > test_archtype_deprecated() {
101 101 > echo "allow$1 = True" >> .hg/hgrc
102 102 > test_archtype_run "$@"
103 103 > }
104 104 > test_archtype_run() {
105 105 > hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log \
106 106 > --config extensions.blackbox= --config blackbox.track=develwarn
107 107 > cat hg.pid >> $DAEMON_PIDS
108 108 > echo % $1 allowed should give 200
109 109 > get-with-headers.py --bodyfile body localhost:$HGPORT "archive/tip.$2" -
110 110 > f --size --sha1 body
111 111 > echo % $3 and $4 disallowed should both give 403
112 112 > get-with-headers.py --bodyfile body localhost:$HGPORT "archive/tip.$3" -
113 113 > f --size --sha1 body
114 114 > get-with-headers.py --bodyfile body localhost:$HGPORT "archive/tip.$4" -
115 115 > f --size --sha1 body
116 116 > killdaemons.py
117 117 > cat errors.log
118 118 > hg blackbox --config extensions.blackbox= --config blackbox.track=
119 119 > cp .hg/hgrc-base .hg/hgrc
120 120 > }
121 121
122 122 check http return codes
123 123
124 124 $ test_archtype gz tar.gz tar.bz2 zip
125 125 % gz allowed should give 200
126 126 200 Script output follows
127 127 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.gz
128 128 content-type: application/x-gzip
129 129 date: $HTTP_DATE$
130 130 etag: W/"*" (glob)
131 131 server: testing stub value
132 132 transfer-encoding: chunked
133 133
134 134 body: size=408, sha1=8fa06531bddecc365a9f5edb0f88b65974bfe505
135 135 % tar.bz2 and zip disallowed should both give 403
136 136 403 Archive type not allowed: bz2
137 137 content-type: text/html; charset=ascii
138 138 date: $HTTP_DATE$
139 139 etag: W/"*" (glob)
140 140 server: testing stub value
141 141 transfer-encoding: chunked
142 142
143 143 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
144 144 403 Archive type not allowed: zip
145 145 content-type: text/html; charset=ascii
146 146 date: $HTTP_DATE$
147 147 etag: W/"*" (glob)
148 148 server: testing stub value
149 149 transfer-encoding: chunked
150 150
151 151 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
152 152 $ test_archtype bz2 tar.bz2 zip tar.gz
153 153 % bz2 allowed should give 200
154 154 200 Script output follows
155 155 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.bz2
156 156 content-type: application/x-bzip2
157 157 date: $HTTP_DATE$
158 158 etag: W/"*" (glob)
159 159 server: testing stub value
160 160 transfer-encoding: chunked
161 161
162 162 body: size=426, sha1=8d87f5aba6e14f1bfea6c232985982c278b2fb0b
163 163 % zip and tar.gz disallowed should both give 403
164 164 403 Archive type not allowed: zip
165 165 content-type: text/html; charset=ascii
166 166 date: $HTTP_DATE$
167 167 etag: W/"*" (glob)
168 168 server: testing stub value
169 169 transfer-encoding: chunked
170 170
171 171 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
172 172 403 Archive type not allowed: gz
173 173 content-type: text/html; charset=ascii
174 174 date: $HTTP_DATE$
175 175 etag: W/"*" (glob)
176 176 server: testing stub value
177 177 transfer-encoding: chunked
178 178
179 179 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
180 180 $ test_archtype zip zip tar.gz tar.bz2
181 181 % zip allowed should give 200
182 182 200 Script output follows
183 183 content-disposition: attachment; filename=test-archive-1701ef1f1510.zip
184 184 content-type: application/zip
185 185 date: $HTTP_DATE$
186 186 etag: W/"*" (glob)
187 187 server: testing stub value
188 188 transfer-encoding: chunked
189 189
190 190 body: size=(1377|1461|1489), sha1=(677b14d3d048778d5eb5552c14a67e6192068650|be6d3983aa13dfe930361b2569291cdedd02b537|1897e496871aa89ad685a92b936f5fa0d008b9e8) (re)
191 191 % tar.gz and tar.bz2 disallowed should both give 403
192 192 403 Archive type not allowed: gz
193 193 content-type: text/html; charset=ascii
194 194 date: $HTTP_DATE$
195 195 etag: W/"*" (glob)
196 196 server: testing stub value
197 197 transfer-encoding: chunked
198 198
199 199 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
200 200 403 Archive type not allowed: bz2
201 201 content-type: text/html; charset=ascii
202 202 date: $HTTP_DATE$
203 203 etag: W/"*" (glob)
204 204 server: testing stub value
205 205 transfer-encoding: chunked
206 206
207 207 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
208 208
209 209 check http return codes (with deprecated option)
210 210
211 211 $ test_archtype_deprecated gz tar.gz tar.bz2 zip
212 212 % gz allowed should give 200
213 213 200 Script output follows
214 214 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.gz
215 215 content-type: application/x-gzip
216 216 date: $HTTP_DATE$
217 217 etag: W/"*" (glob)
218 218 server: testing stub value
219 219 transfer-encoding: chunked
220 220
221 221 body: size=408, sha1=8fa06531bddecc365a9f5edb0f88b65974bfe505
222 222 % tar.bz2 and zip disallowed should both give 403
223 223 403 Archive type not allowed: bz2
224 224 content-type: text/html; charset=ascii
225 225 date: $HTTP_DATE$
226 226 etag: W/"*" (glob)
227 227 server: testing stub value
228 228 transfer-encoding: chunked
229 229
230 230 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
231 231 403 Archive type not allowed: zip
232 232 content-type: text/html; charset=ascii
233 233 date: $HTTP_DATE$
234 234 etag: W/"*" (glob)
235 235 server: testing stub value
236 236 transfer-encoding: chunked
237 237
238 238 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
239 239 $ test_archtype_deprecated bz2 tar.bz2 zip tar.gz
240 240 % bz2 allowed should give 200
241 241 200 Script output follows
242 242 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.bz2
243 243 content-type: application/x-bzip2
244 244 date: $HTTP_DATE$
245 245 etag: W/"*" (glob)
246 246 server: testing stub value
247 247 transfer-encoding: chunked
248 248
249 249 body: size=426, sha1=8d87f5aba6e14f1bfea6c232985982c278b2fb0b
250 250 % zip and tar.gz disallowed should both give 403
251 251 403 Archive type not allowed: zip
252 252 content-type: text/html; charset=ascii
253 253 date: $HTTP_DATE$
254 254 etag: W/"*" (glob)
255 255 server: testing stub value
256 256 transfer-encoding: chunked
257 257
258 258 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
259 259 403 Archive type not allowed: gz
260 260 content-type: text/html; charset=ascii
261 261 date: $HTTP_DATE$
262 262 etag: W/"*" (glob)
263 263 server: testing stub value
264 264 transfer-encoding: chunked
265 265
266 266 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
267 267 $ test_archtype_deprecated zip zip tar.gz tar.bz2
268 268 % zip allowed should give 200
269 269 200 Script output follows
270 270 content-disposition: attachment; filename=test-archive-1701ef1f1510.zip
271 271 content-type: application/zip
272 272 date: $HTTP_DATE$
273 273 etag: W/"*" (glob)
274 274 server: testing stub value
275 275 transfer-encoding: chunked
276 276
277 277 body: size=(1377|1461|1489), sha1=(677b14d3d048778d5eb5552c14a67e6192068650|be6d3983aa13dfe930361b2569291cdedd02b537|1897e496871aa89ad685a92b936f5fa0d008b9e8) (re)
278 278 % tar.gz and tar.bz2 disallowed should both give 403
279 279 403 Archive type not allowed: gz
280 280 content-type: text/html; charset=ascii
281 281 date: $HTTP_DATE$
282 282 etag: W/"*" (glob)
283 283 server: testing stub value
284 284 transfer-encoding: chunked
285 285
286 286 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
287 287 403 Archive type not allowed: bz2
288 288 content-type: text/html; charset=ascii
289 289 date: $HTTP_DATE$
290 290 etag: W/"*" (glob)
291 291 server: testing stub value
292 292 transfer-encoding: chunked
293 293
294 294 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
295 295
296 296 $ echo "allow-archive = gz bz2 zip" >> .hg/hgrc
297 297 $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
298 298 $ cat hg.pid >> $DAEMON_PIDS
299 299
300 300 check archive links' order
301 301
302 302 $ get-with-headers.py localhost:$HGPORT "?revcount=1" | grep '/archive/tip.'
303 303 <a href="/archive/tip.zip">zip</a>
304 304 <a href="/archive/tip.tar.gz">gz</a>
305 305 <a href="/archive/tip.tar.bz2">bz2</a>
306 306
307 307 invalid arch type should give 404
308 308
309 309 $ get-with-headers.py localhost:$HGPORT "archive/tip.invalid" | head -n 1
310 310 404 Unsupported archive type: None
311 311
312 312 $ TIP=`hg id -v | cut -f1 -d' '`
313 313 $ QTIP=`hg id -q`
314 314 $ cat > getarchive.py <<EOF
315 315 > from __future__ import absolute_import
316 316 > import os
317 317 > import sys
318 318 > from mercurial import (
319 319 > util,
320 320 > )
321 321 > try:
322 322 > # Set stdout to binary mode for win32 platforms
323 323 > import msvcrt
324 324 > msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
325 325 > except ImportError:
326 326 > pass
327 327 > if len(sys.argv) <= 3:
328 328 > node, archive = sys.argv[1:]
329 329 > requeststr = 'cmd=archive;node=%s;type=%s' % (node, archive)
330 330 > else:
331 331 > node, archive, file = sys.argv[1:]
332 332 > requeststr = 'cmd=archive;node=%s;type=%s;file=%s' % (node, archive, file)
333 333 > try:
334 334 > stdout = sys.stdout.buffer
335 335 > except AttributeError:
336 336 > stdout = sys.stdout
337 337 > try:
338 338 > f = util.urlreq.urlopen('http://$LOCALIP:%s/?%s'
339 339 > % (os.environ['HGPORT'], requeststr))
340 340 > stdout.write(f.read())
341 341 > except util.urlerr.httperror as e:
342 342 > sys.stderr.write(str(e) + '\n')
343 343 > EOF
344 344 $ "$PYTHON" getarchive.py "$TIP" gz | gunzip | tar tf - 2>/dev/null
345 345 test-archive-1701ef1f1510/.hg_archival.txt
346 346 test-archive-1701ef1f1510/.hgsub
347 347 test-archive-1701ef1f1510/.hgsubstate
348 348 test-archive-1701ef1f1510/bar
349 349 test-archive-1701ef1f1510/baz/bletch
350 350 test-archive-1701ef1f1510/foo
351 351 test-archive-1701ef1f1510/subrepo/sub
352 352 $ "$PYTHON" getarchive.py "$TIP" bz2 | bunzip2 | tar tf - 2>/dev/null
353 353 test-archive-1701ef1f1510/.hg_archival.txt
354 354 test-archive-1701ef1f1510/.hgsub
355 355 test-archive-1701ef1f1510/.hgsubstate
356 356 test-archive-1701ef1f1510/bar
357 357 test-archive-1701ef1f1510/baz/bletch
358 358 test-archive-1701ef1f1510/foo
359 359 test-archive-1701ef1f1510/subrepo/sub
360 360 $ "$PYTHON" getarchive.py "$TIP" zip > archive.zip
361 361 $ unzip -t archive.zip
362 362 Archive: archive.zip
363 363 testing: test-archive-1701ef1f1510/.hg_archival.txt*OK (glob)
364 364 testing: test-archive-1701ef1f1510/.hgsub*OK (glob)
365 365 testing: test-archive-1701ef1f1510/.hgsubstate*OK (glob)
366 366 testing: test-archive-1701ef1f1510/bar*OK (glob)
367 367 testing: test-archive-1701ef1f1510/baz/bletch*OK (glob)
368 368 testing: test-archive-1701ef1f1510/foo*OK (glob)
369 369 testing: test-archive-1701ef1f1510/subrepo/sub*OK (glob)
370 370 No errors detected in compressed data of archive.zip.
371 371
372 372 test that we can download single directories and files
373 373
374 374 $ "$PYTHON" getarchive.py "$TIP" gz baz | gunzip | tar tf - 2>/dev/null
375 375 test-archive-1701ef1f1510/baz/bletch
376 376 $ "$PYTHON" getarchive.py "$TIP" gz foo | gunzip | tar tf - 2>/dev/null
377 377 test-archive-1701ef1f1510/foo
378 378
379 379 test that we detect file patterns that match no files
380 380
381 381 $ "$PYTHON" getarchive.py "$TIP" gz foobar
382 382 HTTP Error 404: file(s) not found: foobar
383 383
384 384 test that we reject unsafe patterns
385 385
386 386 $ "$PYTHON" getarchive.py "$TIP" gz relre:baz
387 387 HTTP Error 404: file(s) not found: relre:baz
388 388
389 389 $ killdaemons.py
390 390
391 391 $ hg archive -t tar test.tar
392 392 $ tar tf test.tar
393 393 test/.hg_archival.txt
394 394 test/.hgsub
395 395 test/.hgsubstate
396 396 test/bar
397 397 test/baz/bletch
398 398 test/foo
399 399
400 400 $ hg archive --debug -t tbz2 -X baz test.tar.bz2 --config progress.debug=true
401 401 archiving: 0/4 files (0.00%)
402 402 archiving: .hgsub 1/4 files (25.00%)
403 403 archiving: .hgsubstate 2/4 files (50.00%)
404 404 archiving: bar 3/4 files (75.00%)
405 405 archiving: foo 4/4 files (100.00%)
406 406 $ bunzip2 -dc test.tar.bz2 | tar tf - 2>/dev/null
407 407 test/.hg_archival.txt
408 408 test/.hgsub
409 409 test/.hgsubstate
410 410 test/bar
411 411 test/foo
412 412
413 413 $ hg archive -t tgz -p %b-%h test-%h.tar.gz
414 414 $ gzip -dc test-$QTIP.tar.gz | tar tf - 2>/dev/null
415 415 test-1701ef1f1510/.hg_archival.txt
416 416 test-1701ef1f1510/.hgsub
417 417 test-1701ef1f1510/.hgsubstate
418 418 test-1701ef1f1510/bar
419 419 test-1701ef1f1510/baz/bletch
420 420 test-1701ef1f1510/foo
421 421
422 422 $ hg archive autodetected_test.tar
423 423 $ tar tf autodetected_test.tar
424 424 autodetected_test/.hg_archival.txt
425 425 autodetected_test/.hgsub
426 426 autodetected_test/.hgsubstate
427 427 autodetected_test/bar
428 428 autodetected_test/baz/bletch
429 429 autodetected_test/foo
430 430
431 431 The '-t' should override autodetection
432 432
433 433 $ hg archive -t tar autodetect_override_test.zip
434 434 $ tar tf autodetect_override_test.zip
435 435 autodetect_override_test.zip/.hg_archival.txt
436 436 autodetect_override_test.zip/.hgsub
437 437 autodetect_override_test.zip/.hgsubstate
438 438 autodetect_override_test.zip/bar
439 439 autodetect_override_test.zip/baz/bletch
440 440 autodetect_override_test.zip/foo
441 441
442 442 $ for ext in tar tar.gz tgz tar.bz2 tbz2 zip; do
443 443 > hg archive auto_test.$ext
444 444 > if [ -d auto_test.$ext ]; then
445 445 > echo "extension $ext was not autodetected."
446 446 > fi
447 447 > done
448 448
449 449 $ cat > md5comp.py <<EOF
450 450 > from __future__ import absolute_import, print_function
451 451 > import hashlib
452 452 > import sys
453 453 > f1, f2 = sys.argv[1:3]
454 454 > h1 = hashlib.md5(open(f1, 'rb').read()).hexdigest()
455 455 > h2 = hashlib.md5(open(f2, 'rb').read()).hexdigest()
456 456 > print(h1 == h2 or "md5 differ: " + repr((h1, h2)))
457 457 > EOF
458 458
459 459 archive name is stored in the archive, so create similar archives and
460 460 rename them afterwards.
461 461
462 462 $ hg archive -t tgz tip.tar.gz
463 463 $ mv tip.tar.gz tip1.tar.gz
464 464 $ sleep 1
465 465 $ hg archive -t tgz tip.tar.gz
466 466 $ mv tip.tar.gz tip2.tar.gz
467 467 $ "$PYTHON" md5comp.py tip1.tar.gz tip2.tar.gz
468 468 True
469 469
470 470 $ hg archive -t zip -p /illegal test.zip
471 471 abort: archive prefix contains illegal components
472 472 [255]
473 473 $ hg archive -t zip -p very/../bad test.zip
474 474
475 475 $ hg archive --config ui.archivemeta=false -t zip -r 2 test.zip
476 476 $ unzip -t test.zip
477 477 Archive: test.zip
478 478 testing: test/bar*OK (glob)
479 479 testing: test/baz/bletch*OK (glob)
480 480 testing: test/foo*OK (glob)
481 481 No errors detected in compressed data of test.zip.
482 482
483 483 $ hg archive -t tar - | tar tf - 2>/dev/null
484 484 test-1701ef1f1510/.hg_archival.txt
485 485 test-1701ef1f1510/.hgsub
486 486 test-1701ef1f1510/.hgsubstate
487 487 test-1701ef1f1510/bar
488 488 test-1701ef1f1510/baz/bletch
489 489 test-1701ef1f1510/foo
490 490
491 491 $ hg archive -r 0 -t tar rev-%r.tar
492 492 $ [ -f rev-0.tar ]
493 493
494 494 test .hg_archival.txt
495 495
496 496 $ hg archive ../test-tags
497 497 $ cat ../test-tags/.hg_archival.txt
498 498 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
499 499 node: 1701ef1f151069b8747038e93b5186bb43a47504
500 500 branch: default
501 501 latesttag: null
502 502 latesttagdistance: 4
503 503 changessincelatesttag: 4
504 504 $ hg tag -r 2 mytag
505 505 $ hg tag -r 2 anothertag
506 506 $ hg archive -r 2 ../test-lasttag
507 507 $ cat ../test-lasttag/.hg_archival.txt
508 508 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
509 509 node: 2c0277f05ed49d1c8328fb9ba92fba7a5ebcb33e
510 510 branch: default
511 511 tag: anothertag
512 512 tag: mytag
513 513
514 514 $ hg archive -t bogus test.bogus
515 515 abort: unknown archive type 'bogus'
516 516 [255]
517 517
518 518 enable progress extension:
519 519
520 520 $ cp $HGRCPATH $HGRCPATH.no-progress
521 521 $ cat >> $HGRCPATH <<EOF
522 522 > [progress]
523 523 > assume-tty = 1
524 524 > format = topic bar number
525 525 > delay = 0
526 526 > refresh = 0
527 527 > width = 60
528 528 > EOF
529 529
530 530 $ hg archive ../with-progress
531 531 \r (no-eol) (esc)
532 532 archiving [ ] 0/6\r (no-eol) (esc)
533 533 archiving [======> ] 1/6\r (no-eol) (esc)
534 534 archiving [=============> ] 2/6\r (no-eol) (esc)
535 535 archiving [====================> ] 3/6\r (no-eol) (esc)
536 536 archiving [===========================> ] 4/6\r (no-eol) (esc)
537 537 archiving [==================================> ] 5/6\r (no-eol) (esc)
538 538 archiving [==========================================>] 6/6\r (no-eol) (esc)
539 539 \r (no-eol) (esc)
540 540
541 541 cleanup after progress extension test:
542 542
543 543 $ cp $HGRCPATH.no-progress $HGRCPATH
544 544
545 545 server errors
546 546
547 547 $ cat errors.log
548 548
549 549 empty repo
550 550
551 551 $ hg init ../empty
552 552 $ cd ../empty
553 553 $ hg archive ../test-empty
554 554 abort: no working directory: please specify a revision
555 555 [255]
556 556
557 557 old file -- date clamped to 1980
558 558
559 559 $ touch -t 197501010000 old
560 560 $ hg add old
561 561 $ hg commit -m old
562 562 $ hg archive ../old.zip
563 563 $ unzip -l ../old.zip | grep -v -- ----- | egrep -v files$
564 564 Archive: ../old.zip
565 565 \s*Length.* (re)
566 566 *172*80*00:00*old/.hg_archival.txt (glob)
567 567 *0*80*00:00*old/old (glob)
568 568
569 test xz support only available in Python 3.4
570
571 #if py3
572 $ hg archive ../archive.txz
573 $ xz -l ../archive.txz | head -n1
574 Strms Blocks Compressed Uncompressed Ratio Check Filename
575 $ rm -f ../archive.txz
576 #else
577 $ hg archive ../archive.txz
578 abort: xz compression is only available in Python 3
579 [255]
580 #endif
581
569 582 show an error when a provided pattern matches no files
570 583
571 584 $ hg archive -I file_that_does_not_exist.foo ../empty.zip
572 585 abort: no files match the archive pattern
573 586 [255]
574 587
575 588 $ hg archive -X * ../empty.zip
576 589 abort: no files match the archive pattern
577 590 [255]
578 591
579 592 $ cd ..
580 593
581 594 issue3600: check whether "hg archive" can create archive files which
582 595 are extracted with expected timestamp, even though TZ is not
583 596 configured as GMT.
584 597
585 598 $ mkdir issue3600
586 599 $ cd issue3600
587 600
588 601 $ hg init repo
589 602 $ echo a > repo/a
590 603 $ hg -R repo add repo/a
591 604 $ hg -R repo commit -m '#0' -d '456789012 21600'
592 605 $ cat > show_mtime.py <<EOF
593 606 > from __future__ import absolute_import, print_function
594 607 > import os
595 608 > import sys
596 609 > print(int(os.stat(sys.argv[1]).st_mtime))
597 610 > EOF
598 611
599 612 $ hg -R repo archive --prefix tar-extracted archive.tar
600 613 $ (TZ=UTC-3; export TZ; tar xf archive.tar)
601 614 $ "$PYTHON" show_mtime.py tar-extracted/a
602 615 456789012
603 616
604 617 $ hg -R repo archive --prefix zip-extracted archive.zip
605 618 $ (TZ=UTC-3; export TZ; unzip -q archive.zip)
606 619 $ "$PYTHON" show_mtime.py zip-extracted/a
607 620 456789012
608 621
609 622 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now