##// END OF EJS Templates
fix wording and not-completely-trivial spelling errors and bad docstrings
Mads Kiilerich -
r17425:e95ec38f default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,467 +1,467 b''
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''largefiles utility code: must not import other modules in this package.'''
9 '''largefiles utility code: must not import other modules in this package.'''
10
10
11 import os
11 import os
12 import errno
12 import errno
13 import platform
13 import platform
14 import shutil
14 import shutil
15 import stat
15 import stat
16
16
17 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
17 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
18 from mercurial.i18n import _
18 from mercurial.i18n import _
19
19
20 shortname = '.hglf'
20 shortname = '.hglf'
21 longname = 'largefiles'
21 longname = 'largefiles'
22
22
23
23
24 # -- Portability wrappers ----------------------------------------------
24 # -- Portability wrappers ----------------------------------------------
25
25
26 def dirstatewalk(dirstate, matcher, unknown=False, ignored=False):
26 def dirstatewalk(dirstate, matcher, unknown=False, ignored=False):
27 return dirstate.walk(matcher, [], unknown, ignored)
27 return dirstate.walk(matcher, [], unknown, ignored)
28
28
29 def repoadd(repo, list):
29 def repoadd(repo, list):
30 add = repo[None].add
30 add = repo[None].add
31 return add(list)
31 return add(list)
32
32
33 def reporemove(repo, list, unlink=False):
33 def reporemove(repo, list, unlink=False):
34 def remove(list, unlink):
34 def remove(list, unlink):
35 wlock = repo.wlock()
35 wlock = repo.wlock()
36 try:
36 try:
37 if unlink:
37 if unlink:
38 for f in list:
38 for f in list:
39 try:
39 try:
40 util.unlinkpath(repo.wjoin(f))
40 util.unlinkpath(repo.wjoin(f))
41 except OSError, inst:
41 except OSError, inst:
42 if inst.errno != errno.ENOENT:
42 if inst.errno != errno.ENOENT:
43 raise
43 raise
44 repo[None].forget(list)
44 repo[None].forget(list)
45 finally:
45 finally:
46 wlock.release()
46 wlock.release()
47 return remove(list, unlink=unlink)
47 return remove(list, unlink=unlink)
48
48
49 def repoforget(repo, list):
49 def repoforget(repo, list):
50 forget = repo[None].forget
50 forget = repo[None].forget
51 return forget(list)
51 return forget(list)
52
52
53 def findoutgoing(repo, remote, force):
53 def findoutgoing(repo, remote, force):
54 from mercurial import discovery
54 from mercurial import discovery
55 common, _anyinc, _heads = discovery.findcommonincoming(repo,
55 common, _anyinc, _heads = discovery.findcommonincoming(repo,
56 remote.peer(), force=force)
56 remote.peer(), force=force)
57 return repo.changelog.findmissing(common)
57 return repo.changelog.findmissing(common)
58
58
59 # -- Private worker functions ------------------------------------------
59 # -- Private worker functions ------------------------------------------
60
60
61 def getminsize(ui, assumelfiles, opt, default=10):
61 def getminsize(ui, assumelfiles, opt, default=10):
62 lfsize = opt
62 lfsize = opt
63 if not lfsize and assumelfiles:
63 if not lfsize and assumelfiles:
64 lfsize = ui.config(longname, 'minsize', default=default)
64 lfsize = ui.config(longname, 'minsize', default=default)
65 if lfsize:
65 if lfsize:
66 try:
66 try:
67 lfsize = float(lfsize)
67 lfsize = float(lfsize)
68 except ValueError:
68 except ValueError:
69 raise util.Abort(_('largefiles: size must be number (not %s)\n')
69 raise util.Abort(_('largefiles: size must be number (not %s)\n')
70 % lfsize)
70 % lfsize)
71 if lfsize is None:
71 if lfsize is None:
72 raise util.Abort(_('minimum size for largefiles must be specified'))
72 raise util.Abort(_('minimum size for largefiles must be specified'))
73 return lfsize
73 return lfsize
74
74
75 def link(src, dest):
75 def link(src, dest):
76 try:
76 try:
77 util.oslink(src, dest)
77 util.oslink(src, dest)
78 except OSError:
78 except OSError:
79 # if hardlinks fail, fallback on atomic copy
79 # if hardlinks fail, fallback on atomic copy
80 dst = util.atomictempfile(dest)
80 dst = util.atomictempfile(dest)
81 for chunk in util.filechunkiter(open(src, 'rb')):
81 for chunk in util.filechunkiter(open(src, 'rb')):
82 dst.write(chunk)
82 dst.write(chunk)
83 dst.close()
83 dst.close()
84 os.chmod(dest, os.stat(src).st_mode)
84 os.chmod(dest, os.stat(src).st_mode)
85
85
86 def usercachepath(ui, hash):
86 def usercachepath(ui, hash):
87 path = ui.configpath(longname, 'usercache', None)
87 path = ui.configpath(longname, 'usercache', None)
88 if path:
88 if path:
89 path = os.path.join(path, hash)
89 path = os.path.join(path, hash)
90 else:
90 else:
91 if os.name == 'nt':
91 if os.name == 'nt':
92 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
92 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
93 if appdata:
93 if appdata:
94 path = os.path.join(appdata, longname, hash)
94 path = os.path.join(appdata, longname, hash)
95 elif platform.system() == 'Darwin':
95 elif platform.system() == 'Darwin':
96 home = os.getenv('HOME')
96 home = os.getenv('HOME')
97 if home:
97 if home:
98 path = os.path.join(home, 'Library', 'Caches',
98 path = os.path.join(home, 'Library', 'Caches',
99 longname, hash)
99 longname, hash)
100 elif os.name == 'posix':
100 elif os.name == 'posix':
101 path = os.getenv('XDG_CACHE_HOME')
101 path = os.getenv('XDG_CACHE_HOME')
102 if path:
102 if path:
103 path = os.path.join(path, longname, hash)
103 path = os.path.join(path, longname, hash)
104 else:
104 else:
105 home = os.getenv('HOME')
105 home = os.getenv('HOME')
106 if home:
106 if home:
107 path = os.path.join(home, '.cache', longname, hash)
107 path = os.path.join(home, '.cache', longname, hash)
108 else:
108 else:
109 raise util.Abort(_('unknown operating system: %s\n') % os.name)
109 raise util.Abort(_('unknown operating system: %s\n') % os.name)
110 return path
110 return path
111
111
112 def inusercache(ui, hash):
112 def inusercache(ui, hash):
113 path = usercachepath(ui, hash)
113 path = usercachepath(ui, hash)
114 return path and os.path.exists(path)
114 return path and os.path.exists(path)
115
115
116 def findfile(repo, hash):
116 def findfile(repo, hash):
117 if instore(repo, hash):
117 if instore(repo, hash):
118 repo.ui.note(_('found %s in store\n') % hash)
118 repo.ui.note(_('found %s in store\n') % hash)
119 return storepath(repo, hash)
119 return storepath(repo, hash)
120 elif inusercache(repo.ui, hash):
120 elif inusercache(repo.ui, hash):
121 repo.ui.note(_('found %s in system cache\n') % hash)
121 repo.ui.note(_('found %s in system cache\n') % hash)
122 path = storepath(repo, hash)
122 path = storepath(repo, hash)
123 util.makedirs(os.path.dirname(path))
123 util.makedirs(os.path.dirname(path))
124 link(usercachepath(repo.ui, hash), path)
124 link(usercachepath(repo.ui, hash), path)
125 return path
125 return path
126 return None
126 return None
127
127
128 class largefilesdirstate(dirstate.dirstate):
128 class largefilesdirstate(dirstate.dirstate):
129 def __getitem__(self, key):
129 def __getitem__(self, key):
130 return super(largefilesdirstate, self).__getitem__(unixpath(key))
130 return super(largefilesdirstate, self).__getitem__(unixpath(key))
131 def normal(self, f):
131 def normal(self, f):
132 return super(largefilesdirstate, self).normal(unixpath(f))
132 return super(largefilesdirstate, self).normal(unixpath(f))
133 def remove(self, f):
133 def remove(self, f):
134 return super(largefilesdirstate, self).remove(unixpath(f))
134 return super(largefilesdirstate, self).remove(unixpath(f))
135 def add(self, f):
135 def add(self, f):
136 return super(largefilesdirstate, self).add(unixpath(f))
136 return super(largefilesdirstate, self).add(unixpath(f))
137 def drop(self, f):
137 def drop(self, f):
138 return super(largefilesdirstate, self).drop(unixpath(f))
138 return super(largefilesdirstate, self).drop(unixpath(f))
139 def forget(self, f):
139 def forget(self, f):
140 return super(largefilesdirstate, self).forget(unixpath(f))
140 return super(largefilesdirstate, self).forget(unixpath(f))
141 def normallookup(self, f):
141 def normallookup(self, f):
142 return super(largefilesdirstate, self).normallookup(unixpath(f))
142 return super(largefilesdirstate, self).normallookup(unixpath(f))
143
143
144 def openlfdirstate(ui, repo):
144 def openlfdirstate(ui, repo):
145 '''
145 '''
146 Return a dirstate object that tracks largefiles: i.e. its root is
146 Return a dirstate object that tracks largefiles: i.e. its root is
147 the repo root, but it is saved in .hg/largefiles/dirstate.
147 the repo root, but it is saved in .hg/largefiles/dirstate.
148 '''
148 '''
149 admin = repo.join(longname)
149 admin = repo.join(longname)
150 opener = scmutil.opener(admin)
150 opener = scmutil.opener(admin)
151 lfdirstate = largefilesdirstate(opener, ui, repo.root,
151 lfdirstate = largefilesdirstate(opener, ui, repo.root,
152 repo.dirstate._validate)
152 repo.dirstate._validate)
153
153
154 # If the largefiles dirstate does not exist, populate and create
154 # If the largefiles dirstate does not exist, populate and create
155 # it. This ensures that we create it on the first meaningful
155 # it. This ensures that we create it on the first meaningful
156 # largefiles operation in a new clone.
156 # largefiles operation in a new clone.
157 if not os.path.exists(os.path.join(admin, 'dirstate')):
157 if not os.path.exists(os.path.join(admin, 'dirstate')):
158 util.makedirs(admin)
158 util.makedirs(admin)
159 matcher = getstandinmatcher(repo)
159 matcher = getstandinmatcher(repo)
160 for standin in dirstatewalk(repo.dirstate, matcher):
160 for standin in dirstatewalk(repo.dirstate, matcher):
161 lfile = splitstandin(standin)
161 lfile = splitstandin(standin)
162 hash = readstandin(repo, lfile)
162 hash = readstandin(repo, lfile)
163 lfdirstate.normallookup(lfile)
163 lfdirstate.normallookup(lfile)
164 try:
164 try:
165 if hash == hashfile(repo.wjoin(lfile)):
165 if hash == hashfile(repo.wjoin(lfile)):
166 lfdirstate.normal(lfile)
166 lfdirstate.normal(lfile)
167 except OSError, err:
167 except OSError, err:
168 if err.errno != errno.ENOENT:
168 if err.errno != errno.ENOENT:
169 raise
169 raise
170 return lfdirstate
170 return lfdirstate
171
171
172 def lfdirstatestatus(lfdirstate, repo, rev):
172 def lfdirstatestatus(lfdirstate, repo, rev):
173 match = match_.always(repo.root, repo.getcwd())
173 match = match_.always(repo.root, repo.getcwd())
174 s = lfdirstate.status(match, [], False, False, False)
174 s = lfdirstate.status(match, [], False, False, False)
175 unsure, modified, added, removed, missing, unknown, ignored, clean = s
175 unsure, modified, added, removed, missing, unknown, ignored, clean = s
176 for lfile in unsure:
176 for lfile in unsure:
177 if repo[rev][standin(lfile)].data().strip() != \
177 if repo[rev][standin(lfile)].data().strip() != \
178 hashfile(repo.wjoin(lfile)):
178 hashfile(repo.wjoin(lfile)):
179 modified.append(lfile)
179 modified.append(lfile)
180 else:
180 else:
181 clean.append(lfile)
181 clean.append(lfile)
182 lfdirstate.normal(lfile)
182 lfdirstate.normal(lfile)
183 return (modified, added, removed, missing, unknown, ignored, clean)
183 return (modified, added, removed, missing, unknown, ignored, clean)
184
184
185 def listlfiles(repo, rev=None, matcher=None):
185 def listlfiles(repo, rev=None, matcher=None):
186 '''return a list of largefiles in the working copy or the
186 '''return a list of largefiles in the working copy or the
187 specified changeset'''
187 specified changeset'''
188
188
189 if matcher is None:
189 if matcher is None:
190 matcher = getstandinmatcher(repo)
190 matcher = getstandinmatcher(repo)
191
191
192 # ignore unknown files in working directory
192 # ignore unknown files in working directory
193 return [splitstandin(f)
193 return [splitstandin(f)
194 for f in repo[rev].walk(matcher)
194 for f in repo[rev].walk(matcher)
195 if rev is not None or repo.dirstate[f] != '?']
195 if rev is not None or repo.dirstate[f] != '?']
196
196
197 def instore(repo, hash):
197 def instore(repo, hash):
198 return os.path.exists(storepath(repo, hash))
198 return os.path.exists(storepath(repo, hash))
199
199
200 def storepath(repo, hash):
200 def storepath(repo, hash):
201 return repo.join(os.path.join(longname, hash))
201 return repo.join(os.path.join(longname, hash))
202
202
203 def copyfromcache(repo, hash, filename):
203 def copyfromcache(repo, hash, filename):
204 '''Copy the specified largefile from the repo or system cache to
204 '''Copy the specified largefile from the repo or system cache to
205 filename in the repository. Return true on success or false if the
205 filename in the repository. Return true on success or false if the
206 file was not found in either cache (which should not happened:
206 file was not found in either cache (which should not happened:
207 this is meant to be called only after ensuring that the needed
207 this is meant to be called only after ensuring that the needed
208 largefile exists in the cache).'''
208 largefile exists in the cache).'''
209 path = findfile(repo, hash)
209 path = findfile(repo, hash)
210 if path is None:
210 if path is None:
211 return False
211 return False
212 util.makedirs(os.path.dirname(repo.wjoin(filename)))
212 util.makedirs(os.path.dirname(repo.wjoin(filename)))
213 # The write may fail before the file is fully written, but we
213 # The write may fail before the file is fully written, but we
214 # don't use atomic writes in the working copy.
214 # don't use atomic writes in the working copy.
215 shutil.copy(path, repo.wjoin(filename))
215 shutil.copy(path, repo.wjoin(filename))
216 return True
216 return True
217
217
218 def copytostore(repo, rev, file, uploaded=False):
218 def copytostore(repo, rev, file, uploaded=False):
219 hash = readstandin(repo, file)
219 hash = readstandin(repo, file)
220 if instore(repo, hash):
220 if instore(repo, hash):
221 return
221 return
222 copytostoreabsolute(repo, repo.wjoin(file), hash)
222 copytostoreabsolute(repo, repo.wjoin(file), hash)
223
223
224 def copyalltostore(repo, node):
224 def copyalltostore(repo, node):
225 '''Copy all largefiles in a given revision to the store'''
225 '''Copy all largefiles in a given revision to the store'''
226
226
227 ctx = repo[node]
227 ctx = repo[node]
228 for filename in ctx.files():
228 for filename in ctx.files():
229 if isstandin(filename) and filename in ctx.manifest():
229 if isstandin(filename) and filename in ctx.manifest():
230 realfile = splitstandin(filename)
230 realfile = splitstandin(filename)
231 copytostore(repo, ctx.node(), realfile)
231 copytostore(repo, ctx.node(), realfile)
232
232
233
233
234 def copytostoreabsolute(repo, file, hash):
234 def copytostoreabsolute(repo, file, hash):
235 util.makedirs(os.path.dirname(storepath(repo, hash)))
235 util.makedirs(os.path.dirname(storepath(repo, hash)))
236 if inusercache(repo.ui, hash):
236 if inusercache(repo.ui, hash):
237 link(usercachepath(repo.ui, hash), storepath(repo, hash))
237 link(usercachepath(repo.ui, hash), storepath(repo, hash))
238 else:
238 else:
239 dst = util.atomictempfile(storepath(repo, hash),
239 dst = util.atomictempfile(storepath(repo, hash),
240 createmode=repo.store.createmode)
240 createmode=repo.store.createmode)
241 for chunk in util.filechunkiter(open(file, 'rb')):
241 for chunk in util.filechunkiter(open(file, 'rb')):
242 dst.write(chunk)
242 dst.write(chunk)
243 dst.close()
243 dst.close()
244 linktousercache(repo, hash)
244 linktousercache(repo, hash)
245
245
246 def linktousercache(repo, hash):
246 def linktousercache(repo, hash):
247 path = usercachepath(repo.ui, hash)
247 path = usercachepath(repo.ui, hash)
248 if path:
248 if path:
249 util.makedirs(os.path.dirname(path))
249 util.makedirs(os.path.dirname(path))
250 link(storepath(repo, hash), path)
250 link(storepath(repo, hash), path)
251
251
252 def getstandinmatcher(repo, pats=[], opts={}):
252 def getstandinmatcher(repo, pats=[], opts={}):
253 '''Return a match object that applies pats to the standin directory'''
253 '''Return a match object that applies pats to the standin directory'''
254 standindir = repo.pathto(shortname)
254 standindir = repo.pathto(shortname)
255 if pats:
255 if pats:
256 # patterns supplied: search standin directory relative to current dir
256 # patterns supplied: search standin directory relative to current dir
257 cwd = repo.getcwd()
257 cwd = repo.getcwd()
258 if os.path.isabs(cwd):
258 if os.path.isabs(cwd):
259 # cwd is an absolute path for hg -R <reponame>
259 # cwd is an absolute path for hg -R <reponame>
260 # work relative to the repository root in this case
260 # work relative to the repository root in this case
261 cwd = ''
261 cwd = ''
262 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
262 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
263 elif os.path.isdir(standindir):
263 elif os.path.isdir(standindir):
264 # no patterns: relative to repo root
264 # no patterns: relative to repo root
265 pats = [standindir]
265 pats = [standindir]
266 else:
266 else:
267 # no patterns and no standin dir: return matcher that matches nothing
267 # no patterns and no standin dir: return matcher that matches nothing
268 match = match_.match(repo.root, None, [], exact=True)
268 match = match_.match(repo.root, None, [], exact=True)
269 match.matchfn = lambda f: False
269 match.matchfn = lambda f: False
270 return match
270 return match
271 return getmatcher(repo, pats, opts, showbad=False)
271 return getmatcher(repo, pats, opts, showbad=False)
272
272
273 def getmatcher(repo, pats=[], opts={}, showbad=True):
273 def getmatcher(repo, pats=[], opts={}, showbad=True):
274 '''Wrapper around scmutil.match() that adds showbad: if false,
274 '''Wrapper around scmutil.match() that adds showbad: if false,
275 neuter the match object's bad() method so it does not print any
275 neuter the match object's bad() method so it does not print any
276 warnings about missing files or directories.'''
276 warnings about missing files or directories.'''
277 match = scmutil.match(repo[None], pats, opts)
277 match = scmutil.match(repo[None], pats, opts)
278
278
279 if not showbad:
279 if not showbad:
280 match.bad = lambda f, msg: None
280 match.bad = lambda f, msg: None
281 return match
281 return match
282
282
283 def composestandinmatcher(repo, rmatcher):
283 def composestandinmatcher(repo, rmatcher):
284 '''Return a matcher that accepts standins corresponding to the
284 '''Return a matcher that accepts standins corresponding to the
285 files accepted by rmatcher. Pass the list of files in the matcher
285 files accepted by rmatcher. Pass the list of files in the matcher
286 as the paths specified by the user.'''
286 as the paths specified by the user.'''
287 smatcher = getstandinmatcher(repo, rmatcher.files())
287 smatcher = getstandinmatcher(repo, rmatcher.files())
288 isstandin = smatcher.matchfn
288 isstandin = smatcher.matchfn
289 def composedmatchfn(f):
289 def composedmatchfn(f):
290 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
290 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
291 smatcher.matchfn = composedmatchfn
291 smatcher.matchfn = composedmatchfn
292
292
293 return smatcher
293 return smatcher
294
294
295 def standin(filename):
295 def standin(filename):
296 '''Return the repo-relative path to the standin for the specified big
296 '''Return the repo-relative path to the standin for the specified big
297 file.'''
297 file.'''
298 # Notes:
298 # Notes:
299 # 1) Most callers want an absolute path, but _createstandin() needs
299 # 1) Some callers want an absolute path, but for instance addlargefiles
300 # it repo-relative so lfadd() can pass it to repoadd(). So leave
300 # needs it repo-relative so it can be passed to repoadd(). So leave
301 # it up to the caller to use repo.wjoin() to get an absolute path.
301 # it up to the caller to use repo.wjoin() to get an absolute path.
302 # 2) Join with '/' because that's what dirstate always uses, even on
302 # 2) Join with '/' because that's what dirstate always uses, even on
303 # Windows. Change existing separator to '/' first in case we are
303 # Windows. Change existing separator to '/' first in case we are
304 # passed filenames from an external source (like the command line).
304 # passed filenames from an external source (like the command line).
305 return shortname + '/' + util.pconvert(filename)
305 return shortname + '/' + util.pconvert(filename)
306
306
307 def isstandin(filename):
307 def isstandin(filename):
308 '''Return true if filename is a big file standin. filename must be
308 '''Return true if filename is a big file standin. filename must be
309 in Mercurial's internal form (slash-separated).'''
309 in Mercurial's internal form (slash-separated).'''
310 return filename.startswith(shortname + '/')
310 return filename.startswith(shortname + '/')
311
311
312 def splitstandin(filename):
312 def splitstandin(filename):
313 # Split on / because that's what dirstate always uses, even on Windows.
313 # Split on / because that's what dirstate always uses, even on Windows.
314 # Change local separator to / first just in case we are passed filenames
314 # Change local separator to / first just in case we are passed filenames
315 # from an external source (like the command line).
315 # from an external source (like the command line).
316 bits = util.pconvert(filename).split('/', 1)
316 bits = util.pconvert(filename).split('/', 1)
317 if len(bits) == 2 and bits[0] == shortname:
317 if len(bits) == 2 and bits[0] == shortname:
318 return bits[1]
318 return bits[1]
319 else:
319 else:
320 return None
320 return None
321
321
322 def updatestandin(repo, standin):
322 def updatestandin(repo, standin):
323 file = repo.wjoin(splitstandin(standin))
323 file = repo.wjoin(splitstandin(standin))
324 if os.path.exists(file):
324 if os.path.exists(file):
325 hash = hashfile(file)
325 hash = hashfile(file)
326 executable = getexecutable(file)
326 executable = getexecutable(file)
327 writestandin(repo, standin, hash, executable)
327 writestandin(repo, standin, hash, executable)
328
328
329 def readstandin(repo, filename, node=None):
329 def readstandin(repo, filename, node=None):
330 '''read hex hash from standin for filename at given node, or working
330 '''read hex hash from standin for filename at given node, or working
331 directory if no node is given'''
331 directory if no node is given'''
332 return repo[node][standin(filename)].data().strip()
332 return repo[node][standin(filename)].data().strip()
333
333
334 def writestandin(repo, standin, hash, executable):
334 def writestandin(repo, standin, hash, executable):
335 '''write hash to <repo.root>/<standin>'''
335 '''write hash to <repo.root>/<standin>'''
336 writehash(hash, repo.wjoin(standin), executable)
336 writehash(hash, repo.wjoin(standin), executable)
337
337
338 def copyandhash(instream, outfile):
338 def copyandhash(instream, outfile):
339 '''Read bytes from instream (iterable) and write them to outfile,
339 '''Read bytes from instream (iterable) and write them to outfile,
340 computing the SHA-1 hash of the data along the way. Close outfile
340 computing the SHA-1 hash of the data along the way. Close outfile
341 when done and return the binary hash.'''
341 when done and return the binary hash.'''
342 hasher = util.sha1('')
342 hasher = util.sha1('')
343 for data in instream:
343 for data in instream:
344 hasher.update(data)
344 hasher.update(data)
345 outfile.write(data)
345 outfile.write(data)
346
346
347 # Blecch: closing a file that somebody else opened is rude and
347 # Blecch: closing a file that somebody else opened is rude and
348 # wrong. But it's so darn convenient and practical! After all,
348 # wrong. But it's so darn convenient and practical! After all,
349 # outfile was opened just to copy and hash.
349 # outfile was opened just to copy and hash.
350 outfile.close()
350 outfile.close()
351
351
352 return hasher.digest()
352 return hasher.digest()
353
353
354 def hashrepofile(repo, file):
354 def hashrepofile(repo, file):
355 return hashfile(repo.wjoin(file))
355 return hashfile(repo.wjoin(file))
356
356
357 def hashfile(file):
357 def hashfile(file):
358 if not os.path.exists(file):
358 if not os.path.exists(file):
359 return ''
359 return ''
360 hasher = util.sha1('')
360 hasher = util.sha1('')
361 fd = open(file, 'rb')
361 fd = open(file, 'rb')
362 for data in blockstream(fd):
362 for data in blockstream(fd):
363 hasher.update(data)
363 hasher.update(data)
364 fd.close()
364 fd.close()
365 return hasher.hexdigest()
365 return hasher.hexdigest()
366
366
367 class limitreader(object):
367 class limitreader(object):
368 def __init__(self, f, limit):
368 def __init__(self, f, limit):
369 self.f = f
369 self.f = f
370 self.limit = limit
370 self.limit = limit
371
371
372 def read(self, length):
372 def read(self, length):
373 if self.limit == 0:
373 if self.limit == 0:
374 return ''
374 return ''
375 length = length > self.limit and self.limit or length
375 length = length > self.limit and self.limit or length
376 self.limit -= length
376 self.limit -= length
377 return self.f.read(length)
377 return self.f.read(length)
378
378
379 def close(self):
379 def close(self):
380 pass
380 pass
381
381
382 def blockstream(infile, blocksize=128 * 1024):
382 def blockstream(infile, blocksize=128 * 1024):
383 """Generator that yields blocks of data from infile and closes infile."""
383 """Generator that yields blocks of data from infile and closes infile."""
384 while True:
384 while True:
385 data = infile.read(blocksize)
385 data = infile.read(blocksize)
386 if not data:
386 if not data:
387 break
387 break
388 yield data
388 yield data
389 # same blecch as copyandhash() above
389 # same blecch as copyandhash() above
390 infile.close()
390 infile.close()
391
391
392 def writehash(hash, filename, executable):
392 def writehash(hash, filename, executable):
393 util.makedirs(os.path.dirname(filename))
393 util.makedirs(os.path.dirname(filename))
394 util.writefile(filename, hash + '\n')
394 util.writefile(filename, hash + '\n')
395 os.chmod(filename, getmode(executable))
395 os.chmod(filename, getmode(executable))
396
396
397 def getexecutable(filename):
397 def getexecutable(filename):
398 mode = os.stat(filename).st_mode
398 mode = os.stat(filename).st_mode
399 return ((mode & stat.S_IXUSR) and
399 return ((mode & stat.S_IXUSR) and
400 (mode & stat.S_IXGRP) and
400 (mode & stat.S_IXGRP) and
401 (mode & stat.S_IXOTH))
401 (mode & stat.S_IXOTH))
402
402
403 def getmode(executable):
403 def getmode(executable):
404 if executable:
404 if executable:
405 return 0755
405 return 0755
406 else:
406 else:
407 return 0644
407 return 0644
408
408
409 def urljoin(first, second, *arg):
409 def urljoin(first, second, *arg):
410 def join(left, right):
410 def join(left, right):
411 if not left.endswith('/'):
411 if not left.endswith('/'):
412 left += '/'
412 left += '/'
413 if right.startswith('/'):
413 if right.startswith('/'):
414 right = right[1:]
414 right = right[1:]
415 return left + right
415 return left + right
416
416
417 url = join(first, second)
417 url = join(first, second)
418 for a in arg:
418 for a in arg:
419 url = join(url, a)
419 url = join(url, a)
420 return url
420 return url
421
421
422 def hexsha1(data):
422 def hexsha1(data):
423 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
423 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
424 object data"""
424 object data"""
425 h = util.sha1()
425 h = util.sha1()
426 for chunk in util.filechunkiter(data):
426 for chunk in util.filechunkiter(data):
427 h.update(chunk)
427 h.update(chunk)
428 return h.hexdigest()
428 return h.hexdigest()
429
429
430 def httpsendfile(ui, filename):
430 def httpsendfile(ui, filename):
431 return httpconnection.httpsendfile(ui, filename, 'rb')
431 return httpconnection.httpsendfile(ui, filename, 'rb')
432
432
433 def unixpath(path):
433 def unixpath(path):
434 '''Return a version of path normalized for use with the lfdirstate.'''
434 '''Return a version of path normalized for use with the lfdirstate.'''
435 return util.pconvert(os.path.normpath(path))
435 return util.pconvert(os.path.normpath(path))
436
436
437 def islfilesrepo(repo):
437 def islfilesrepo(repo):
438 return ('largefiles' in repo.requirements and
438 return ('largefiles' in repo.requirements and
439 util.any(shortname + '/' in f[0] for f in repo.store.datafiles()))
439 util.any(shortname + '/' in f[0] for f in repo.store.datafiles()))
440
440
441 class storeprotonotcapable(Exception):
441 class storeprotonotcapable(Exception):
442 def __init__(self, storetypes):
442 def __init__(self, storetypes):
443 self.storetypes = storetypes
443 self.storetypes = storetypes
444
444
445 def getcurrentheads(repo):
445 def getcurrentheads(repo):
446 branches = repo.branchmap()
446 branches = repo.branchmap()
447 heads = []
447 heads = []
448 for branch in branches:
448 for branch in branches:
449 newheads = repo.branchheads(branch)
449 newheads = repo.branchheads(branch)
450 heads = heads + newheads
450 heads = heads + newheads
451 return heads
451 return heads
452
452
453 def getstandinsstate(repo):
453 def getstandinsstate(repo):
454 standins = []
454 standins = []
455 matcher = getstandinmatcher(repo)
455 matcher = getstandinmatcher(repo)
456 for standin in dirstatewalk(repo.dirstate, matcher):
456 for standin in dirstatewalk(repo.dirstate, matcher):
457 lfile = splitstandin(standin)
457 lfile = splitstandin(standin)
458 standins.append((lfile, readstandin(repo, lfile)))
458 standins.append((lfile, readstandin(repo, lfile)))
459 return standins
459 return standins
460
460
461 def getlfilestoupdate(oldstandins, newstandins):
461 def getlfilestoupdate(oldstandins, newstandins):
462 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
462 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
463 filelist = []
463 filelist = []
464 for f in changedstandins:
464 for f in changedstandins:
465 if f[0] not in filelist:
465 if f[0] not in filelist:
466 filelist.append(f[0])
466 filelist.append(f[0])
467 return filelist
467 return filelist
@@ -1,110 +1,110 b''
1 # Copyright 2010-2011 Fog Creek Software
1 # Copyright 2010-2011 Fog Creek Software
2 # Copyright 2010-2011 Unity Technologies
2 # Copyright 2010-2011 Unity Technologies
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 '''remote largefile store; the base class for servestore'''
7 '''remote largefile store; the base class for wirestore'''
8
8
9 import urllib2
9 import urllib2
10
10
11 from mercurial import util
11 from mercurial import util
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from mercurial.wireproto import remotebatch
13 from mercurial.wireproto import remotebatch
14
14
15 import lfutil
15 import lfutil
16 import basestore
16 import basestore
17
17
18 class remotestore(basestore.basestore):
18 class remotestore(basestore.basestore):
19 '''a largefile store accessed over a network'''
19 '''a largefile store accessed over a network'''
20 def __init__(self, ui, repo, url):
20 def __init__(self, ui, repo, url):
21 super(remotestore, self).__init__(ui, repo, url)
21 super(remotestore, self).__init__(ui, repo, url)
22
22
23 def put(self, source, hash):
23 def put(self, source, hash):
24 if self.sendfile(source, hash):
24 if self.sendfile(source, hash):
25 raise util.Abort(
25 raise util.Abort(
26 _('remotestore: could not put %s to remote store %s')
26 _('remotestore: could not put %s to remote store %s')
27 % (source, self.url))
27 % (source, self.url))
28 self.ui.debug(
28 self.ui.debug(
29 _('remotestore: put %s to remote store %s') % (source, self.url))
29 _('remotestore: put %s to remote store %s') % (source, self.url))
30
30
31 def exists(self, hashes):
31 def exists(self, hashes):
32 return self._verify(hashes)
32 return self._verify(hashes)
33
33
34 def sendfile(self, filename, hash):
34 def sendfile(self, filename, hash):
35 self.ui.debug('remotestore: sendfile(%s, %s)\n' % (filename, hash))
35 self.ui.debug('remotestore: sendfile(%s, %s)\n' % (filename, hash))
36 fd = None
36 fd = None
37 try:
37 try:
38 try:
38 try:
39 fd = lfutil.httpsendfile(self.ui, filename)
39 fd = lfutil.httpsendfile(self.ui, filename)
40 except IOError, e:
40 except IOError, e:
41 raise util.Abort(
41 raise util.Abort(
42 _('remotestore: could not open file %s: %s')
42 _('remotestore: could not open file %s: %s')
43 % (filename, str(e)))
43 % (filename, str(e)))
44 return self._put(hash, fd)
44 return self._put(hash, fd)
45 finally:
45 finally:
46 if fd:
46 if fd:
47 fd.close()
47 fd.close()
48
48
49 def _getfile(self, tmpfile, filename, hash):
49 def _getfile(self, tmpfile, filename, hash):
50 # quit if the largefile isn't there
50 # quit if the largefile isn't there
51 stat = self._stat(hash)
51 stat = self._stat(hash)
52 if stat == 1:
52 if stat == 1:
53 raise util.Abort(_('remotestore: largefile %s is invalid') % hash)
53 raise util.Abort(_('remotestore: largefile %s is invalid') % hash)
54 elif stat == 2:
54 elif stat == 2:
55 raise util.Abort(_('remotestore: largefile %s is missing') % hash)
55 raise util.Abort(_('remotestore: largefile %s is missing') % hash)
56
56
57 try:
57 try:
58 length, infile = self._get(hash)
58 length, infile = self._get(hash)
59 except urllib2.HTTPError, e:
59 except urllib2.HTTPError, e:
60 # 401s get converted to util.Aborts; everything else is fine being
60 # 401s get converted to util.Aborts; everything else is fine being
61 # turned into a StoreError
61 # turned into a StoreError
62 raise basestore.StoreError(filename, hash, self.url, str(e))
62 raise basestore.StoreError(filename, hash, self.url, str(e))
63 except urllib2.URLError, e:
63 except urllib2.URLError, e:
64 # This usually indicates a connection problem, so don't
64 # This usually indicates a connection problem, so don't
65 # keep trying with the other files... they will probably
65 # keep trying with the other files... they will probably
66 # all fail too.
66 # all fail too.
67 raise util.Abort('%s: %s' % (self.url, e.reason))
67 raise util.Abort('%s: %s' % (self.url, e.reason))
68 except IOError, e:
68 except IOError, e:
69 raise basestore.StoreError(filename, hash, self.url, str(e))
69 raise basestore.StoreError(filename, hash, self.url, str(e))
70
70
71 # Mercurial does not close its SSH connections after writing a stream
71 # Mercurial does not close its SSH connections after writing a stream
72 if length is not None:
72 if length is not None:
73 infile = lfutil.limitreader(infile, length)
73 infile = lfutil.limitreader(infile, length)
74 return lfutil.copyandhash(lfutil.blockstream(infile), tmpfile)
74 return lfutil.copyandhash(lfutil.blockstream(infile), tmpfile)
75
75
76 def _verify(self, hashes):
76 def _verify(self, hashes):
77 return self._stat(hashes)
77 return self._stat(hashes)
78
78
79 def _verifyfile(self, cctx, cset, contents, standin, verified):
79 def _verifyfile(self, cctx, cset, contents, standin, verified):
80 filename = lfutil.splitstandin(standin)
80 filename = lfutil.splitstandin(standin)
81 if not filename:
81 if not filename:
82 return False
82 return False
83 fctx = cctx[standin]
83 fctx = cctx[standin]
84 key = (filename, fctx.filenode())
84 key = (filename, fctx.filenode())
85 if key in verified:
85 if key in verified:
86 return False
86 return False
87
87
88 verified.add(key)
88 verified.add(key)
89
89
90 stat = self._stat(hash)
90 stat = self._stat(hash)
91 if not stat:
91 if not stat:
92 return False
92 return False
93 elif stat == 1:
93 elif stat == 1:
94 self.ui.warn(
94 self.ui.warn(
95 _('changeset %s: %s: contents differ\n')
95 _('changeset %s: %s: contents differ\n')
96 % (cset, filename))
96 % (cset, filename))
97 return True # failed
97 return True # failed
98 elif stat == 2:
98 elif stat == 2:
99 self.ui.warn(
99 self.ui.warn(
100 _('changeset %s: %s missing\n')
100 _('changeset %s: %s missing\n')
101 % (cset, filename))
101 % (cset, filename))
102 return True # failed
102 return True # failed
103 else:
103 else:
104 raise RuntimeError('verify failed: unexpected response from '
104 raise RuntimeError('verify failed: unexpected response from '
105 'statlfile (%r)' % stat)
105 'statlfile (%r)' % stat)
106
106
107 def batch(self):
107 def batch(self):
108 '''Support for remote batching.'''
108 '''Support for remote batching.'''
109 return remotebatch(self)
109 return remotebatch(self)
110
110
@@ -1,3597 +1,3597 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use :hg:`help command` for more details)::
17 Common tasks (use :hg:`help command` for more details)::
18
18
19 create new patch qnew
19 create new patch qnew
20 import existing patch qimport
20 import existing patch qimport
21
21
22 print patch series qseries
22 print patch series qseries
23 print applied patches qapplied
23 print applied patches qapplied
24
24
25 add known patch to applied stack qpush
25 add known patch to applied stack qpush
26 remove patch from applied stack qpop
26 remove patch from applied stack qpop
27 refresh contents of top applied patch qrefresh
27 refresh contents of top applied patch qrefresh
28
28
29 By default, mq will automatically use git patches when required to
29 By default, mq will automatically use git patches when required to
30 avoid losing file mode changes, copy records, binary files or empty
30 avoid losing file mode changes, copy records, binary files or empty
31 files creations or deletions. This behaviour can be configured with::
31 files creations or deletions. This behaviour can be configured with::
32
32
33 [mq]
33 [mq]
34 git = auto/keep/yes/no
34 git = auto/keep/yes/no
35
35
36 If set to 'keep', mq will obey the [diff] section configuration while
36 If set to 'keep', mq will obey the [diff] section configuration while
37 preserving existing git patches upon qrefresh. If set to 'yes' or
37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 'no', mq will override the [diff] section and always generate git or
38 'no', mq will override the [diff] section and always generate git or
39 regular patches, possibly losing data in the second case.
39 regular patches, possibly losing data in the second case.
40
40
41 It may be desirable for mq changesets to be kept in the secret phase (see
41 It may be desirable for mq changesets to be kept in the secret phase (see
42 :hg:`help phases`), which can be enabled with the following setting::
42 :hg:`help phases`), which can be enabled with the following setting::
43
43
44 [mq]
44 [mq]
45 secret = True
45 secret = True
46
46
47 You will by default be managing a patch queue named "patches". You can
47 You will by default be managing a patch queue named "patches". You can
48 create other, independent patch queues with the :hg:`qqueue` command.
48 create other, independent patch queues with the :hg:`qqueue` command.
49
49
50 If the working directory contains uncommitted files, qpush, qpop and
50 If the working directory contains uncommitted files, qpush, qpop and
51 qgoto abort immediately. If -f/--force is used, the changes are
51 qgoto abort immediately. If -f/--force is used, the changes are
52 discarded. Setting::
52 discarded. Setting::
53
53
54 [mq]
54 [mq]
55 keepchanges = True
55 keepchanges = True
56
56
57 make them behave as if --keep-changes were passed, and non-conflicting
57 make them behave as if --keep-changes were passed, and non-conflicting
58 local changes will be tolerated and preserved. If incompatible options
58 local changes will be tolerated and preserved. If incompatible options
59 such as -f/--force or --exact are passed, this setting is ignored.
59 such as -f/--force or --exact are passed, this setting is ignored.
60 '''
60 '''
61
61
62 from mercurial.i18n import _
62 from mercurial.i18n import _
63 from mercurial.node import bin, hex, short, nullid, nullrev
63 from mercurial.node import bin, hex, short, nullid, nullrev
64 from mercurial.lock import release
64 from mercurial.lock import release
65 from mercurial import commands, cmdutil, hg, scmutil, util, revset
65 from mercurial import commands, cmdutil, hg, scmutil, util, revset
66 from mercurial import repair, extensions, url, error, phases
66 from mercurial import repair, extensions, url, error, phases
67 from mercurial import patch as patchmod
67 from mercurial import patch as patchmod
68 import os, re, errno, shutil
68 import os, re, errno, shutil
69
69
70 commands.norepo += " qclone"
70 commands.norepo += " qclone"
71
71
72 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
72 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
73
73
74 cmdtable = {}
74 cmdtable = {}
75 command = cmdutil.command(cmdtable)
75 command = cmdutil.command(cmdtable)
76 testedwith = 'internal'
76 testedwith = 'internal'
77
77
78 # Patch names looks like unix-file names.
78 # Patch names looks like unix-file names.
79 # They must be joinable with queue directory and result in the patch path.
79 # They must be joinable with queue directory and result in the patch path.
80 normname = util.normpath
80 normname = util.normpath
81
81
82 class statusentry(object):
82 class statusentry(object):
83 def __init__(self, node, name):
83 def __init__(self, node, name):
84 self.node, self.name = node, name
84 self.node, self.name = node, name
85 def __repr__(self):
85 def __repr__(self):
86 return hex(self.node) + ':' + self.name
86 return hex(self.node) + ':' + self.name
87
87
88 class patchheader(object):
88 class patchheader(object):
89 def __init__(self, pf, plainmode=False):
89 def __init__(self, pf, plainmode=False):
90 def eatdiff(lines):
90 def eatdiff(lines):
91 while lines:
91 while lines:
92 l = lines[-1]
92 l = lines[-1]
93 if (l.startswith("diff -") or
93 if (l.startswith("diff -") or
94 l.startswith("Index:") or
94 l.startswith("Index:") or
95 l.startswith("===========")):
95 l.startswith("===========")):
96 del lines[-1]
96 del lines[-1]
97 else:
97 else:
98 break
98 break
99 def eatempty(lines):
99 def eatempty(lines):
100 while lines:
100 while lines:
101 if not lines[-1].strip():
101 if not lines[-1].strip():
102 del lines[-1]
102 del lines[-1]
103 else:
103 else:
104 break
104 break
105
105
106 message = []
106 message = []
107 comments = []
107 comments = []
108 user = None
108 user = None
109 date = None
109 date = None
110 parent = None
110 parent = None
111 format = None
111 format = None
112 subject = None
112 subject = None
113 branch = None
113 branch = None
114 nodeid = None
114 nodeid = None
115 diffstart = 0
115 diffstart = 0
116
116
117 for line in file(pf):
117 for line in file(pf):
118 line = line.rstrip()
118 line = line.rstrip()
119 if (line.startswith('diff --git')
119 if (line.startswith('diff --git')
120 or (diffstart and line.startswith('+++ '))):
120 or (diffstart and line.startswith('+++ '))):
121 diffstart = 2
121 diffstart = 2
122 break
122 break
123 diffstart = 0 # reset
123 diffstart = 0 # reset
124 if line.startswith("--- "):
124 if line.startswith("--- "):
125 diffstart = 1
125 diffstart = 1
126 continue
126 continue
127 elif format == "hgpatch":
127 elif format == "hgpatch":
128 # parse values when importing the result of an hg export
128 # parse values when importing the result of an hg export
129 if line.startswith("# User "):
129 if line.startswith("# User "):
130 user = line[7:]
130 user = line[7:]
131 elif line.startswith("# Date "):
131 elif line.startswith("# Date "):
132 date = line[7:]
132 date = line[7:]
133 elif line.startswith("# Parent "):
133 elif line.startswith("# Parent "):
134 parent = line[9:].lstrip()
134 parent = line[9:].lstrip()
135 elif line.startswith("# Branch "):
135 elif line.startswith("# Branch "):
136 branch = line[9:]
136 branch = line[9:]
137 elif line.startswith("# Node ID "):
137 elif line.startswith("# Node ID "):
138 nodeid = line[10:]
138 nodeid = line[10:]
139 elif not line.startswith("# ") and line:
139 elif not line.startswith("# ") and line:
140 message.append(line)
140 message.append(line)
141 format = None
141 format = None
142 elif line == '# HG changeset patch':
142 elif line == '# HG changeset patch':
143 message = []
143 message = []
144 format = "hgpatch"
144 format = "hgpatch"
145 elif (format != "tagdone" and (line.startswith("Subject: ") or
145 elif (format != "tagdone" and (line.startswith("Subject: ") or
146 line.startswith("subject: "))):
146 line.startswith("subject: "))):
147 subject = line[9:]
147 subject = line[9:]
148 format = "tag"
148 format = "tag"
149 elif (format != "tagdone" and (line.startswith("From: ") or
149 elif (format != "tagdone" and (line.startswith("From: ") or
150 line.startswith("from: "))):
150 line.startswith("from: "))):
151 user = line[6:]
151 user = line[6:]
152 format = "tag"
152 format = "tag"
153 elif (format != "tagdone" and (line.startswith("Date: ") or
153 elif (format != "tagdone" and (line.startswith("Date: ") or
154 line.startswith("date: "))):
154 line.startswith("date: "))):
155 date = line[6:]
155 date = line[6:]
156 format = "tag"
156 format = "tag"
157 elif format == "tag" and line == "":
157 elif format == "tag" and line == "":
158 # when looking for tags (subject: from: etc) they
158 # when looking for tags (subject: from: etc) they
159 # end once you find a blank line in the source
159 # end once you find a blank line in the source
160 format = "tagdone"
160 format = "tagdone"
161 elif message or line:
161 elif message or line:
162 message.append(line)
162 message.append(line)
163 comments.append(line)
163 comments.append(line)
164
164
165 eatdiff(message)
165 eatdiff(message)
166 eatdiff(comments)
166 eatdiff(comments)
167 # Remember the exact starting line of the patch diffs before consuming
167 # Remember the exact starting line of the patch diffs before consuming
168 # empty lines, for external use by TortoiseHg and others
168 # empty lines, for external use by TortoiseHg and others
169 self.diffstartline = len(comments)
169 self.diffstartline = len(comments)
170 eatempty(message)
170 eatempty(message)
171 eatempty(comments)
171 eatempty(comments)
172
172
173 # make sure message isn't empty
173 # make sure message isn't empty
174 if format and format.startswith("tag") and subject:
174 if format and format.startswith("tag") and subject:
175 message.insert(0, "")
175 message.insert(0, "")
176 message.insert(0, subject)
176 message.insert(0, subject)
177
177
178 self.message = message
178 self.message = message
179 self.comments = comments
179 self.comments = comments
180 self.user = user
180 self.user = user
181 self.date = date
181 self.date = date
182 self.parent = parent
182 self.parent = parent
183 # nodeid and branch are for external use by TortoiseHg and others
183 # nodeid and branch are for external use by TortoiseHg and others
184 self.nodeid = nodeid
184 self.nodeid = nodeid
185 self.branch = branch
185 self.branch = branch
186 self.haspatch = diffstart > 1
186 self.haspatch = diffstart > 1
187 self.plainmode = plainmode
187 self.plainmode = plainmode
188
188
189 def setuser(self, user):
189 def setuser(self, user):
190 if not self.updateheader(['From: ', '# User '], user):
190 if not self.updateheader(['From: ', '# User '], user):
191 try:
191 try:
192 patchheaderat = self.comments.index('# HG changeset patch')
192 patchheaderat = self.comments.index('# HG changeset patch')
193 self.comments.insert(patchheaderat + 1, '# User ' + user)
193 self.comments.insert(patchheaderat + 1, '# User ' + user)
194 except ValueError:
194 except ValueError:
195 if self.plainmode or self._hasheader(['Date: ']):
195 if self.plainmode or self._hasheader(['Date: ']):
196 self.comments = ['From: ' + user] + self.comments
196 self.comments = ['From: ' + user] + self.comments
197 else:
197 else:
198 tmp = ['# HG changeset patch', '# User ' + user, '']
198 tmp = ['# HG changeset patch', '# User ' + user, '']
199 self.comments = tmp + self.comments
199 self.comments = tmp + self.comments
200 self.user = user
200 self.user = user
201
201
202 def setdate(self, date):
202 def setdate(self, date):
203 if not self.updateheader(['Date: ', '# Date '], date):
203 if not self.updateheader(['Date: ', '# Date '], date):
204 try:
204 try:
205 patchheaderat = self.comments.index('# HG changeset patch')
205 patchheaderat = self.comments.index('# HG changeset patch')
206 self.comments.insert(patchheaderat + 1, '# Date ' + date)
206 self.comments.insert(patchheaderat + 1, '# Date ' + date)
207 except ValueError:
207 except ValueError:
208 if self.plainmode or self._hasheader(['From: ']):
208 if self.plainmode or self._hasheader(['From: ']):
209 self.comments = ['Date: ' + date] + self.comments
209 self.comments = ['Date: ' + date] + self.comments
210 else:
210 else:
211 tmp = ['# HG changeset patch', '# Date ' + date, '']
211 tmp = ['# HG changeset patch', '# Date ' + date, '']
212 self.comments = tmp + self.comments
212 self.comments = tmp + self.comments
213 self.date = date
213 self.date = date
214
214
215 def setparent(self, parent):
215 def setparent(self, parent):
216 if not self.updateheader(['# Parent '], parent):
216 if not self.updateheader(['# Parent '], parent):
217 try:
217 try:
218 patchheaderat = self.comments.index('# HG changeset patch')
218 patchheaderat = self.comments.index('# HG changeset patch')
219 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
219 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
220 except ValueError:
220 except ValueError:
221 pass
221 pass
222 self.parent = parent
222 self.parent = parent
223
223
224 def setmessage(self, message):
224 def setmessage(self, message):
225 if self.comments:
225 if self.comments:
226 self._delmsg()
226 self._delmsg()
227 self.message = [message]
227 self.message = [message]
228 self.comments += self.message
228 self.comments += self.message
229
229
230 def updateheader(self, prefixes, new):
230 def updateheader(self, prefixes, new):
231 '''Update all references to a field in the patch header.
231 '''Update all references to a field in the patch header.
232 Return whether the field is present.'''
232 Return whether the field is present.'''
233 res = False
233 res = False
234 for prefix in prefixes:
234 for prefix in prefixes:
235 for i in xrange(len(self.comments)):
235 for i in xrange(len(self.comments)):
236 if self.comments[i].startswith(prefix):
236 if self.comments[i].startswith(prefix):
237 self.comments[i] = prefix + new
237 self.comments[i] = prefix + new
238 res = True
238 res = True
239 break
239 break
240 return res
240 return res
241
241
242 def _hasheader(self, prefixes):
242 def _hasheader(self, prefixes):
243 '''Check if a header starts with any of the given prefixes.'''
243 '''Check if a header starts with any of the given prefixes.'''
244 for prefix in prefixes:
244 for prefix in prefixes:
245 for comment in self.comments:
245 for comment in self.comments:
246 if comment.startswith(prefix):
246 if comment.startswith(prefix):
247 return True
247 return True
248 return False
248 return False
249
249
250 def __str__(self):
250 def __str__(self):
251 if not self.comments:
251 if not self.comments:
252 return ''
252 return ''
253 return '\n'.join(self.comments) + '\n\n'
253 return '\n'.join(self.comments) + '\n\n'
254
254
255 def _delmsg(self):
255 def _delmsg(self):
256 '''Remove existing message, keeping the rest of the comments fields.
256 '''Remove existing message, keeping the rest of the comments fields.
257 If comments contains 'subject: ', message will prepend
257 If comments contains 'subject: ', message will prepend
258 the field and a blank line.'''
258 the field and a blank line.'''
259 if self.message:
259 if self.message:
260 subj = 'subject: ' + self.message[0].lower()
260 subj = 'subject: ' + self.message[0].lower()
261 for i in xrange(len(self.comments)):
261 for i in xrange(len(self.comments)):
262 if subj == self.comments[i].lower():
262 if subj == self.comments[i].lower():
263 del self.comments[i]
263 del self.comments[i]
264 self.message = self.message[2:]
264 self.message = self.message[2:]
265 break
265 break
266 ci = 0
266 ci = 0
267 for mi in self.message:
267 for mi in self.message:
268 while mi != self.comments[ci]:
268 while mi != self.comments[ci]:
269 ci += 1
269 ci += 1
270 del self.comments[ci]
270 del self.comments[ci]
271
271
272 def newcommit(repo, phase, *args, **kwargs):
272 def newcommit(repo, phase, *args, **kwargs):
273 """helper dedicated to ensure a commit respect mq.secret setting
273 """helper dedicated to ensure a commit respect mq.secret setting
274
274
275 It should be used instead of repo.commit inside the mq source for operation
275 It should be used instead of repo.commit inside the mq source for operation
276 creating new changeset.
276 creating new changeset.
277 """
277 """
278 if phase is None:
278 if phase is None:
279 if repo.ui.configbool('mq', 'secret', False):
279 if repo.ui.configbool('mq', 'secret', False):
280 phase = phases.secret
280 phase = phases.secret
281 if phase is not None:
281 if phase is not None:
282 backup = repo.ui.backupconfig('phases', 'new-commit')
282 backup = repo.ui.backupconfig('phases', 'new-commit')
283 # Marking the repository as committing an mq patch can be used
283 # Marking the repository as committing an mq patch can be used
284 # to optimize operations like _branchtags().
284 # to optimize operations like _branchtags().
285 repo._committingpatch = True
285 repo._committingpatch = True
286 try:
286 try:
287 if phase is not None:
287 if phase is not None:
288 repo.ui.setconfig('phases', 'new-commit', phase)
288 repo.ui.setconfig('phases', 'new-commit', phase)
289 return repo.commit(*args, **kwargs)
289 return repo.commit(*args, **kwargs)
290 finally:
290 finally:
291 repo._committingpatch = False
291 repo._committingpatch = False
292 if phase is not None:
292 if phase is not None:
293 repo.ui.restoreconfig(backup)
293 repo.ui.restoreconfig(backup)
294
294
295 class AbortNoCleanup(error.Abort):
295 class AbortNoCleanup(error.Abort):
296 pass
296 pass
297
297
298 class queue(object):
298 class queue(object):
299 def __init__(self, ui, path, patchdir=None):
299 def __init__(self, ui, path, patchdir=None):
300 self.basepath = path
300 self.basepath = path
301 try:
301 try:
302 fh = open(os.path.join(path, 'patches.queue'))
302 fh = open(os.path.join(path, 'patches.queue'))
303 cur = fh.read().rstrip()
303 cur = fh.read().rstrip()
304 fh.close()
304 fh.close()
305 if not cur:
305 if not cur:
306 curpath = os.path.join(path, 'patches')
306 curpath = os.path.join(path, 'patches')
307 else:
307 else:
308 curpath = os.path.join(path, 'patches-' + cur)
308 curpath = os.path.join(path, 'patches-' + cur)
309 except IOError:
309 except IOError:
310 curpath = os.path.join(path, 'patches')
310 curpath = os.path.join(path, 'patches')
311 self.path = patchdir or curpath
311 self.path = patchdir or curpath
312 self.opener = scmutil.opener(self.path)
312 self.opener = scmutil.opener(self.path)
313 self.ui = ui
313 self.ui = ui
314 self.applieddirty = False
314 self.applieddirty = False
315 self.seriesdirty = False
315 self.seriesdirty = False
316 self.added = []
316 self.added = []
317 self.seriespath = "series"
317 self.seriespath = "series"
318 self.statuspath = "status"
318 self.statuspath = "status"
319 self.guardspath = "guards"
319 self.guardspath = "guards"
320 self.activeguards = None
320 self.activeguards = None
321 self.guardsdirty = False
321 self.guardsdirty = False
322 # Handle mq.git as a bool with extended values
322 # Handle mq.git as a bool with extended values
323 try:
323 try:
324 gitmode = ui.configbool('mq', 'git', None)
324 gitmode = ui.configbool('mq', 'git', None)
325 if gitmode is None:
325 if gitmode is None:
326 raise error.ConfigError
326 raise error.ConfigError
327 self.gitmode = gitmode and 'yes' or 'no'
327 self.gitmode = gitmode and 'yes' or 'no'
328 except error.ConfigError:
328 except error.ConfigError:
329 self.gitmode = ui.config('mq', 'git', 'auto').lower()
329 self.gitmode = ui.config('mq', 'git', 'auto').lower()
330 self.plainmode = ui.configbool('mq', 'plain', False)
330 self.plainmode = ui.configbool('mq', 'plain', False)
331
331
332 @util.propertycache
332 @util.propertycache
333 def applied(self):
333 def applied(self):
334 def parselines(lines):
334 def parselines(lines):
335 for l in lines:
335 for l in lines:
336 entry = l.split(':', 1)
336 entry = l.split(':', 1)
337 if len(entry) > 1:
337 if len(entry) > 1:
338 n, name = entry
338 n, name = entry
339 yield statusentry(bin(n), name)
339 yield statusentry(bin(n), name)
340 elif l.strip():
340 elif l.strip():
341 self.ui.warn(_('malformated mq status line: %s\n') % entry)
341 self.ui.warn(_('malformated mq status line: %s\n') % entry)
342 # else we ignore empty lines
342 # else we ignore empty lines
343 try:
343 try:
344 lines = self.opener.read(self.statuspath).splitlines()
344 lines = self.opener.read(self.statuspath).splitlines()
345 return list(parselines(lines))
345 return list(parselines(lines))
346 except IOError, e:
346 except IOError, e:
347 if e.errno == errno.ENOENT:
347 if e.errno == errno.ENOENT:
348 return []
348 return []
349 raise
349 raise
350
350
351 @util.propertycache
351 @util.propertycache
352 def fullseries(self):
352 def fullseries(self):
353 try:
353 try:
354 return self.opener.read(self.seriespath).splitlines()
354 return self.opener.read(self.seriespath).splitlines()
355 except IOError, e:
355 except IOError, e:
356 if e.errno == errno.ENOENT:
356 if e.errno == errno.ENOENT:
357 return []
357 return []
358 raise
358 raise
359
359
360 @util.propertycache
360 @util.propertycache
361 def series(self):
361 def series(self):
362 self.parseseries()
362 self.parseseries()
363 return self.series
363 return self.series
364
364
365 @util.propertycache
365 @util.propertycache
366 def seriesguards(self):
366 def seriesguards(self):
367 self.parseseries()
367 self.parseseries()
368 return self.seriesguards
368 return self.seriesguards
369
369
370 def invalidate(self):
370 def invalidate(self):
371 for a in 'applied fullseries series seriesguards'.split():
371 for a in 'applied fullseries series seriesguards'.split():
372 if a in self.__dict__:
372 if a in self.__dict__:
373 delattr(self, a)
373 delattr(self, a)
374 self.applieddirty = False
374 self.applieddirty = False
375 self.seriesdirty = False
375 self.seriesdirty = False
376 self.guardsdirty = False
376 self.guardsdirty = False
377 self.activeguards = None
377 self.activeguards = None
378
378
379 def diffopts(self, opts={}, patchfn=None):
379 def diffopts(self, opts={}, patchfn=None):
380 diffopts = patchmod.diffopts(self.ui, opts)
380 diffopts = patchmod.diffopts(self.ui, opts)
381 if self.gitmode == 'auto':
381 if self.gitmode == 'auto':
382 diffopts.upgrade = True
382 diffopts.upgrade = True
383 elif self.gitmode == 'keep':
383 elif self.gitmode == 'keep':
384 pass
384 pass
385 elif self.gitmode in ('yes', 'no'):
385 elif self.gitmode in ('yes', 'no'):
386 diffopts.git = self.gitmode == 'yes'
386 diffopts.git = self.gitmode == 'yes'
387 else:
387 else:
388 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
388 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
389 ' got %s') % self.gitmode)
389 ' got %s') % self.gitmode)
390 if patchfn:
390 if patchfn:
391 diffopts = self.patchopts(diffopts, patchfn)
391 diffopts = self.patchopts(diffopts, patchfn)
392 return diffopts
392 return diffopts
393
393
394 def patchopts(self, diffopts, *patches):
394 def patchopts(self, diffopts, *patches):
395 """Return a copy of input diff options with git set to true if
395 """Return a copy of input diff options with git set to true if
396 referenced patch is a git patch and should be preserved as such.
396 referenced patch is a git patch and should be preserved as such.
397 """
397 """
398 diffopts = diffopts.copy()
398 diffopts = diffopts.copy()
399 if not diffopts.git and self.gitmode == 'keep':
399 if not diffopts.git and self.gitmode == 'keep':
400 for patchfn in patches:
400 for patchfn in patches:
401 patchf = self.opener(patchfn, 'r')
401 patchf = self.opener(patchfn, 'r')
402 # if the patch was a git patch, refresh it as a git patch
402 # if the patch was a git patch, refresh it as a git patch
403 for line in patchf:
403 for line in patchf:
404 if line.startswith('diff --git'):
404 if line.startswith('diff --git'):
405 diffopts.git = True
405 diffopts.git = True
406 break
406 break
407 patchf.close()
407 patchf.close()
408 return diffopts
408 return diffopts
409
409
410 def join(self, *p):
410 def join(self, *p):
411 return os.path.join(self.path, *p)
411 return os.path.join(self.path, *p)
412
412
413 def findseries(self, patch):
413 def findseries(self, patch):
414 def matchpatch(l):
414 def matchpatch(l):
415 l = l.split('#', 1)[0]
415 l = l.split('#', 1)[0]
416 return l.strip() == patch
416 return l.strip() == patch
417 for index, l in enumerate(self.fullseries):
417 for index, l in enumerate(self.fullseries):
418 if matchpatch(l):
418 if matchpatch(l):
419 return index
419 return index
420 return None
420 return None
421
421
422 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
422 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
423
423
424 def parseseries(self):
424 def parseseries(self):
425 self.series = []
425 self.series = []
426 self.seriesguards = []
426 self.seriesguards = []
427 for l in self.fullseries:
427 for l in self.fullseries:
428 h = l.find('#')
428 h = l.find('#')
429 if h == -1:
429 if h == -1:
430 patch = l
430 patch = l
431 comment = ''
431 comment = ''
432 elif h == 0:
432 elif h == 0:
433 continue
433 continue
434 else:
434 else:
435 patch = l[:h]
435 patch = l[:h]
436 comment = l[h:]
436 comment = l[h:]
437 patch = patch.strip()
437 patch = patch.strip()
438 if patch:
438 if patch:
439 if patch in self.series:
439 if patch in self.series:
440 raise util.Abort(_('%s appears more than once in %s') %
440 raise util.Abort(_('%s appears more than once in %s') %
441 (patch, self.join(self.seriespath)))
441 (patch, self.join(self.seriespath)))
442 self.series.append(patch)
442 self.series.append(patch)
443 self.seriesguards.append(self.guard_re.findall(comment))
443 self.seriesguards.append(self.guard_re.findall(comment))
444
444
445 def checkguard(self, guard):
445 def checkguard(self, guard):
446 if not guard:
446 if not guard:
447 return _('guard cannot be an empty string')
447 return _('guard cannot be an empty string')
448 bad_chars = '# \t\r\n\f'
448 bad_chars = '# \t\r\n\f'
449 first = guard[0]
449 first = guard[0]
450 if first in '-+':
450 if first in '-+':
451 return (_('guard %r starts with invalid character: %r') %
451 return (_('guard %r starts with invalid character: %r') %
452 (guard, first))
452 (guard, first))
453 for c in bad_chars:
453 for c in bad_chars:
454 if c in guard:
454 if c in guard:
455 return _('invalid character in guard %r: %r') % (guard, c)
455 return _('invalid character in guard %r: %r') % (guard, c)
456
456
457 def setactive(self, guards):
457 def setactive(self, guards):
458 for guard in guards:
458 for guard in guards:
459 bad = self.checkguard(guard)
459 bad = self.checkguard(guard)
460 if bad:
460 if bad:
461 raise util.Abort(bad)
461 raise util.Abort(bad)
462 guards = sorted(set(guards))
462 guards = sorted(set(guards))
463 self.ui.debug('active guards: %s\n' % ' '.join(guards))
463 self.ui.debug('active guards: %s\n' % ' '.join(guards))
464 self.activeguards = guards
464 self.activeguards = guards
465 self.guardsdirty = True
465 self.guardsdirty = True
466
466
467 def active(self):
467 def active(self):
468 if self.activeguards is None:
468 if self.activeguards is None:
469 self.activeguards = []
469 self.activeguards = []
470 try:
470 try:
471 guards = self.opener.read(self.guardspath).split()
471 guards = self.opener.read(self.guardspath).split()
472 except IOError, err:
472 except IOError, err:
473 if err.errno != errno.ENOENT:
473 if err.errno != errno.ENOENT:
474 raise
474 raise
475 guards = []
475 guards = []
476 for i, guard in enumerate(guards):
476 for i, guard in enumerate(guards):
477 bad = self.checkguard(guard)
477 bad = self.checkguard(guard)
478 if bad:
478 if bad:
479 self.ui.warn('%s:%d: %s\n' %
479 self.ui.warn('%s:%d: %s\n' %
480 (self.join(self.guardspath), i + 1, bad))
480 (self.join(self.guardspath), i + 1, bad))
481 else:
481 else:
482 self.activeguards.append(guard)
482 self.activeguards.append(guard)
483 return self.activeguards
483 return self.activeguards
484
484
485 def setguards(self, idx, guards):
485 def setguards(self, idx, guards):
486 for g in guards:
486 for g in guards:
487 if len(g) < 2:
487 if len(g) < 2:
488 raise util.Abort(_('guard %r too short') % g)
488 raise util.Abort(_('guard %r too short') % g)
489 if g[0] not in '-+':
489 if g[0] not in '-+':
490 raise util.Abort(_('guard %r starts with invalid char') % g)
490 raise util.Abort(_('guard %r starts with invalid char') % g)
491 bad = self.checkguard(g[1:])
491 bad = self.checkguard(g[1:])
492 if bad:
492 if bad:
493 raise util.Abort(bad)
493 raise util.Abort(bad)
494 drop = self.guard_re.sub('', self.fullseries[idx])
494 drop = self.guard_re.sub('', self.fullseries[idx])
495 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
495 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
496 self.parseseries()
496 self.parseseries()
497 self.seriesdirty = True
497 self.seriesdirty = True
498
498
499 def pushable(self, idx):
499 def pushable(self, idx):
500 if isinstance(idx, str):
500 if isinstance(idx, str):
501 idx = self.series.index(idx)
501 idx = self.series.index(idx)
502 patchguards = self.seriesguards[idx]
502 patchguards = self.seriesguards[idx]
503 if not patchguards:
503 if not patchguards:
504 return True, None
504 return True, None
505 guards = self.active()
505 guards = self.active()
506 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
506 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
507 if exactneg:
507 if exactneg:
508 return False, repr(exactneg[0])
508 return False, repr(exactneg[0])
509 pos = [g for g in patchguards if g[0] == '+']
509 pos = [g for g in patchguards if g[0] == '+']
510 exactpos = [g for g in pos if g[1:] in guards]
510 exactpos = [g for g in pos if g[1:] in guards]
511 if pos:
511 if pos:
512 if exactpos:
512 if exactpos:
513 return True, repr(exactpos[0])
513 return True, repr(exactpos[0])
514 return False, ' '.join(map(repr, pos))
514 return False, ' '.join(map(repr, pos))
515 return True, ''
515 return True, ''
516
516
517 def explainpushable(self, idx, all_patches=False):
517 def explainpushable(self, idx, all_patches=False):
518 write = all_patches and self.ui.write or self.ui.warn
518 write = all_patches and self.ui.write or self.ui.warn
519 if all_patches or self.ui.verbose:
519 if all_patches or self.ui.verbose:
520 if isinstance(idx, str):
520 if isinstance(idx, str):
521 idx = self.series.index(idx)
521 idx = self.series.index(idx)
522 pushable, why = self.pushable(idx)
522 pushable, why = self.pushable(idx)
523 if all_patches and pushable:
523 if all_patches and pushable:
524 if why is None:
524 if why is None:
525 write(_('allowing %s - no guards in effect\n') %
525 write(_('allowing %s - no guards in effect\n') %
526 self.series[idx])
526 self.series[idx])
527 else:
527 else:
528 if not why:
528 if not why:
529 write(_('allowing %s - no matching negative guards\n') %
529 write(_('allowing %s - no matching negative guards\n') %
530 self.series[idx])
530 self.series[idx])
531 else:
531 else:
532 write(_('allowing %s - guarded by %s\n') %
532 write(_('allowing %s - guarded by %s\n') %
533 (self.series[idx], why))
533 (self.series[idx], why))
534 if not pushable:
534 if not pushable:
535 if why:
535 if why:
536 write(_('skipping %s - guarded by %s\n') %
536 write(_('skipping %s - guarded by %s\n') %
537 (self.series[idx], why))
537 (self.series[idx], why))
538 else:
538 else:
539 write(_('skipping %s - no matching guards\n') %
539 write(_('skipping %s - no matching guards\n') %
540 self.series[idx])
540 self.series[idx])
541
541
542 def savedirty(self):
542 def savedirty(self):
543 def writelist(items, path):
543 def writelist(items, path):
544 fp = self.opener(path, 'w')
544 fp = self.opener(path, 'w')
545 for i in items:
545 for i in items:
546 fp.write("%s\n" % i)
546 fp.write("%s\n" % i)
547 fp.close()
547 fp.close()
548 if self.applieddirty:
548 if self.applieddirty:
549 writelist(map(str, self.applied), self.statuspath)
549 writelist(map(str, self.applied), self.statuspath)
550 self.applieddirty = False
550 self.applieddirty = False
551 if self.seriesdirty:
551 if self.seriesdirty:
552 writelist(self.fullseries, self.seriespath)
552 writelist(self.fullseries, self.seriespath)
553 self.seriesdirty = False
553 self.seriesdirty = False
554 if self.guardsdirty:
554 if self.guardsdirty:
555 writelist(self.activeguards, self.guardspath)
555 writelist(self.activeguards, self.guardspath)
556 self.guardsdirty = False
556 self.guardsdirty = False
557 if self.added:
557 if self.added:
558 qrepo = self.qrepo()
558 qrepo = self.qrepo()
559 if qrepo:
559 if qrepo:
560 qrepo[None].add(f for f in self.added if f not in qrepo[None])
560 qrepo[None].add(f for f in self.added if f not in qrepo[None])
561 self.added = []
561 self.added = []
562
562
563 def removeundo(self, repo):
563 def removeundo(self, repo):
564 undo = repo.sjoin('undo')
564 undo = repo.sjoin('undo')
565 if not os.path.exists(undo):
565 if not os.path.exists(undo):
566 return
566 return
567 try:
567 try:
568 os.unlink(undo)
568 os.unlink(undo)
569 except OSError, inst:
569 except OSError, inst:
570 self.ui.warn(_('error removing undo: %s\n') % str(inst))
570 self.ui.warn(_('error removing undo: %s\n') % str(inst))
571
571
572 def backup(self, repo, files, copy=False):
572 def backup(self, repo, files, copy=False):
573 # backup local changes in --force case
573 # backup local changes in --force case
574 for f in sorted(files):
574 for f in sorted(files):
575 absf = repo.wjoin(f)
575 absf = repo.wjoin(f)
576 if os.path.lexists(absf):
576 if os.path.lexists(absf):
577 self.ui.note(_('saving current version of %s as %s\n') %
577 self.ui.note(_('saving current version of %s as %s\n') %
578 (f, f + '.orig'))
578 (f, f + '.orig'))
579 if copy:
579 if copy:
580 util.copyfile(absf, absf + '.orig')
580 util.copyfile(absf, absf + '.orig')
581 else:
581 else:
582 util.rename(absf, absf + '.orig')
582 util.rename(absf, absf + '.orig')
583
583
584 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
584 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
585 fp=None, changes=None, opts={}):
585 fp=None, changes=None, opts={}):
586 stat = opts.get('stat')
586 stat = opts.get('stat')
587 m = scmutil.match(repo[node1], files, opts)
587 m = scmutil.match(repo[node1], files, opts)
588 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
588 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
589 changes, stat, fp)
589 changes, stat, fp)
590
590
591 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
591 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
592 # first try just applying the patch
592 # first try just applying the patch
593 (err, n) = self.apply(repo, [patch], update_status=False,
593 (err, n) = self.apply(repo, [patch], update_status=False,
594 strict=True, merge=rev)
594 strict=True, merge=rev)
595
595
596 if err == 0:
596 if err == 0:
597 return (err, n)
597 return (err, n)
598
598
599 if n is None:
599 if n is None:
600 raise util.Abort(_("apply failed for patch %s") % patch)
600 raise util.Abort(_("apply failed for patch %s") % patch)
601
601
602 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
602 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
603
603
604 # apply failed, strip away that rev and merge.
604 # apply failed, strip away that rev and merge.
605 hg.clean(repo, head)
605 hg.clean(repo, head)
606 self.strip(repo, [n], update=False, backup='strip')
606 self.strip(repo, [n], update=False, backup='strip')
607
607
608 ctx = repo[rev]
608 ctx = repo[rev]
609 ret = hg.merge(repo, rev)
609 ret = hg.merge(repo, rev)
610 if ret:
610 if ret:
611 raise util.Abort(_("update returned %d") % ret)
611 raise util.Abort(_("update returned %d") % ret)
612 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
612 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
613 if n is None:
613 if n is None:
614 raise util.Abort(_("repo commit failed"))
614 raise util.Abort(_("repo commit failed"))
615 try:
615 try:
616 ph = patchheader(mergeq.join(patch), self.plainmode)
616 ph = patchheader(mergeq.join(patch), self.plainmode)
617 except Exception:
617 except Exception:
618 raise util.Abort(_("unable to read %s") % patch)
618 raise util.Abort(_("unable to read %s") % patch)
619
619
620 diffopts = self.patchopts(diffopts, patch)
620 diffopts = self.patchopts(diffopts, patch)
621 patchf = self.opener(patch, "w")
621 patchf = self.opener(patch, "w")
622 comments = str(ph)
622 comments = str(ph)
623 if comments:
623 if comments:
624 patchf.write(comments)
624 patchf.write(comments)
625 self.printdiff(repo, diffopts, head, n, fp=patchf)
625 self.printdiff(repo, diffopts, head, n, fp=patchf)
626 patchf.close()
626 patchf.close()
627 self.removeundo(repo)
627 self.removeundo(repo)
628 return (0, n)
628 return (0, n)
629
629
630 def qparents(self, repo, rev=None):
630 def qparents(self, repo, rev=None):
631 if rev is None:
631 if rev is None:
632 (p1, p2) = repo.dirstate.parents()
632 (p1, p2) = repo.dirstate.parents()
633 if p2 == nullid:
633 if p2 == nullid:
634 return p1
634 return p1
635 if not self.applied:
635 if not self.applied:
636 return None
636 return None
637 return self.applied[-1].node
637 return self.applied[-1].node
638 p1, p2 = repo.changelog.parents(rev)
638 p1, p2 = repo.changelog.parents(rev)
639 if p2 != nullid and p2 in [x.node for x in self.applied]:
639 if p2 != nullid and p2 in [x.node for x in self.applied]:
640 return p2
640 return p2
641 return p1
641 return p1
642
642
643 def mergepatch(self, repo, mergeq, series, diffopts):
643 def mergepatch(self, repo, mergeq, series, diffopts):
644 if not self.applied:
644 if not self.applied:
645 # each of the patches merged in will have two parents. This
645 # each of the patches merged in will have two parents. This
646 # can confuse the qrefresh, qdiff, and strip code because it
646 # can confuse the qrefresh, qdiff, and strip code because it
647 # needs to know which parent is actually in the patch queue.
647 # needs to know which parent is actually in the patch queue.
648 # so, we insert a merge marker with only one parent. This way
648 # so, we insert a merge marker with only one parent. This way
649 # the first patch in the queue is never a merge patch
649 # the first patch in the queue is never a merge patch
650 #
650 #
651 pname = ".hg.patches.merge.marker"
651 pname = ".hg.patches.merge.marker"
652 n = newcommit(repo, None, '[mq]: merge marker', force=True)
652 n = newcommit(repo, None, '[mq]: merge marker', force=True)
653 self.removeundo(repo)
653 self.removeundo(repo)
654 self.applied.append(statusentry(n, pname))
654 self.applied.append(statusentry(n, pname))
655 self.applieddirty = True
655 self.applieddirty = True
656
656
657 head = self.qparents(repo)
657 head = self.qparents(repo)
658
658
659 for patch in series:
659 for patch in series:
660 patch = mergeq.lookup(patch, strict=True)
660 patch = mergeq.lookup(patch, strict=True)
661 if not patch:
661 if not patch:
662 self.ui.warn(_("patch %s does not exist\n") % patch)
662 self.ui.warn(_("patch %s does not exist\n") % patch)
663 return (1, None)
663 return (1, None)
664 pushable, reason = self.pushable(patch)
664 pushable, reason = self.pushable(patch)
665 if not pushable:
665 if not pushable:
666 self.explainpushable(patch, all_patches=True)
666 self.explainpushable(patch, all_patches=True)
667 continue
667 continue
668 info = mergeq.isapplied(patch)
668 info = mergeq.isapplied(patch)
669 if not info:
669 if not info:
670 self.ui.warn(_("patch %s is not applied\n") % patch)
670 self.ui.warn(_("patch %s is not applied\n") % patch)
671 return (1, None)
671 return (1, None)
672 rev = info[1]
672 rev = info[1]
673 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
673 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
674 if head:
674 if head:
675 self.applied.append(statusentry(head, patch))
675 self.applied.append(statusentry(head, patch))
676 self.applieddirty = True
676 self.applieddirty = True
677 if err:
677 if err:
678 return (err, head)
678 return (err, head)
679 self.savedirty()
679 self.savedirty()
680 return (0, head)
680 return (0, head)
681
681
682 def patch(self, repo, patchfile):
682 def patch(self, repo, patchfile):
683 '''Apply patchfile to the working directory.
683 '''Apply patchfile to the working directory.
684 patchfile: name of patch file'''
684 patchfile: name of patch file'''
685 files = set()
685 files = set()
686 try:
686 try:
687 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
687 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
688 files=files, eolmode=None)
688 files=files, eolmode=None)
689 return (True, list(files), fuzz)
689 return (True, list(files), fuzz)
690 except Exception, inst:
690 except Exception, inst:
691 self.ui.note(str(inst) + '\n')
691 self.ui.note(str(inst) + '\n')
692 if not self.ui.verbose:
692 if not self.ui.verbose:
693 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
693 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
694 self.ui.traceback()
694 self.ui.traceback()
695 return (False, list(files), False)
695 return (False, list(files), False)
696
696
697 def apply(self, repo, series, list=False, update_status=True,
697 def apply(self, repo, series, list=False, update_status=True,
698 strict=False, patchdir=None, merge=None, all_files=None,
698 strict=False, patchdir=None, merge=None, all_files=None,
699 tobackup=None, keepchanges=False):
699 tobackup=None, keepchanges=False):
700 wlock = lock = tr = None
700 wlock = lock = tr = None
701 try:
701 try:
702 wlock = repo.wlock()
702 wlock = repo.wlock()
703 lock = repo.lock()
703 lock = repo.lock()
704 tr = repo.transaction("qpush")
704 tr = repo.transaction("qpush")
705 try:
705 try:
706 ret = self._apply(repo, series, list, update_status,
706 ret = self._apply(repo, series, list, update_status,
707 strict, patchdir, merge, all_files=all_files,
707 strict, patchdir, merge, all_files=all_files,
708 tobackup=tobackup, keepchanges=keepchanges)
708 tobackup=tobackup, keepchanges=keepchanges)
709 tr.close()
709 tr.close()
710 self.savedirty()
710 self.savedirty()
711 return ret
711 return ret
712 except AbortNoCleanup:
712 except AbortNoCleanup:
713 tr.close()
713 tr.close()
714 self.savedirty()
714 self.savedirty()
715 return 2, repo.dirstate.p1()
715 return 2, repo.dirstate.p1()
716 except: # re-raises
716 except: # re-raises
717 try:
717 try:
718 tr.abort()
718 tr.abort()
719 finally:
719 finally:
720 repo.invalidate()
720 repo.invalidate()
721 repo.dirstate.invalidate()
721 repo.dirstate.invalidate()
722 self.invalidate()
722 self.invalidate()
723 raise
723 raise
724 finally:
724 finally:
725 release(tr, lock, wlock)
725 release(tr, lock, wlock)
726 self.removeundo(repo)
726 self.removeundo(repo)
727
727
728 def _apply(self, repo, series, list=False, update_status=True,
728 def _apply(self, repo, series, list=False, update_status=True,
729 strict=False, patchdir=None, merge=None, all_files=None,
729 strict=False, patchdir=None, merge=None, all_files=None,
730 tobackup=None, keepchanges=False):
730 tobackup=None, keepchanges=False):
731 """returns (error, hash)
731 """returns (error, hash)
732
732
733 error = 1 for unable to read, 2 for patch failed, 3 for patch
733 error = 1 for unable to read, 2 for patch failed, 3 for patch
734 fuzz. tobackup is None or a set of files to backup before they
734 fuzz. tobackup is None or a set of files to backup before they
735 are modified by a patch.
735 are modified by a patch.
736 """
736 """
737 # TODO unify with commands.py
737 # TODO unify with commands.py
738 if not patchdir:
738 if not patchdir:
739 patchdir = self.path
739 patchdir = self.path
740 err = 0
740 err = 0
741 n = None
741 n = None
742 for patchname in series:
742 for patchname in series:
743 pushable, reason = self.pushable(patchname)
743 pushable, reason = self.pushable(patchname)
744 if not pushable:
744 if not pushable:
745 self.explainpushable(patchname, all_patches=True)
745 self.explainpushable(patchname, all_patches=True)
746 continue
746 continue
747 self.ui.status(_("applying %s\n") % patchname)
747 self.ui.status(_("applying %s\n") % patchname)
748 pf = os.path.join(patchdir, patchname)
748 pf = os.path.join(patchdir, patchname)
749
749
750 try:
750 try:
751 ph = patchheader(self.join(patchname), self.plainmode)
751 ph = patchheader(self.join(patchname), self.plainmode)
752 except IOError:
752 except IOError:
753 self.ui.warn(_("unable to read %s\n") % patchname)
753 self.ui.warn(_("unable to read %s\n") % patchname)
754 err = 1
754 err = 1
755 break
755 break
756
756
757 message = ph.message
757 message = ph.message
758 if not message:
758 if not message:
759 # The commit message should not be translated
759 # The commit message should not be translated
760 message = "imported patch %s\n" % patchname
760 message = "imported patch %s\n" % patchname
761 else:
761 else:
762 if list:
762 if list:
763 # The commit message should not be translated
763 # The commit message should not be translated
764 message.append("\nimported patch %s" % patchname)
764 message.append("\nimported patch %s" % patchname)
765 message = '\n'.join(message)
765 message = '\n'.join(message)
766
766
767 if ph.haspatch:
767 if ph.haspatch:
768 if tobackup:
768 if tobackup:
769 touched = patchmod.changedfiles(self.ui, repo, pf)
769 touched = patchmod.changedfiles(self.ui, repo, pf)
770 touched = set(touched) & tobackup
770 touched = set(touched) & tobackup
771 if touched and keepchanges:
771 if touched and keepchanges:
772 raise AbortNoCleanup(
772 raise AbortNoCleanup(
773 _("local changes found, refresh first"))
773 _("local changes found, refresh first"))
774 self.backup(repo, touched, copy=True)
774 self.backup(repo, touched, copy=True)
775 tobackup = tobackup - touched
775 tobackup = tobackup - touched
776 (patcherr, files, fuzz) = self.patch(repo, pf)
776 (patcherr, files, fuzz) = self.patch(repo, pf)
777 if all_files is not None:
777 if all_files is not None:
778 all_files.update(files)
778 all_files.update(files)
779 patcherr = not patcherr
779 patcherr = not patcherr
780 else:
780 else:
781 self.ui.warn(_("patch %s is empty\n") % patchname)
781 self.ui.warn(_("patch %s is empty\n") % patchname)
782 patcherr, files, fuzz = 0, [], 0
782 patcherr, files, fuzz = 0, [], 0
783
783
784 if merge and files:
784 if merge and files:
785 # Mark as removed/merged and update dirstate parent info
785 # Mark as removed/merged and update dirstate parent info
786 removed = []
786 removed = []
787 merged = []
787 merged = []
788 for f in files:
788 for f in files:
789 if os.path.lexists(repo.wjoin(f)):
789 if os.path.lexists(repo.wjoin(f)):
790 merged.append(f)
790 merged.append(f)
791 else:
791 else:
792 removed.append(f)
792 removed.append(f)
793 for f in removed:
793 for f in removed:
794 repo.dirstate.remove(f)
794 repo.dirstate.remove(f)
795 for f in merged:
795 for f in merged:
796 repo.dirstate.merge(f)
796 repo.dirstate.merge(f)
797 p1, p2 = repo.dirstate.parents()
797 p1, p2 = repo.dirstate.parents()
798 repo.setparents(p1, merge)
798 repo.setparents(p1, merge)
799
799
800 match = scmutil.matchfiles(repo, files or [])
800 match = scmutil.matchfiles(repo, files or [])
801 oldtip = repo['tip']
801 oldtip = repo['tip']
802 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
802 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
803 force=True)
803 force=True)
804 if repo['tip'] == oldtip:
804 if repo['tip'] == oldtip:
805 raise util.Abort(_("qpush exactly duplicates child changeset"))
805 raise util.Abort(_("qpush exactly duplicates child changeset"))
806 if n is None:
806 if n is None:
807 raise util.Abort(_("repository commit failed"))
807 raise util.Abort(_("repository commit failed"))
808
808
809 if update_status:
809 if update_status:
810 self.applied.append(statusentry(n, patchname))
810 self.applied.append(statusentry(n, patchname))
811
811
812 if patcherr:
812 if patcherr:
813 self.ui.warn(_("patch failed, rejects left in working dir\n"))
813 self.ui.warn(_("patch failed, rejects left in working dir\n"))
814 err = 2
814 err = 2
815 break
815 break
816
816
817 if fuzz and strict:
817 if fuzz and strict:
818 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
818 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
819 err = 3
819 err = 3
820 break
820 break
821 return (err, n)
821 return (err, n)
822
822
823 def _cleanup(self, patches, numrevs, keep=False):
823 def _cleanup(self, patches, numrevs, keep=False):
824 if not keep:
824 if not keep:
825 r = self.qrepo()
825 r = self.qrepo()
826 if r:
826 if r:
827 r[None].forget(patches)
827 r[None].forget(patches)
828 for p in patches:
828 for p in patches:
829 os.unlink(self.join(p))
829 os.unlink(self.join(p))
830
830
831 qfinished = []
831 qfinished = []
832 if numrevs:
832 if numrevs:
833 qfinished = self.applied[:numrevs]
833 qfinished = self.applied[:numrevs]
834 del self.applied[:numrevs]
834 del self.applied[:numrevs]
835 self.applieddirty = True
835 self.applieddirty = True
836
836
837 unknown = []
837 unknown = []
838
838
839 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
839 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
840 reverse=True):
840 reverse=True):
841 if i is not None:
841 if i is not None:
842 del self.fullseries[i]
842 del self.fullseries[i]
843 else:
843 else:
844 unknown.append(p)
844 unknown.append(p)
845
845
846 if unknown:
846 if unknown:
847 if numrevs:
847 if numrevs:
848 rev = dict((entry.name, entry.node) for entry in qfinished)
848 rev = dict((entry.name, entry.node) for entry in qfinished)
849 for p in unknown:
849 for p in unknown:
850 msg = _('revision %s refers to unknown patches: %s\n')
850 msg = _('revision %s refers to unknown patches: %s\n')
851 self.ui.warn(msg % (short(rev[p]), p))
851 self.ui.warn(msg % (short(rev[p]), p))
852 else:
852 else:
853 msg = _('unknown patches: %s\n')
853 msg = _('unknown patches: %s\n')
854 raise util.Abort(''.join(msg % p for p in unknown))
854 raise util.Abort(''.join(msg % p for p in unknown))
855
855
856 self.parseseries()
856 self.parseseries()
857 self.seriesdirty = True
857 self.seriesdirty = True
858 return [entry.node for entry in qfinished]
858 return [entry.node for entry in qfinished]
859
859
860 def _revpatches(self, repo, revs):
860 def _revpatches(self, repo, revs):
861 firstrev = repo[self.applied[0].node].rev()
861 firstrev = repo[self.applied[0].node].rev()
862 patches = []
862 patches = []
863 for i, rev in enumerate(revs):
863 for i, rev in enumerate(revs):
864
864
865 if rev < firstrev:
865 if rev < firstrev:
866 raise util.Abort(_('revision %d is not managed') % rev)
866 raise util.Abort(_('revision %d is not managed') % rev)
867
867
868 ctx = repo[rev]
868 ctx = repo[rev]
869 base = self.applied[i].node
869 base = self.applied[i].node
870 if ctx.node() != base:
870 if ctx.node() != base:
871 msg = _('cannot delete revision %d above applied patches')
871 msg = _('cannot delete revision %d above applied patches')
872 raise util.Abort(msg % rev)
872 raise util.Abort(msg % rev)
873
873
874 patch = self.applied[i].name
874 patch = self.applied[i].name
875 for fmt in ('[mq]: %s', 'imported patch %s'):
875 for fmt in ('[mq]: %s', 'imported patch %s'):
876 if ctx.description() == fmt % patch:
876 if ctx.description() == fmt % patch:
877 msg = _('patch %s finalized without changeset message\n')
877 msg = _('patch %s finalized without changeset message\n')
878 repo.ui.status(msg % patch)
878 repo.ui.status(msg % patch)
879 break
879 break
880
880
881 patches.append(patch)
881 patches.append(patch)
882 return patches
882 return patches
883
883
884 def finish(self, repo, revs):
884 def finish(self, repo, revs):
885 # Manually trigger phase computation to ensure phasedefaults is
885 # Manually trigger phase computation to ensure phasedefaults is
886 # executed before we remove the patches.
886 # executed before we remove the patches.
887 repo._phasecache
887 repo._phasecache
888 patches = self._revpatches(repo, sorted(revs))
888 patches = self._revpatches(repo, sorted(revs))
889 qfinished = self._cleanup(patches, len(patches))
889 qfinished = self._cleanup(patches, len(patches))
890 if qfinished and repo.ui.configbool('mq', 'secret', False):
890 if qfinished and repo.ui.configbool('mq', 'secret', False):
891 # only use this logic when the secret option is added
891 # only use this logic when the secret option is added
892 oldqbase = repo[qfinished[0]]
892 oldqbase = repo[qfinished[0]]
893 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
893 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
894 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
894 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
895 phases.advanceboundary(repo, tphase, qfinished)
895 phases.advanceboundary(repo, tphase, qfinished)
896
896
897 def delete(self, repo, patches, opts):
897 def delete(self, repo, patches, opts):
898 if not patches and not opts.get('rev'):
898 if not patches and not opts.get('rev'):
899 raise util.Abort(_('qdelete requires at least one revision or '
899 raise util.Abort(_('qdelete requires at least one revision or '
900 'patch name'))
900 'patch name'))
901
901
902 realpatches = []
902 realpatches = []
903 for patch in patches:
903 for patch in patches:
904 patch = self.lookup(patch, strict=True)
904 patch = self.lookup(patch, strict=True)
905 info = self.isapplied(patch)
905 info = self.isapplied(patch)
906 if info:
906 if info:
907 raise util.Abort(_("cannot delete applied patch %s") % patch)
907 raise util.Abort(_("cannot delete applied patch %s") % patch)
908 if patch not in self.series:
908 if patch not in self.series:
909 raise util.Abort(_("patch %s not in series file") % patch)
909 raise util.Abort(_("patch %s not in series file") % patch)
910 if patch not in realpatches:
910 if patch not in realpatches:
911 realpatches.append(patch)
911 realpatches.append(patch)
912
912
913 numrevs = 0
913 numrevs = 0
914 if opts.get('rev'):
914 if opts.get('rev'):
915 if not self.applied:
915 if not self.applied:
916 raise util.Abort(_('no patches applied'))
916 raise util.Abort(_('no patches applied'))
917 revs = scmutil.revrange(repo, opts.get('rev'))
917 revs = scmutil.revrange(repo, opts.get('rev'))
918 if len(revs) > 1 and revs[0] > revs[1]:
918 if len(revs) > 1 and revs[0] > revs[1]:
919 revs.reverse()
919 revs.reverse()
920 revpatches = self._revpatches(repo, revs)
920 revpatches = self._revpatches(repo, revs)
921 realpatches += revpatches
921 realpatches += revpatches
922 numrevs = len(revpatches)
922 numrevs = len(revpatches)
923
923
924 self._cleanup(realpatches, numrevs, opts.get('keep'))
924 self._cleanup(realpatches, numrevs, opts.get('keep'))
925
925
926 def checktoppatch(self, repo):
926 def checktoppatch(self, repo):
927 if self.applied:
927 if self.applied:
928 top = self.applied[-1].node
928 top = self.applied[-1].node
929 patch = self.applied[-1].name
929 patch = self.applied[-1].name
930 pp = repo.dirstate.parents()
930 pp = repo.dirstate.parents()
931 if top not in pp:
931 if top not in pp:
932 raise util.Abort(_("working directory revision is not qtip"))
932 raise util.Abort(_("working directory revision is not qtip"))
933 return top, patch
933 return top, patch
934 return None, None
934 return None, None
935
935
936 def checksubstate(self, repo, baserev=None):
936 def checksubstate(self, repo, baserev=None):
937 '''return list of subrepos at a different revision than substate.
937 '''return list of subrepos at a different revision than substate.
938 Abort if any subrepos have uncommitted changes.'''
938 Abort if any subrepos have uncommitted changes.'''
939 inclsubs = []
939 inclsubs = []
940 wctx = repo[None]
940 wctx = repo[None]
941 if baserev:
941 if baserev:
942 bctx = repo[baserev]
942 bctx = repo[baserev]
943 else:
943 else:
944 bctx = wctx.parents()[0]
944 bctx = wctx.parents()[0]
945 for s in wctx.substate:
945 for s in wctx.substate:
946 if wctx.sub(s).dirty(True):
946 if wctx.sub(s).dirty(True):
947 raise util.Abort(
947 raise util.Abort(
948 _("uncommitted changes in subrepository %s") % s)
948 _("uncommitted changes in subrepository %s") % s)
949 elif s not in bctx.substate or bctx.sub(s).dirty():
949 elif s not in bctx.substate or bctx.sub(s).dirty():
950 inclsubs.append(s)
950 inclsubs.append(s)
951 return inclsubs
951 return inclsubs
952
952
953 def putsubstate2changes(self, substatestate, changes):
953 def putsubstate2changes(self, substatestate, changes):
954 for files in changes[:3]:
954 for files in changes[:3]:
955 if '.hgsubstate' in files:
955 if '.hgsubstate' in files:
956 return # already listed up
956 return # already listed up
957 # not yet listed up
957 # not yet listed up
958 if substatestate in 'a?':
958 if substatestate in 'a?':
959 changes[1].append('.hgsubstate')
959 changes[1].append('.hgsubstate')
960 elif substatestate in 'r':
960 elif substatestate in 'r':
961 changes[2].append('.hgsubstate')
961 changes[2].append('.hgsubstate')
962 else: # modified
962 else: # modified
963 changes[0].append('.hgsubstate')
963 changes[0].append('.hgsubstate')
964
964
965 def localchangesfound(self, refresh=True):
965 def localchangesfound(self, refresh=True):
966 if refresh:
966 if refresh:
967 raise util.Abort(_("local changes found, refresh first"))
967 raise util.Abort(_("local changes found, refresh first"))
968 else:
968 else:
969 raise util.Abort(_("local changes found"))
969 raise util.Abort(_("local changes found"))
970
970
971 def checklocalchanges(self, repo, force=False, refresh=True):
971 def checklocalchanges(self, repo, force=False, refresh=True):
972 m, a, r, d = repo.status()[:4]
972 m, a, r, d = repo.status()[:4]
973 if (m or a or r or d) and not force:
973 if (m or a or r or d) and not force:
974 self.localchangesfound(refresh)
974 self.localchangesfound(refresh)
975 return m, a, r, d
975 return m, a, r, d
976
976
977 _reserved = ('series', 'status', 'guards', '.', '..')
977 _reserved = ('series', 'status', 'guards', '.', '..')
978 def checkreservedname(self, name):
978 def checkreservedname(self, name):
979 if name in self._reserved:
979 if name in self._reserved:
980 raise util.Abort(_('"%s" cannot be used as the name of a patch')
980 raise util.Abort(_('"%s" cannot be used as the name of a patch')
981 % name)
981 % name)
982 for prefix in ('.hg', '.mq'):
982 for prefix in ('.hg', '.mq'):
983 if name.startswith(prefix):
983 if name.startswith(prefix):
984 raise util.Abort(_('patch name cannot begin with "%s"')
984 raise util.Abort(_('patch name cannot begin with "%s"')
985 % prefix)
985 % prefix)
986 for c in ('#', ':'):
986 for c in ('#', ':'):
987 if c in name:
987 if c in name:
988 raise util.Abort(_('"%s" cannot be used in the name of a patch')
988 raise util.Abort(_('"%s" cannot be used in the name of a patch')
989 % c)
989 % c)
990
990
991 def checkpatchname(self, name, force=False):
991 def checkpatchname(self, name, force=False):
992 self.checkreservedname(name)
992 self.checkreservedname(name)
993 if not force and os.path.exists(self.join(name)):
993 if not force and os.path.exists(self.join(name)):
994 if os.path.isdir(self.join(name)):
994 if os.path.isdir(self.join(name)):
995 raise util.Abort(_('"%s" already exists as a directory')
995 raise util.Abort(_('"%s" already exists as a directory')
996 % name)
996 % name)
997 else:
997 else:
998 raise util.Abort(_('patch "%s" already exists') % name)
998 raise util.Abort(_('patch "%s" already exists') % name)
999
999
1000 def checkkeepchanges(self, keepchanges, force):
1000 def checkkeepchanges(self, keepchanges, force):
1001 if force and keepchanges:
1001 if force and keepchanges:
1002 raise util.Abort(_('cannot use both --force and --keep-changes'))
1002 raise util.Abort(_('cannot use both --force and --keep-changes'))
1003
1003
1004 def new(self, repo, patchfn, *pats, **opts):
1004 def new(self, repo, patchfn, *pats, **opts):
1005 """options:
1005 """options:
1006 msg: a string or a no-argument function returning a string
1006 msg: a string or a no-argument function returning a string
1007 """
1007 """
1008 msg = opts.get('msg')
1008 msg = opts.get('msg')
1009 user = opts.get('user')
1009 user = opts.get('user')
1010 date = opts.get('date')
1010 date = opts.get('date')
1011 if date:
1011 if date:
1012 date = util.parsedate(date)
1012 date = util.parsedate(date)
1013 diffopts = self.diffopts({'git': opts.get('git')})
1013 diffopts = self.diffopts({'git': opts.get('git')})
1014 if opts.get('checkname', True):
1014 if opts.get('checkname', True):
1015 self.checkpatchname(patchfn)
1015 self.checkpatchname(patchfn)
1016 inclsubs = self.checksubstate(repo)
1016 inclsubs = self.checksubstate(repo)
1017 if inclsubs:
1017 if inclsubs:
1018 inclsubs.append('.hgsubstate')
1018 inclsubs.append('.hgsubstate')
1019 substatestate = repo.dirstate['.hgsubstate']
1019 substatestate = repo.dirstate['.hgsubstate']
1020 if opts.get('include') or opts.get('exclude') or pats:
1020 if opts.get('include') or opts.get('exclude') or pats:
1021 if inclsubs:
1021 if inclsubs:
1022 pats = list(pats or []) + inclsubs
1022 pats = list(pats or []) + inclsubs
1023 match = scmutil.match(repo[None], pats, opts)
1023 match = scmutil.match(repo[None], pats, opts)
1024 # detect missing files in pats
1024 # detect missing files in pats
1025 def badfn(f, msg):
1025 def badfn(f, msg):
1026 if f != '.hgsubstate': # .hgsubstate is auto-created
1026 if f != '.hgsubstate': # .hgsubstate is auto-created
1027 raise util.Abort('%s: %s' % (f, msg))
1027 raise util.Abort('%s: %s' % (f, msg))
1028 match.bad = badfn
1028 match.bad = badfn
1029 changes = repo.status(match=match)
1029 changes = repo.status(match=match)
1030 m, a, r, d = changes[:4]
1030 m, a, r, d = changes[:4]
1031 else:
1031 else:
1032 changes = self.checklocalchanges(repo, force=True)
1032 changes = self.checklocalchanges(repo, force=True)
1033 m, a, r, d = changes
1033 m, a, r, d = changes
1034 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1034 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1035 if len(repo[None].parents()) > 1:
1035 if len(repo[None].parents()) > 1:
1036 raise util.Abort(_('cannot manage merge changesets'))
1036 raise util.Abort(_('cannot manage merge changesets'))
1037 commitfiles = m + a + r
1037 commitfiles = m + a + r
1038 self.checktoppatch(repo)
1038 self.checktoppatch(repo)
1039 insert = self.fullseriesend()
1039 insert = self.fullseriesend()
1040 wlock = repo.wlock()
1040 wlock = repo.wlock()
1041 try:
1041 try:
1042 try:
1042 try:
1043 # if patch file write fails, abort early
1043 # if patch file write fails, abort early
1044 p = self.opener(patchfn, "w")
1044 p = self.opener(patchfn, "w")
1045 except IOError, e:
1045 except IOError, e:
1046 raise util.Abort(_('cannot write patch "%s": %s')
1046 raise util.Abort(_('cannot write patch "%s": %s')
1047 % (patchfn, e.strerror))
1047 % (patchfn, e.strerror))
1048 try:
1048 try:
1049 if self.plainmode:
1049 if self.plainmode:
1050 if user:
1050 if user:
1051 p.write("From: " + user + "\n")
1051 p.write("From: " + user + "\n")
1052 if not date:
1052 if not date:
1053 p.write("\n")
1053 p.write("\n")
1054 if date:
1054 if date:
1055 p.write("Date: %d %d\n\n" % date)
1055 p.write("Date: %d %d\n\n" % date)
1056 else:
1056 else:
1057 p.write("# HG changeset patch\n")
1057 p.write("# HG changeset patch\n")
1058 p.write("# Parent "
1058 p.write("# Parent "
1059 + hex(repo[None].p1().node()) + "\n")
1059 + hex(repo[None].p1().node()) + "\n")
1060 if user:
1060 if user:
1061 p.write("# User " + user + "\n")
1061 p.write("# User " + user + "\n")
1062 if date:
1062 if date:
1063 p.write("# Date %s %s\n\n" % date)
1063 p.write("# Date %s %s\n\n" % date)
1064 if util.safehasattr(msg, '__call__'):
1064 if util.safehasattr(msg, '__call__'):
1065 msg = msg()
1065 msg = msg()
1066 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1066 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1067 n = newcommit(repo, None, commitmsg, user, date, match=match,
1067 n = newcommit(repo, None, commitmsg, user, date, match=match,
1068 force=True)
1068 force=True)
1069 if n is None:
1069 if n is None:
1070 raise util.Abort(_("repo commit failed"))
1070 raise util.Abort(_("repo commit failed"))
1071 try:
1071 try:
1072 self.fullseries[insert:insert] = [patchfn]
1072 self.fullseries[insert:insert] = [patchfn]
1073 self.applied.append(statusentry(n, patchfn))
1073 self.applied.append(statusentry(n, patchfn))
1074 self.parseseries()
1074 self.parseseries()
1075 self.seriesdirty = True
1075 self.seriesdirty = True
1076 self.applieddirty = True
1076 self.applieddirty = True
1077 if msg:
1077 if msg:
1078 msg = msg + "\n\n"
1078 msg = msg + "\n\n"
1079 p.write(msg)
1079 p.write(msg)
1080 if commitfiles:
1080 if commitfiles:
1081 parent = self.qparents(repo, n)
1081 parent = self.qparents(repo, n)
1082 if inclsubs:
1082 if inclsubs:
1083 self.putsubstate2changes(substatestate, changes)
1083 self.putsubstate2changes(substatestate, changes)
1084 chunks = patchmod.diff(repo, node1=parent, node2=n,
1084 chunks = patchmod.diff(repo, node1=parent, node2=n,
1085 changes=changes, opts=diffopts)
1085 changes=changes, opts=diffopts)
1086 for chunk in chunks:
1086 for chunk in chunks:
1087 p.write(chunk)
1087 p.write(chunk)
1088 p.close()
1088 p.close()
1089 r = self.qrepo()
1089 r = self.qrepo()
1090 if r:
1090 if r:
1091 r[None].add([patchfn])
1091 r[None].add([patchfn])
1092 except: # re-raises
1092 except: # re-raises
1093 repo.rollback()
1093 repo.rollback()
1094 raise
1094 raise
1095 except Exception:
1095 except Exception:
1096 patchpath = self.join(patchfn)
1096 patchpath = self.join(patchfn)
1097 try:
1097 try:
1098 os.unlink(patchpath)
1098 os.unlink(patchpath)
1099 except OSError:
1099 except OSError:
1100 self.ui.warn(_('error unlinking %s\n') % patchpath)
1100 self.ui.warn(_('error unlinking %s\n') % patchpath)
1101 raise
1101 raise
1102 self.removeundo(repo)
1102 self.removeundo(repo)
1103 finally:
1103 finally:
1104 release(wlock)
1104 release(wlock)
1105
1105
1106 def strip(self, repo, revs, update=True, backup="all", force=None):
1106 def strip(self, repo, revs, update=True, backup="all", force=None):
1107 wlock = lock = None
1107 wlock = lock = None
1108 try:
1108 try:
1109 wlock = repo.wlock()
1109 wlock = repo.wlock()
1110 lock = repo.lock()
1110 lock = repo.lock()
1111
1111
1112 if update:
1112 if update:
1113 self.checklocalchanges(repo, force=force, refresh=False)
1113 self.checklocalchanges(repo, force=force, refresh=False)
1114 urev = self.qparents(repo, revs[0])
1114 urev = self.qparents(repo, revs[0])
1115 hg.clean(repo, urev)
1115 hg.clean(repo, urev)
1116 repo.dirstate.write()
1116 repo.dirstate.write()
1117
1117
1118 repair.strip(self.ui, repo, revs, backup)
1118 repair.strip(self.ui, repo, revs, backup)
1119 finally:
1119 finally:
1120 release(lock, wlock)
1120 release(lock, wlock)
1121
1121
1122 def isapplied(self, patch):
1122 def isapplied(self, patch):
1123 """returns (index, rev, patch)"""
1123 """returns (index, rev, patch)"""
1124 for i, a in enumerate(self.applied):
1124 for i, a in enumerate(self.applied):
1125 if a.name == patch:
1125 if a.name == patch:
1126 return (i, a.node, a.name)
1126 return (i, a.node, a.name)
1127 return None
1127 return None
1128
1128
1129 # if the exact patch name does not exist, we try a few
1129 # if the exact patch name does not exist, we try a few
1130 # variations. If strict is passed, we try only #1
1130 # variations. If strict is passed, we try only #1
1131 #
1131 #
1132 # 1) a number (as string) to indicate an offset in the series file
1132 # 1) a number (as string) to indicate an offset in the series file
1133 # 2) a unique substring of the patch name was given
1133 # 2) a unique substring of the patch name was given
1134 # 3) patchname[-+]num to indicate an offset in the series file
1134 # 3) patchname[-+]num to indicate an offset in the series file
1135 def lookup(self, patch, strict=False):
1135 def lookup(self, patch, strict=False):
1136 def partialname(s):
1136 def partialname(s):
1137 if s in self.series:
1137 if s in self.series:
1138 return s
1138 return s
1139 matches = [x for x in self.series if s in x]
1139 matches = [x for x in self.series if s in x]
1140 if len(matches) > 1:
1140 if len(matches) > 1:
1141 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1141 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1142 for m in matches:
1142 for m in matches:
1143 self.ui.warn(' %s\n' % m)
1143 self.ui.warn(' %s\n' % m)
1144 return None
1144 return None
1145 if matches:
1145 if matches:
1146 return matches[0]
1146 return matches[0]
1147 if self.series and self.applied:
1147 if self.series and self.applied:
1148 if s == 'qtip':
1148 if s == 'qtip':
1149 return self.series[self.seriesend(True)-1]
1149 return self.series[self.seriesend(True)-1]
1150 if s == 'qbase':
1150 if s == 'qbase':
1151 return self.series[0]
1151 return self.series[0]
1152 return None
1152 return None
1153
1153
1154 if patch in self.series:
1154 if patch in self.series:
1155 return patch
1155 return patch
1156
1156
1157 if not os.path.isfile(self.join(patch)):
1157 if not os.path.isfile(self.join(patch)):
1158 try:
1158 try:
1159 sno = int(patch)
1159 sno = int(patch)
1160 except (ValueError, OverflowError):
1160 except (ValueError, OverflowError):
1161 pass
1161 pass
1162 else:
1162 else:
1163 if -len(self.series) <= sno < len(self.series):
1163 if -len(self.series) <= sno < len(self.series):
1164 return self.series[sno]
1164 return self.series[sno]
1165
1165
1166 if not strict:
1166 if not strict:
1167 res = partialname(patch)
1167 res = partialname(patch)
1168 if res:
1168 if res:
1169 return res
1169 return res
1170 minus = patch.rfind('-')
1170 minus = patch.rfind('-')
1171 if minus >= 0:
1171 if minus >= 0:
1172 res = partialname(patch[:minus])
1172 res = partialname(patch[:minus])
1173 if res:
1173 if res:
1174 i = self.series.index(res)
1174 i = self.series.index(res)
1175 try:
1175 try:
1176 off = int(patch[minus + 1:] or 1)
1176 off = int(patch[minus + 1:] or 1)
1177 except (ValueError, OverflowError):
1177 except (ValueError, OverflowError):
1178 pass
1178 pass
1179 else:
1179 else:
1180 if i - off >= 0:
1180 if i - off >= 0:
1181 return self.series[i - off]
1181 return self.series[i - off]
1182 plus = patch.rfind('+')
1182 plus = patch.rfind('+')
1183 if plus >= 0:
1183 if plus >= 0:
1184 res = partialname(patch[:plus])
1184 res = partialname(patch[:plus])
1185 if res:
1185 if res:
1186 i = self.series.index(res)
1186 i = self.series.index(res)
1187 try:
1187 try:
1188 off = int(patch[plus + 1:] or 1)
1188 off = int(patch[plus + 1:] or 1)
1189 except (ValueError, OverflowError):
1189 except (ValueError, OverflowError):
1190 pass
1190 pass
1191 else:
1191 else:
1192 if i + off < len(self.series):
1192 if i + off < len(self.series):
1193 return self.series[i + off]
1193 return self.series[i + off]
1194 raise util.Abort(_("patch %s not in series") % patch)
1194 raise util.Abort(_("patch %s not in series") % patch)
1195
1195
1196 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1196 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1197 all=False, move=False, exact=False, nobackup=False,
1197 all=False, move=False, exact=False, nobackup=False,
1198 keepchanges=False):
1198 keepchanges=False):
1199 self.checkkeepchanges(keepchanges, force)
1199 self.checkkeepchanges(keepchanges, force)
1200 diffopts = self.diffopts()
1200 diffopts = self.diffopts()
1201 wlock = repo.wlock()
1201 wlock = repo.wlock()
1202 try:
1202 try:
1203 heads = []
1203 heads = []
1204 for b, ls in repo.branchmap().iteritems():
1204 for b, ls in repo.branchmap().iteritems():
1205 heads += ls
1205 heads += ls
1206 if not heads:
1206 if not heads:
1207 heads = [nullid]
1207 heads = [nullid]
1208 if repo.dirstate.p1() not in heads and not exact:
1208 if repo.dirstate.p1() not in heads and not exact:
1209 self.ui.status(_("(working directory not at a head)\n"))
1209 self.ui.status(_("(working directory not at a head)\n"))
1210
1210
1211 if not self.series:
1211 if not self.series:
1212 self.ui.warn(_('no patches in series\n'))
1212 self.ui.warn(_('no patches in series\n'))
1213 return 0
1213 return 0
1214
1214
1215 # Suppose our series file is: A B C and the current 'top'
1215 # Suppose our series file is: A B C and the current 'top'
1216 # patch is B. qpush C should be performed (moving forward)
1216 # patch is B. qpush C should be performed (moving forward)
1217 # qpush B is a NOP (no change) qpush A is an error (can't
1217 # qpush B is a NOP (no change) qpush A is an error (can't
1218 # go backwards with qpush)
1218 # go backwards with qpush)
1219 if patch:
1219 if patch:
1220 patch = self.lookup(patch)
1220 patch = self.lookup(patch)
1221 info = self.isapplied(patch)
1221 info = self.isapplied(patch)
1222 if info and info[0] >= len(self.applied) - 1:
1222 if info and info[0] >= len(self.applied) - 1:
1223 self.ui.warn(
1223 self.ui.warn(
1224 _('qpush: %s is already at the top\n') % patch)
1224 _('qpush: %s is already at the top\n') % patch)
1225 return 0
1225 return 0
1226
1226
1227 pushable, reason = self.pushable(patch)
1227 pushable, reason = self.pushable(patch)
1228 if pushable:
1228 if pushable:
1229 if self.series.index(patch) < self.seriesend():
1229 if self.series.index(patch) < self.seriesend():
1230 raise util.Abort(
1230 raise util.Abort(
1231 _("cannot push to a previous patch: %s") % patch)
1231 _("cannot push to a previous patch: %s") % patch)
1232 else:
1232 else:
1233 if reason:
1233 if reason:
1234 reason = _('guarded by %s') % reason
1234 reason = _('guarded by %s') % reason
1235 else:
1235 else:
1236 reason = _('no matching guards')
1236 reason = _('no matching guards')
1237 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1237 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1238 return 1
1238 return 1
1239 elif all:
1239 elif all:
1240 patch = self.series[-1]
1240 patch = self.series[-1]
1241 if self.isapplied(patch):
1241 if self.isapplied(patch):
1242 self.ui.warn(_('all patches are currently applied\n'))
1242 self.ui.warn(_('all patches are currently applied\n'))
1243 return 0
1243 return 0
1244
1244
1245 # Following the above example, starting at 'top' of B:
1245 # Following the above example, starting at 'top' of B:
1246 # qpush should be performed (pushes C), but a subsequent
1246 # qpush should be performed (pushes C), but a subsequent
1247 # qpush without an argument is an error (nothing to
1247 # qpush without an argument is an error (nothing to
1248 # apply). This allows a loop of "...while hg qpush..." to
1248 # apply). This allows a loop of "...while hg qpush..." to
1249 # work as it detects an error when done
1249 # work as it detects an error when done
1250 start = self.seriesend()
1250 start = self.seriesend()
1251 if start == len(self.series):
1251 if start == len(self.series):
1252 self.ui.warn(_('patch series already fully applied\n'))
1252 self.ui.warn(_('patch series already fully applied\n'))
1253 return 1
1253 return 1
1254 if not force and not keepchanges:
1254 if not force and not keepchanges:
1255 self.checklocalchanges(repo, refresh=self.applied)
1255 self.checklocalchanges(repo, refresh=self.applied)
1256
1256
1257 if exact:
1257 if exact:
1258 if keepchanges:
1258 if keepchanges:
1259 raise util.Abort(
1259 raise util.Abort(
1260 _("cannot use --exact and --keep-changes together"))
1260 _("cannot use --exact and --keep-changes together"))
1261 if move:
1261 if move:
1262 raise util.Abort(_('cannot use --exact and --move '
1262 raise util.Abort(_('cannot use --exact and --move '
1263 'together'))
1263 'together'))
1264 if self.applied:
1264 if self.applied:
1265 raise util.Abort(_('cannot push --exact with applied '
1265 raise util.Abort(_('cannot push --exact with applied '
1266 'patches'))
1266 'patches'))
1267 root = self.series[start]
1267 root = self.series[start]
1268 target = patchheader(self.join(root), self.plainmode).parent
1268 target = patchheader(self.join(root), self.plainmode).parent
1269 if not target:
1269 if not target:
1270 raise util.Abort(
1270 raise util.Abort(
1271 _("%s does not have a parent recorded") % root)
1271 _("%s does not have a parent recorded") % root)
1272 if not repo[target] == repo['.']:
1272 if not repo[target] == repo['.']:
1273 hg.update(repo, target)
1273 hg.update(repo, target)
1274
1274
1275 if move:
1275 if move:
1276 if not patch:
1276 if not patch:
1277 raise util.Abort(_("please specify the patch to move"))
1277 raise util.Abort(_("please specify the patch to move"))
1278 for fullstart, rpn in enumerate(self.fullseries):
1278 for fullstart, rpn in enumerate(self.fullseries):
1279 # strip markers for patch guards
1279 # strip markers for patch guards
1280 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1280 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1281 break
1281 break
1282 for i, rpn in enumerate(self.fullseries[fullstart:]):
1282 for i, rpn in enumerate(self.fullseries[fullstart:]):
1283 # strip markers for patch guards
1283 # strip markers for patch guards
1284 if self.guard_re.split(rpn, 1)[0] == patch:
1284 if self.guard_re.split(rpn, 1)[0] == patch:
1285 break
1285 break
1286 index = fullstart + i
1286 index = fullstart + i
1287 assert index < len(self.fullseries)
1287 assert index < len(self.fullseries)
1288 fullpatch = self.fullseries[index]
1288 fullpatch = self.fullseries[index]
1289 del self.fullseries[index]
1289 del self.fullseries[index]
1290 self.fullseries.insert(fullstart, fullpatch)
1290 self.fullseries.insert(fullstart, fullpatch)
1291 self.parseseries()
1291 self.parseseries()
1292 self.seriesdirty = True
1292 self.seriesdirty = True
1293
1293
1294 self.applieddirty = True
1294 self.applieddirty = True
1295 if start > 0:
1295 if start > 0:
1296 self.checktoppatch(repo)
1296 self.checktoppatch(repo)
1297 if not patch:
1297 if not patch:
1298 patch = self.series[start]
1298 patch = self.series[start]
1299 end = start + 1
1299 end = start + 1
1300 else:
1300 else:
1301 end = self.series.index(patch, start) + 1
1301 end = self.series.index(patch, start) + 1
1302
1302
1303 tobackup = set()
1303 tobackup = set()
1304 if (not nobackup and force) or keepchanges:
1304 if (not nobackup and force) or keepchanges:
1305 m, a, r, d = self.checklocalchanges(repo, force=True)
1305 m, a, r, d = self.checklocalchanges(repo, force=True)
1306 if keepchanges:
1306 if keepchanges:
1307 tobackup.update(m + a + r + d)
1307 tobackup.update(m + a + r + d)
1308 else:
1308 else:
1309 tobackup.update(m + a)
1309 tobackup.update(m + a)
1310
1310
1311 s = self.series[start:end]
1311 s = self.series[start:end]
1312 all_files = set()
1312 all_files = set()
1313 try:
1313 try:
1314 if mergeq:
1314 if mergeq:
1315 ret = self.mergepatch(repo, mergeq, s, diffopts)
1315 ret = self.mergepatch(repo, mergeq, s, diffopts)
1316 else:
1316 else:
1317 ret = self.apply(repo, s, list, all_files=all_files,
1317 ret = self.apply(repo, s, list, all_files=all_files,
1318 tobackup=tobackup, keepchanges=keepchanges)
1318 tobackup=tobackup, keepchanges=keepchanges)
1319 except: # re-raises
1319 except: # re-raises
1320 self.ui.warn(_('cleaning up working directory...'))
1320 self.ui.warn(_('cleaning up working directory...'))
1321 node = repo.dirstate.p1()
1321 node = repo.dirstate.p1()
1322 hg.revert(repo, node, None)
1322 hg.revert(repo, node, None)
1323 # only remove unknown files that we know we touched or
1323 # only remove unknown files that we know we touched or
1324 # created while patching
1324 # created while patching
1325 for f in all_files:
1325 for f in all_files:
1326 if f not in repo.dirstate:
1326 if f not in repo.dirstate:
1327 try:
1327 try:
1328 util.unlinkpath(repo.wjoin(f))
1328 util.unlinkpath(repo.wjoin(f))
1329 except OSError, inst:
1329 except OSError, inst:
1330 if inst.errno != errno.ENOENT:
1330 if inst.errno != errno.ENOENT:
1331 raise
1331 raise
1332 self.ui.warn(_('done\n'))
1332 self.ui.warn(_('done\n'))
1333 raise
1333 raise
1334
1334
1335 if not self.applied:
1335 if not self.applied:
1336 return ret[0]
1336 return ret[0]
1337 top = self.applied[-1].name
1337 top = self.applied[-1].name
1338 if ret[0] and ret[0] > 1:
1338 if ret[0] and ret[0] > 1:
1339 msg = _("errors during apply, please fix and refresh %s\n")
1339 msg = _("errors during apply, please fix and refresh %s\n")
1340 self.ui.write(msg % top)
1340 self.ui.write(msg % top)
1341 else:
1341 else:
1342 self.ui.write(_("now at: %s\n") % top)
1342 self.ui.write(_("now at: %s\n") % top)
1343 return ret[0]
1343 return ret[0]
1344
1344
1345 finally:
1345 finally:
1346 wlock.release()
1346 wlock.release()
1347
1347
1348 def pop(self, repo, patch=None, force=False, update=True, all=False,
1348 def pop(self, repo, patch=None, force=False, update=True, all=False,
1349 nobackup=False, keepchanges=False):
1349 nobackup=False, keepchanges=False):
1350 self.checkkeepchanges(keepchanges, force)
1350 self.checkkeepchanges(keepchanges, force)
1351 wlock = repo.wlock()
1351 wlock = repo.wlock()
1352 try:
1352 try:
1353 if patch:
1353 if patch:
1354 # index, rev, patch
1354 # index, rev, patch
1355 info = self.isapplied(patch)
1355 info = self.isapplied(patch)
1356 if not info:
1356 if not info:
1357 patch = self.lookup(patch)
1357 patch = self.lookup(patch)
1358 info = self.isapplied(patch)
1358 info = self.isapplied(patch)
1359 if not info:
1359 if not info:
1360 raise util.Abort(_("patch %s is not applied") % patch)
1360 raise util.Abort(_("patch %s is not applied") % patch)
1361
1361
1362 if not self.applied:
1362 if not self.applied:
1363 # Allow qpop -a to work repeatedly,
1363 # Allow qpop -a to work repeatedly,
1364 # but not qpop without an argument
1364 # but not qpop without an argument
1365 self.ui.warn(_("no patches applied\n"))
1365 self.ui.warn(_("no patches applied\n"))
1366 return not all
1366 return not all
1367
1367
1368 if all:
1368 if all:
1369 start = 0
1369 start = 0
1370 elif patch:
1370 elif patch:
1371 start = info[0] + 1
1371 start = info[0] + 1
1372 else:
1372 else:
1373 start = len(self.applied) - 1
1373 start = len(self.applied) - 1
1374
1374
1375 if start >= len(self.applied):
1375 if start >= len(self.applied):
1376 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1376 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1377 return
1377 return
1378
1378
1379 if not update:
1379 if not update:
1380 parents = repo.dirstate.parents()
1380 parents = repo.dirstate.parents()
1381 rr = [x.node for x in self.applied]
1381 rr = [x.node for x in self.applied]
1382 for p in parents:
1382 for p in parents:
1383 if p in rr:
1383 if p in rr:
1384 self.ui.warn(_("qpop: forcing dirstate update\n"))
1384 self.ui.warn(_("qpop: forcing dirstate update\n"))
1385 update = True
1385 update = True
1386 else:
1386 else:
1387 parents = [p.node() for p in repo[None].parents()]
1387 parents = [p.node() for p in repo[None].parents()]
1388 needupdate = False
1388 needupdate = False
1389 for entry in self.applied[start:]:
1389 for entry in self.applied[start:]:
1390 if entry.node in parents:
1390 if entry.node in parents:
1391 needupdate = True
1391 needupdate = True
1392 break
1392 break
1393 update = needupdate
1393 update = needupdate
1394
1394
1395 tobackup = set()
1395 tobackup = set()
1396 if update:
1396 if update:
1397 m, a, r, d = self.checklocalchanges(
1397 m, a, r, d = self.checklocalchanges(
1398 repo, force=force or keepchanges)
1398 repo, force=force or keepchanges)
1399 if force:
1399 if force:
1400 if not nobackup:
1400 if not nobackup:
1401 tobackup.update(m + a)
1401 tobackup.update(m + a)
1402 elif keepchanges:
1402 elif keepchanges:
1403 tobackup.update(m + a + r + d)
1403 tobackup.update(m + a + r + d)
1404
1404
1405 self.applieddirty = True
1405 self.applieddirty = True
1406 end = len(self.applied)
1406 end = len(self.applied)
1407 rev = self.applied[start].node
1407 rev = self.applied[start].node
1408 if update:
1408 if update:
1409 top = self.checktoppatch(repo)[0]
1409 top = self.checktoppatch(repo)[0]
1410
1410
1411 try:
1411 try:
1412 heads = repo.changelog.heads(rev)
1412 heads = repo.changelog.heads(rev)
1413 except error.LookupError:
1413 except error.LookupError:
1414 node = short(rev)
1414 node = short(rev)
1415 raise util.Abort(_('trying to pop unknown node %s') % node)
1415 raise util.Abort(_('trying to pop unknown node %s') % node)
1416
1416
1417 if heads != [self.applied[-1].node]:
1417 if heads != [self.applied[-1].node]:
1418 raise util.Abort(_("popping would remove a revision not "
1418 raise util.Abort(_("popping would remove a revision not "
1419 "managed by this patch queue"))
1419 "managed by this patch queue"))
1420 if not repo[self.applied[-1].node].mutable():
1420 if not repo[self.applied[-1].node].mutable():
1421 raise util.Abort(
1421 raise util.Abort(
1422 _("popping would remove an immutable revision"),
1422 _("popping would remove an immutable revision"),
1423 hint=_('see "hg help phases" for details'))
1423 hint=_('see "hg help phases" for details'))
1424
1424
1425 # we know there are no local changes, so we can make a simplified
1425 # we know there are no local changes, so we can make a simplified
1426 # form of hg.update.
1426 # form of hg.update.
1427 if update:
1427 if update:
1428 qp = self.qparents(repo, rev)
1428 qp = self.qparents(repo, rev)
1429 ctx = repo[qp]
1429 ctx = repo[qp]
1430 m, a, r, d = repo.status(qp, top)[:4]
1430 m, a, r, d = repo.status(qp, top)[:4]
1431 if d:
1431 if d:
1432 raise util.Abort(_("deletions found between repo revs"))
1432 raise util.Abort(_("deletions found between repo revs"))
1433
1433
1434 tobackup = set(a + m + r) & tobackup
1434 tobackup = set(a + m + r) & tobackup
1435 if keepchanges and tobackup:
1435 if keepchanges and tobackup:
1436 self.localchangesfound()
1436 self.localchangesfound()
1437 self.backup(repo, tobackup)
1437 self.backup(repo, tobackup)
1438
1438
1439 for f in a:
1439 for f in a:
1440 try:
1440 try:
1441 util.unlinkpath(repo.wjoin(f))
1441 util.unlinkpath(repo.wjoin(f))
1442 except OSError, e:
1442 except OSError, e:
1443 if e.errno != errno.ENOENT:
1443 if e.errno != errno.ENOENT:
1444 raise
1444 raise
1445 repo.dirstate.drop(f)
1445 repo.dirstate.drop(f)
1446 for f in m + r:
1446 for f in m + r:
1447 fctx = ctx[f]
1447 fctx = ctx[f]
1448 repo.wwrite(f, fctx.data(), fctx.flags())
1448 repo.wwrite(f, fctx.data(), fctx.flags())
1449 repo.dirstate.normal(f)
1449 repo.dirstate.normal(f)
1450 repo.setparents(qp, nullid)
1450 repo.setparents(qp, nullid)
1451 for patch in reversed(self.applied[start:end]):
1451 for patch in reversed(self.applied[start:end]):
1452 self.ui.status(_("popping %s\n") % patch.name)
1452 self.ui.status(_("popping %s\n") % patch.name)
1453 del self.applied[start:end]
1453 del self.applied[start:end]
1454 self.strip(repo, [rev], update=False, backup='strip')
1454 self.strip(repo, [rev], update=False, backup='strip')
1455 if self.applied:
1455 if self.applied:
1456 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1456 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1457 else:
1457 else:
1458 self.ui.write(_("patch queue now empty\n"))
1458 self.ui.write(_("patch queue now empty\n"))
1459 finally:
1459 finally:
1460 wlock.release()
1460 wlock.release()
1461
1461
1462 def diff(self, repo, pats, opts):
1462 def diff(self, repo, pats, opts):
1463 top, patch = self.checktoppatch(repo)
1463 top, patch = self.checktoppatch(repo)
1464 if not top:
1464 if not top:
1465 self.ui.write(_("no patches applied\n"))
1465 self.ui.write(_("no patches applied\n"))
1466 return
1466 return
1467 qp = self.qparents(repo, top)
1467 qp = self.qparents(repo, top)
1468 if opts.get('reverse'):
1468 if opts.get('reverse'):
1469 node1, node2 = None, qp
1469 node1, node2 = None, qp
1470 else:
1470 else:
1471 node1, node2 = qp, None
1471 node1, node2 = qp, None
1472 diffopts = self.diffopts(opts, patch)
1472 diffopts = self.diffopts(opts, patch)
1473 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1473 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1474
1474
1475 def refresh(self, repo, pats=None, **opts):
1475 def refresh(self, repo, pats=None, **opts):
1476 if not self.applied:
1476 if not self.applied:
1477 self.ui.write(_("no patches applied\n"))
1477 self.ui.write(_("no patches applied\n"))
1478 return 1
1478 return 1
1479 msg = opts.get('msg', '').rstrip()
1479 msg = opts.get('msg', '').rstrip()
1480 newuser = opts.get('user')
1480 newuser = opts.get('user')
1481 newdate = opts.get('date')
1481 newdate = opts.get('date')
1482 if newdate:
1482 if newdate:
1483 newdate = '%d %d' % util.parsedate(newdate)
1483 newdate = '%d %d' % util.parsedate(newdate)
1484 wlock = repo.wlock()
1484 wlock = repo.wlock()
1485
1485
1486 try:
1486 try:
1487 self.checktoppatch(repo)
1487 self.checktoppatch(repo)
1488 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1488 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1489 if repo.changelog.heads(top) != [top]:
1489 if repo.changelog.heads(top) != [top]:
1490 raise util.Abort(_("cannot refresh a revision with children"))
1490 raise util.Abort(_("cannot refresh a revision with children"))
1491 if not repo[top].mutable():
1491 if not repo[top].mutable():
1492 raise util.Abort(_("cannot refresh immutable revision"),
1492 raise util.Abort(_("cannot refresh immutable revision"),
1493 hint=_('see "hg help phases" for details'))
1493 hint=_('see "hg help phases" for details'))
1494
1494
1495 cparents = repo.changelog.parents(top)
1495 cparents = repo.changelog.parents(top)
1496 patchparent = self.qparents(repo, top)
1496 patchparent = self.qparents(repo, top)
1497
1497
1498 inclsubs = self.checksubstate(repo, hex(patchparent))
1498 inclsubs = self.checksubstate(repo, hex(patchparent))
1499 if inclsubs:
1499 if inclsubs:
1500 inclsubs.append('.hgsubstate')
1500 inclsubs.append('.hgsubstate')
1501 substatestate = repo.dirstate['.hgsubstate']
1501 substatestate = repo.dirstate['.hgsubstate']
1502
1502
1503 ph = patchheader(self.join(patchfn), self.plainmode)
1503 ph = patchheader(self.join(patchfn), self.plainmode)
1504 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1504 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1505 if msg:
1505 if msg:
1506 ph.setmessage(msg)
1506 ph.setmessage(msg)
1507 if newuser:
1507 if newuser:
1508 ph.setuser(newuser)
1508 ph.setuser(newuser)
1509 if newdate:
1509 if newdate:
1510 ph.setdate(newdate)
1510 ph.setdate(newdate)
1511 ph.setparent(hex(patchparent))
1511 ph.setparent(hex(patchparent))
1512
1512
1513 # only commit new patch when write is complete
1513 # only commit new patch when write is complete
1514 patchf = self.opener(patchfn, 'w', atomictemp=True)
1514 patchf = self.opener(patchfn, 'w', atomictemp=True)
1515
1515
1516 comments = str(ph)
1516 comments = str(ph)
1517 if comments:
1517 if comments:
1518 patchf.write(comments)
1518 patchf.write(comments)
1519
1519
1520 # update the dirstate in place, strip off the qtip commit
1520 # update the dirstate in place, strip off the qtip commit
1521 # and then commit.
1521 # and then commit.
1522 #
1522 #
1523 # this should really read:
1523 # this should really read:
1524 # mm, dd, aa = repo.status(top, patchparent)[:3]
1524 # mm, dd, aa = repo.status(top, patchparent)[:3]
1525 # but we do it backwards to take advantage of manifest/chlog
1525 # but we do it backwards to take advantage of manifest/changelog
1526 # caching against the next repo.status call
1526 # caching against the next repo.status call
1527 mm, aa, dd = repo.status(patchparent, top)[:3]
1527 mm, aa, dd = repo.status(patchparent, top)[:3]
1528 changes = repo.changelog.read(top)
1528 changes = repo.changelog.read(top)
1529 man = repo.manifest.read(changes[0])
1529 man = repo.manifest.read(changes[0])
1530 aaa = aa[:]
1530 aaa = aa[:]
1531 matchfn = scmutil.match(repo[None], pats, opts)
1531 matchfn = scmutil.match(repo[None], pats, opts)
1532 # in short mode, we only diff the files included in the
1532 # in short mode, we only diff the files included in the
1533 # patch already plus specified files
1533 # patch already plus specified files
1534 if opts.get('short'):
1534 if opts.get('short'):
1535 # if amending a patch, we start with existing
1535 # if amending a patch, we start with existing
1536 # files plus specified files - unfiltered
1536 # files plus specified files - unfiltered
1537 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1537 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1538 # filter with include/exclude options
1538 # filter with include/exclude options
1539 matchfn = scmutil.match(repo[None], opts=opts)
1539 matchfn = scmutil.match(repo[None], opts=opts)
1540 else:
1540 else:
1541 match = scmutil.matchall(repo)
1541 match = scmutil.matchall(repo)
1542 m, a, r, d = repo.status(match=match)[:4]
1542 m, a, r, d = repo.status(match=match)[:4]
1543 mm = set(mm)
1543 mm = set(mm)
1544 aa = set(aa)
1544 aa = set(aa)
1545 dd = set(dd)
1545 dd = set(dd)
1546
1546
1547 # we might end up with files that were added between
1547 # we might end up with files that were added between
1548 # qtip and the dirstate parent, but then changed in the
1548 # qtip and the dirstate parent, but then changed in the
1549 # local dirstate. in this case, we want them to only
1549 # local dirstate. in this case, we want them to only
1550 # show up in the added section
1550 # show up in the added section
1551 for x in m:
1551 for x in m:
1552 if x not in aa:
1552 if x not in aa:
1553 mm.add(x)
1553 mm.add(x)
1554 # we might end up with files added by the local dirstate that
1554 # we might end up with files added by the local dirstate that
1555 # were deleted by the patch. In this case, they should only
1555 # were deleted by the patch. In this case, they should only
1556 # show up in the changed section.
1556 # show up in the changed section.
1557 for x in a:
1557 for x in a:
1558 if x in dd:
1558 if x in dd:
1559 dd.remove(x)
1559 dd.remove(x)
1560 mm.add(x)
1560 mm.add(x)
1561 else:
1561 else:
1562 aa.add(x)
1562 aa.add(x)
1563 # make sure any files deleted in the local dirstate
1563 # make sure any files deleted in the local dirstate
1564 # are not in the add or change column of the patch
1564 # are not in the add or change column of the patch
1565 forget = []
1565 forget = []
1566 for x in d + r:
1566 for x in d + r:
1567 if x in aa:
1567 if x in aa:
1568 aa.remove(x)
1568 aa.remove(x)
1569 forget.append(x)
1569 forget.append(x)
1570 continue
1570 continue
1571 else:
1571 else:
1572 mm.discard(x)
1572 mm.discard(x)
1573 dd.add(x)
1573 dd.add(x)
1574
1574
1575 m = list(mm)
1575 m = list(mm)
1576 r = list(dd)
1576 r = list(dd)
1577 a = list(aa)
1577 a = list(aa)
1578 c = [filter(matchfn, l) for l in (m, a, r)]
1578 c = [filter(matchfn, l) for l in (m, a, r)]
1579 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1579 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1580
1580
1581 try:
1581 try:
1582 if diffopts.git or diffopts.upgrade:
1582 if diffopts.git or diffopts.upgrade:
1583 copies = {}
1583 copies = {}
1584 for dst in a:
1584 for dst in a:
1585 src = repo.dirstate.copied(dst)
1585 src = repo.dirstate.copied(dst)
1586 # during qfold, the source file for copies may
1586 # during qfold, the source file for copies may
1587 # be removed. Treat this as a simple add.
1587 # be removed. Treat this as a simple add.
1588 if src is not None and src in repo.dirstate:
1588 if src is not None and src in repo.dirstate:
1589 copies.setdefault(src, []).append(dst)
1589 copies.setdefault(src, []).append(dst)
1590 repo.dirstate.add(dst)
1590 repo.dirstate.add(dst)
1591 # remember the copies between patchparent and qtip
1591 # remember the copies between patchparent and qtip
1592 for dst in aaa:
1592 for dst in aaa:
1593 f = repo.file(dst)
1593 f = repo.file(dst)
1594 src = f.renamed(man[dst])
1594 src = f.renamed(man[dst])
1595 if src:
1595 if src:
1596 copies.setdefault(src[0], []).extend(
1596 copies.setdefault(src[0], []).extend(
1597 copies.get(dst, []))
1597 copies.get(dst, []))
1598 if dst in a:
1598 if dst in a:
1599 copies[src[0]].append(dst)
1599 copies[src[0]].append(dst)
1600 # we can't copy a file created by the patch itself
1600 # we can't copy a file created by the patch itself
1601 if dst in copies:
1601 if dst in copies:
1602 del copies[dst]
1602 del copies[dst]
1603 for src, dsts in copies.iteritems():
1603 for src, dsts in copies.iteritems():
1604 for dst in dsts:
1604 for dst in dsts:
1605 repo.dirstate.copy(src, dst)
1605 repo.dirstate.copy(src, dst)
1606 else:
1606 else:
1607 for dst in a:
1607 for dst in a:
1608 repo.dirstate.add(dst)
1608 repo.dirstate.add(dst)
1609 # Drop useless copy information
1609 # Drop useless copy information
1610 for f in list(repo.dirstate.copies()):
1610 for f in list(repo.dirstate.copies()):
1611 repo.dirstate.copy(None, f)
1611 repo.dirstate.copy(None, f)
1612 for f in r:
1612 for f in r:
1613 repo.dirstate.remove(f)
1613 repo.dirstate.remove(f)
1614 # if the patch excludes a modified file, mark that
1614 # if the patch excludes a modified file, mark that
1615 # file with mtime=0 so status can see it.
1615 # file with mtime=0 so status can see it.
1616 mm = []
1616 mm = []
1617 for i in xrange(len(m)-1, -1, -1):
1617 for i in xrange(len(m)-1, -1, -1):
1618 if not matchfn(m[i]):
1618 if not matchfn(m[i]):
1619 mm.append(m[i])
1619 mm.append(m[i])
1620 del m[i]
1620 del m[i]
1621 for f in m:
1621 for f in m:
1622 repo.dirstate.normal(f)
1622 repo.dirstate.normal(f)
1623 for f in mm:
1623 for f in mm:
1624 repo.dirstate.normallookup(f)
1624 repo.dirstate.normallookup(f)
1625 for f in forget:
1625 for f in forget:
1626 repo.dirstate.drop(f)
1626 repo.dirstate.drop(f)
1627
1627
1628 if not msg:
1628 if not msg:
1629 if not ph.message:
1629 if not ph.message:
1630 message = "[mq]: %s\n" % patchfn
1630 message = "[mq]: %s\n" % patchfn
1631 else:
1631 else:
1632 message = "\n".join(ph.message)
1632 message = "\n".join(ph.message)
1633 else:
1633 else:
1634 message = msg
1634 message = msg
1635
1635
1636 user = ph.user or changes[1]
1636 user = ph.user or changes[1]
1637
1637
1638 oldphase = repo[top].phase()
1638 oldphase = repo[top].phase()
1639
1639
1640 # assumes strip can roll itself back if interrupted
1640 # assumes strip can roll itself back if interrupted
1641 repo.setparents(*cparents)
1641 repo.setparents(*cparents)
1642 self.applied.pop()
1642 self.applied.pop()
1643 self.applieddirty = True
1643 self.applieddirty = True
1644 self.strip(repo, [top], update=False,
1644 self.strip(repo, [top], update=False,
1645 backup='strip')
1645 backup='strip')
1646 except: # re-raises
1646 except: # re-raises
1647 repo.dirstate.invalidate()
1647 repo.dirstate.invalidate()
1648 raise
1648 raise
1649
1649
1650 try:
1650 try:
1651 # might be nice to attempt to roll back strip after this
1651 # might be nice to attempt to roll back strip after this
1652
1652
1653 # Ensure we create a new changeset in the same phase than
1653 # Ensure we create a new changeset in the same phase than
1654 # the old one.
1654 # the old one.
1655 n = newcommit(repo, oldphase, message, user, ph.date,
1655 n = newcommit(repo, oldphase, message, user, ph.date,
1656 match=match, force=True)
1656 match=match, force=True)
1657 # only write patch after a successful commit
1657 # only write patch after a successful commit
1658 if inclsubs:
1658 if inclsubs:
1659 self.putsubstate2changes(substatestate, c)
1659 self.putsubstate2changes(substatestate, c)
1660 chunks = patchmod.diff(repo, patchparent,
1660 chunks = patchmod.diff(repo, patchparent,
1661 changes=c, opts=diffopts)
1661 changes=c, opts=diffopts)
1662 for chunk in chunks:
1662 for chunk in chunks:
1663 patchf.write(chunk)
1663 patchf.write(chunk)
1664 patchf.close()
1664 patchf.close()
1665 self.applied.append(statusentry(n, patchfn))
1665 self.applied.append(statusentry(n, patchfn))
1666 except: # re-raises
1666 except: # re-raises
1667 ctx = repo[cparents[0]]
1667 ctx = repo[cparents[0]]
1668 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1668 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1669 self.savedirty()
1669 self.savedirty()
1670 self.ui.warn(_('refresh interrupted while patch was popped! '
1670 self.ui.warn(_('refresh interrupted while patch was popped! '
1671 '(revert --all, qpush to recover)\n'))
1671 '(revert --all, qpush to recover)\n'))
1672 raise
1672 raise
1673 finally:
1673 finally:
1674 wlock.release()
1674 wlock.release()
1675 self.removeundo(repo)
1675 self.removeundo(repo)
1676
1676
1677 def init(self, repo, create=False):
1677 def init(self, repo, create=False):
1678 if not create and os.path.isdir(self.path):
1678 if not create and os.path.isdir(self.path):
1679 raise util.Abort(_("patch queue directory already exists"))
1679 raise util.Abort(_("patch queue directory already exists"))
1680 try:
1680 try:
1681 os.mkdir(self.path)
1681 os.mkdir(self.path)
1682 except OSError, inst:
1682 except OSError, inst:
1683 if inst.errno != errno.EEXIST or not create:
1683 if inst.errno != errno.EEXIST or not create:
1684 raise
1684 raise
1685 if create:
1685 if create:
1686 return self.qrepo(create=True)
1686 return self.qrepo(create=True)
1687
1687
1688 def unapplied(self, repo, patch=None):
1688 def unapplied(self, repo, patch=None):
1689 if patch and patch not in self.series:
1689 if patch and patch not in self.series:
1690 raise util.Abort(_("patch %s is not in series file") % patch)
1690 raise util.Abort(_("patch %s is not in series file") % patch)
1691 if not patch:
1691 if not patch:
1692 start = self.seriesend()
1692 start = self.seriesend()
1693 else:
1693 else:
1694 start = self.series.index(patch) + 1
1694 start = self.series.index(patch) + 1
1695 unapplied = []
1695 unapplied = []
1696 for i in xrange(start, len(self.series)):
1696 for i in xrange(start, len(self.series)):
1697 pushable, reason = self.pushable(i)
1697 pushable, reason = self.pushable(i)
1698 if pushable:
1698 if pushable:
1699 unapplied.append((i, self.series[i]))
1699 unapplied.append((i, self.series[i]))
1700 self.explainpushable(i)
1700 self.explainpushable(i)
1701 return unapplied
1701 return unapplied
1702
1702
1703 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1703 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1704 summary=False):
1704 summary=False):
1705 def displayname(pfx, patchname, state):
1705 def displayname(pfx, patchname, state):
1706 if pfx:
1706 if pfx:
1707 self.ui.write(pfx)
1707 self.ui.write(pfx)
1708 if summary:
1708 if summary:
1709 ph = patchheader(self.join(patchname), self.plainmode)
1709 ph = patchheader(self.join(patchname), self.plainmode)
1710 msg = ph.message and ph.message[0] or ''
1710 msg = ph.message and ph.message[0] or ''
1711 if self.ui.formatted():
1711 if self.ui.formatted():
1712 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1712 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1713 if width > 0:
1713 if width > 0:
1714 msg = util.ellipsis(msg, width)
1714 msg = util.ellipsis(msg, width)
1715 else:
1715 else:
1716 msg = ''
1716 msg = ''
1717 self.ui.write(patchname, label='qseries.' + state)
1717 self.ui.write(patchname, label='qseries.' + state)
1718 self.ui.write(': ')
1718 self.ui.write(': ')
1719 self.ui.write(msg, label='qseries.message.' + state)
1719 self.ui.write(msg, label='qseries.message.' + state)
1720 else:
1720 else:
1721 self.ui.write(patchname, label='qseries.' + state)
1721 self.ui.write(patchname, label='qseries.' + state)
1722 self.ui.write('\n')
1722 self.ui.write('\n')
1723
1723
1724 applied = set([p.name for p in self.applied])
1724 applied = set([p.name for p in self.applied])
1725 if length is None:
1725 if length is None:
1726 length = len(self.series) - start
1726 length = len(self.series) - start
1727 if not missing:
1727 if not missing:
1728 if self.ui.verbose:
1728 if self.ui.verbose:
1729 idxwidth = len(str(start + length - 1))
1729 idxwidth = len(str(start + length - 1))
1730 for i in xrange(start, start + length):
1730 for i in xrange(start, start + length):
1731 patch = self.series[i]
1731 patch = self.series[i]
1732 if patch in applied:
1732 if patch in applied:
1733 char, state = 'A', 'applied'
1733 char, state = 'A', 'applied'
1734 elif self.pushable(i)[0]:
1734 elif self.pushable(i)[0]:
1735 char, state = 'U', 'unapplied'
1735 char, state = 'U', 'unapplied'
1736 else:
1736 else:
1737 char, state = 'G', 'guarded'
1737 char, state = 'G', 'guarded'
1738 pfx = ''
1738 pfx = ''
1739 if self.ui.verbose:
1739 if self.ui.verbose:
1740 pfx = '%*d %s ' % (idxwidth, i, char)
1740 pfx = '%*d %s ' % (idxwidth, i, char)
1741 elif status and status != char:
1741 elif status and status != char:
1742 continue
1742 continue
1743 displayname(pfx, patch, state)
1743 displayname(pfx, patch, state)
1744 else:
1744 else:
1745 msng_list = []
1745 msng_list = []
1746 for root, dirs, files in os.walk(self.path):
1746 for root, dirs, files in os.walk(self.path):
1747 d = root[len(self.path) + 1:]
1747 d = root[len(self.path) + 1:]
1748 for f in files:
1748 for f in files:
1749 fl = os.path.join(d, f)
1749 fl = os.path.join(d, f)
1750 if (fl not in self.series and
1750 if (fl not in self.series and
1751 fl not in (self.statuspath, self.seriespath,
1751 fl not in (self.statuspath, self.seriespath,
1752 self.guardspath)
1752 self.guardspath)
1753 and not fl.startswith('.')):
1753 and not fl.startswith('.')):
1754 msng_list.append(fl)
1754 msng_list.append(fl)
1755 for x in sorted(msng_list):
1755 for x in sorted(msng_list):
1756 pfx = self.ui.verbose and ('D ') or ''
1756 pfx = self.ui.verbose and ('D ') or ''
1757 displayname(pfx, x, 'missing')
1757 displayname(pfx, x, 'missing')
1758
1758
1759 def issaveline(self, l):
1759 def issaveline(self, l):
1760 if l.name == '.hg.patches.save.line':
1760 if l.name == '.hg.patches.save.line':
1761 return True
1761 return True
1762
1762
1763 def qrepo(self, create=False):
1763 def qrepo(self, create=False):
1764 ui = self.ui.copy()
1764 ui = self.ui.copy()
1765 ui.setconfig('paths', 'default', '', overlay=False)
1765 ui.setconfig('paths', 'default', '', overlay=False)
1766 ui.setconfig('paths', 'default-push', '', overlay=False)
1766 ui.setconfig('paths', 'default-push', '', overlay=False)
1767 if create or os.path.isdir(self.join(".hg")):
1767 if create or os.path.isdir(self.join(".hg")):
1768 return hg.repository(ui, path=self.path, create=create)
1768 return hg.repository(ui, path=self.path, create=create)
1769
1769
1770 def restore(self, repo, rev, delete=None, qupdate=None):
1770 def restore(self, repo, rev, delete=None, qupdate=None):
1771 desc = repo[rev].description().strip()
1771 desc = repo[rev].description().strip()
1772 lines = desc.splitlines()
1772 lines = desc.splitlines()
1773 i = 0
1773 i = 0
1774 datastart = None
1774 datastart = None
1775 series = []
1775 series = []
1776 applied = []
1776 applied = []
1777 qpp = None
1777 qpp = None
1778 for i, line in enumerate(lines):
1778 for i, line in enumerate(lines):
1779 if line == 'Patch Data:':
1779 if line == 'Patch Data:':
1780 datastart = i + 1
1780 datastart = i + 1
1781 elif line.startswith('Dirstate:'):
1781 elif line.startswith('Dirstate:'):
1782 l = line.rstrip()
1782 l = line.rstrip()
1783 l = l[10:].split(' ')
1783 l = l[10:].split(' ')
1784 qpp = [bin(x) for x in l]
1784 qpp = [bin(x) for x in l]
1785 elif datastart is not None:
1785 elif datastart is not None:
1786 l = line.rstrip()
1786 l = line.rstrip()
1787 n, name = l.split(':', 1)
1787 n, name = l.split(':', 1)
1788 if n:
1788 if n:
1789 applied.append(statusentry(bin(n), name))
1789 applied.append(statusentry(bin(n), name))
1790 else:
1790 else:
1791 series.append(l)
1791 series.append(l)
1792 if datastart is None:
1792 if datastart is None:
1793 self.ui.warn(_("no saved patch data found\n"))
1793 self.ui.warn(_("no saved patch data found\n"))
1794 return 1
1794 return 1
1795 self.ui.warn(_("restoring status: %s\n") % lines[0])
1795 self.ui.warn(_("restoring status: %s\n") % lines[0])
1796 self.fullseries = series
1796 self.fullseries = series
1797 self.applied = applied
1797 self.applied = applied
1798 self.parseseries()
1798 self.parseseries()
1799 self.seriesdirty = True
1799 self.seriesdirty = True
1800 self.applieddirty = True
1800 self.applieddirty = True
1801 heads = repo.changelog.heads()
1801 heads = repo.changelog.heads()
1802 if delete:
1802 if delete:
1803 if rev not in heads:
1803 if rev not in heads:
1804 self.ui.warn(_("save entry has children, leaving it alone\n"))
1804 self.ui.warn(_("save entry has children, leaving it alone\n"))
1805 else:
1805 else:
1806 self.ui.warn(_("removing save entry %s\n") % short(rev))
1806 self.ui.warn(_("removing save entry %s\n") % short(rev))
1807 pp = repo.dirstate.parents()
1807 pp = repo.dirstate.parents()
1808 if rev in pp:
1808 if rev in pp:
1809 update = True
1809 update = True
1810 else:
1810 else:
1811 update = False
1811 update = False
1812 self.strip(repo, [rev], update=update, backup='strip')
1812 self.strip(repo, [rev], update=update, backup='strip')
1813 if qpp:
1813 if qpp:
1814 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1814 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1815 (short(qpp[0]), short(qpp[1])))
1815 (short(qpp[0]), short(qpp[1])))
1816 if qupdate:
1816 if qupdate:
1817 self.ui.status(_("updating queue directory\n"))
1817 self.ui.status(_("updating queue directory\n"))
1818 r = self.qrepo()
1818 r = self.qrepo()
1819 if not r:
1819 if not r:
1820 self.ui.warn(_("unable to load queue repository\n"))
1820 self.ui.warn(_("unable to load queue repository\n"))
1821 return 1
1821 return 1
1822 hg.clean(r, qpp[0])
1822 hg.clean(r, qpp[0])
1823
1823
1824 def save(self, repo, msg=None):
1824 def save(self, repo, msg=None):
1825 if not self.applied:
1825 if not self.applied:
1826 self.ui.warn(_("save: no patches applied, exiting\n"))
1826 self.ui.warn(_("save: no patches applied, exiting\n"))
1827 return 1
1827 return 1
1828 if self.issaveline(self.applied[-1]):
1828 if self.issaveline(self.applied[-1]):
1829 self.ui.warn(_("status is already saved\n"))
1829 self.ui.warn(_("status is already saved\n"))
1830 return 1
1830 return 1
1831
1831
1832 if not msg:
1832 if not msg:
1833 msg = _("hg patches saved state")
1833 msg = _("hg patches saved state")
1834 else:
1834 else:
1835 msg = "hg patches: " + msg.rstrip('\r\n')
1835 msg = "hg patches: " + msg.rstrip('\r\n')
1836 r = self.qrepo()
1836 r = self.qrepo()
1837 if r:
1837 if r:
1838 pp = r.dirstate.parents()
1838 pp = r.dirstate.parents()
1839 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1839 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1840 msg += "\n\nPatch Data:\n"
1840 msg += "\n\nPatch Data:\n"
1841 msg += ''.join('%s\n' % x for x in self.applied)
1841 msg += ''.join('%s\n' % x for x in self.applied)
1842 msg += ''.join(':%s\n' % x for x in self.fullseries)
1842 msg += ''.join(':%s\n' % x for x in self.fullseries)
1843 n = repo.commit(msg, force=True)
1843 n = repo.commit(msg, force=True)
1844 if not n:
1844 if not n:
1845 self.ui.warn(_("repo commit failed\n"))
1845 self.ui.warn(_("repo commit failed\n"))
1846 return 1
1846 return 1
1847 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1847 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1848 self.applieddirty = True
1848 self.applieddirty = True
1849 self.removeundo(repo)
1849 self.removeundo(repo)
1850
1850
1851 def fullseriesend(self):
1851 def fullseriesend(self):
1852 if self.applied:
1852 if self.applied:
1853 p = self.applied[-1].name
1853 p = self.applied[-1].name
1854 end = self.findseries(p)
1854 end = self.findseries(p)
1855 if end is None:
1855 if end is None:
1856 return len(self.fullseries)
1856 return len(self.fullseries)
1857 return end + 1
1857 return end + 1
1858 return 0
1858 return 0
1859
1859
1860 def seriesend(self, all_patches=False):
1860 def seriesend(self, all_patches=False):
1861 """If all_patches is False, return the index of the next pushable patch
1861 """If all_patches is False, return the index of the next pushable patch
1862 in the series, or the series length. If all_patches is True, return the
1862 in the series, or the series length. If all_patches is True, return the
1863 index of the first patch past the last applied one.
1863 index of the first patch past the last applied one.
1864 """
1864 """
1865 end = 0
1865 end = 0
1866 def next(start):
1866 def next(start):
1867 if all_patches or start >= len(self.series):
1867 if all_patches or start >= len(self.series):
1868 return start
1868 return start
1869 for i in xrange(start, len(self.series)):
1869 for i in xrange(start, len(self.series)):
1870 p, reason = self.pushable(i)
1870 p, reason = self.pushable(i)
1871 if p:
1871 if p:
1872 return i
1872 return i
1873 self.explainpushable(i)
1873 self.explainpushable(i)
1874 return len(self.series)
1874 return len(self.series)
1875 if self.applied:
1875 if self.applied:
1876 p = self.applied[-1].name
1876 p = self.applied[-1].name
1877 try:
1877 try:
1878 end = self.series.index(p)
1878 end = self.series.index(p)
1879 except ValueError:
1879 except ValueError:
1880 return 0
1880 return 0
1881 return next(end + 1)
1881 return next(end + 1)
1882 return next(end)
1882 return next(end)
1883
1883
1884 def appliedname(self, index):
1884 def appliedname(self, index):
1885 pname = self.applied[index].name
1885 pname = self.applied[index].name
1886 if not self.ui.verbose:
1886 if not self.ui.verbose:
1887 p = pname
1887 p = pname
1888 else:
1888 else:
1889 p = str(self.series.index(pname)) + " " + pname
1889 p = str(self.series.index(pname)) + " " + pname
1890 return p
1890 return p
1891
1891
1892 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1892 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1893 force=None, git=False):
1893 force=None, git=False):
1894 def checkseries(patchname):
1894 def checkseries(patchname):
1895 if patchname in self.series:
1895 if patchname in self.series:
1896 raise util.Abort(_('patch %s is already in the series file')
1896 raise util.Abort(_('patch %s is already in the series file')
1897 % patchname)
1897 % patchname)
1898
1898
1899 if rev:
1899 if rev:
1900 if files:
1900 if files:
1901 raise util.Abort(_('option "-r" not valid when importing '
1901 raise util.Abort(_('option "-r" not valid when importing '
1902 'files'))
1902 'files'))
1903 rev = scmutil.revrange(repo, rev)
1903 rev = scmutil.revrange(repo, rev)
1904 rev.sort(reverse=True)
1904 rev.sort(reverse=True)
1905 elif not files:
1905 elif not files:
1906 raise util.Abort(_('no files or revisions specified'))
1906 raise util.Abort(_('no files or revisions specified'))
1907 if (len(files) > 1 or len(rev) > 1) and patchname:
1907 if (len(files) > 1 or len(rev) > 1) and patchname:
1908 raise util.Abort(_('option "-n" not valid when importing multiple '
1908 raise util.Abort(_('option "-n" not valid when importing multiple '
1909 'patches'))
1909 'patches'))
1910 imported = []
1910 imported = []
1911 if rev:
1911 if rev:
1912 # If mq patches are applied, we can only import revisions
1912 # If mq patches are applied, we can only import revisions
1913 # that form a linear path to qbase.
1913 # that form a linear path to qbase.
1914 # Otherwise, they should form a linear path to a head.
1914 # Otherwise, they should form a linear path to a head.
1915 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1915 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1916 if len(heads) > 1:
1916 if len(heads) > 1:
1917 raise util.Abort(_('revision %d is the root of more than one '
1917 raise util.Abort(_('revision %d is the root of more than one '
1918 'branch') % rev[-1])
1918 'branch') % rev[-1])
1919 if self.applied:
1919 if self.applied:
1920 base = repo.changelog.node(rev[0])
1920 base = repo.changelog.node(rev[0])
1921 if base in [n.node for n in self.applied]:
1921 if base in [n.node for n in self.applied]:
1922 raise util.Abort(_('revision %d is already managed')
1922 raise util.Abort(_('revision %d is already managed')
1923 % rev[0])
1923 % rev[0])
1924 if heads != [self.applied[-1].node]:
1924 if heads != [self.applied[-1].node]:
1925 raise util.Abort(_('revision %d is not the parent of '
1925 raise util.Abort(_('revision %d is not the parent of '
1926 'the queue') % rev[0])
1926 'the queue') % rev[0])
1927 base = repo.changelog.rev(self.applied[0].node)
1927 base = repo.changelog.rev(self.applied[0].node)
1928 lastparent = repo.changelog.parentrevs(base)[0]
1928 lastparent = repo.changelog.parentrevs(base)[0]
1929 else:
1929 else:
1930 if heads != [repo.changelog.node(rev[0])]:
1930 if heads != [repo.changelog.node(rev[0])]:
1931 raise util.Abort(_('revision %d has unmanaged children')
1931 raise util.Abort(_('revision %d has unmanaged children')
1932 % rev[0])
1932 % rev[0])
1933 lastparent = None
1933 lastparent = None
1934
1934
1935 diffopts = self.diffopts({'git': git})
1935 diffopts = self.diffopts({'git': git})
1936 for r in rev:
1936 for r in rev:
1937 if not repo[r].mutable():
1937 if not repo[r].mutable():
1938 raise util.Abort(_('revision %d is not mutable') % r,
1938 raise util.Abort(_('revision %d is not mutable') % r,
1939 hint=_('see "hg help phases" for details'))
1939 hint=_('see "hg help phases" for details'))
1940 p1, p2 = repo.changelog.parentrevs(r)
1940 p1, p2 = repo.changelog.parentrevs(r)
1941 n = repo.changelog.node(r)
1941 n = repo.changelog.node(r)
1942 if p2 != nullrev:
1942 if p2 != nullrev:
1943 raise util.Abort(_('cannot import merge revision %d') % r)
1943 raise util.Abort(_('cannot import merge revision %d') % r)
1944 if lastparent and lastparent != r:
1944 if lastparent and lastparent != r:
1945 raise util.Abort(_('revision %d is not the parent of %d')
1945 raise util.Abort(_('revision %d is not the parent of %d')
1946 % (r, lastparent))
1946 % (r, lastparent))
1947 lastparent = p1
1947 lastparent = p1
1948
1948
1949 if not patchname:
1949 if not patchname:
1950 patchname = normname('%d.diff' % r)
1950 patchname = normname('%d.diff' % r)
1951 checkseries(patchname)
1951 checkseries(patchname)
1952 self.checkpatchname(patchname, force)
1952 self.checkpatchname(patchname, force)
1953 self.fullseries.insert(0, patchname)
1953 self.fullseries.insert(0, patchname)
1954
1954
1955 patchf = self.opener(patchname, "w")
1955 patchf = self.opener(patchname, "w")
1956 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1956 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1957 patchf.close()
1957 patchf.close()
1958
1958
1959 se = statusentry(n, patchname)
1959 se = statusentry(n, patchname)
1960 self.applied.insert(0, se)
1960 self.applied.insert(0, se)
1961
1961
1962 self.added.append(patchname)
1962 self.added.append(patchname)
1963 imported.append(patchname)
1963 imported.append(patchname)
1964 patchname = None
1964 patchname = None
1965 if rev and repo.ui.configbool('mq', 'secret', False):
1965 if rev and repo.ui.configbool('mq', 'secret', False):
1966 # if we added anything with --rev, we must move the secret root
1966 # if we added anything with --rev, we must move the secret root
1967 phases.retractboundary(repo, phases.secret, [n])
1967 phases.retractboundary(repo, phases.secret, [n])
1968 self.parseseries()
1968 self.parseseries()
1969 self.applieddirty = True
1969 self.applieddirty = True
1970 self.seriesdirty = True
1970 self.seriesdirty = True
1971
1971
1972 for i, filename in enumerate(files):
1972 for i, filename in enumerate(files):
1973 if existing:
1973 if existing:
1974 if filename == '-':
1974 if filename == '-':
1975 raise util.Abort(_('-e is incompatible with import from -'))
1975 raise util.Abort(_('-e is incompatible with import from -'))
1976 filename = normname(filename)
1976 filename = normname(filename)
1977 self.checkreservedname(filename)
1977 self.checkreservedname(filename)
1978 originpath = self.join(filename)
1978 originpath = self.join(filename)
1979 if not os.path.isfile(originpath):
1979 if not os.path.isfile(originpath):
1980 raise util.Abort(_("patch %s does not exist") % filename)
1980 raise util.Abort(_("patch %s does not exist") % filename)
1981
1981
1982 if patchname:
1982 if patchname:
1983 self.checkpatchname(patchname, force)
1983 self.checkpatchname(patchname, force)
1984
1984
1985 self.ui.write(_('renaming %s to %s\n')
1985 self.ui.write(_('renaming %s to %s\n')
1986 % (filename, patchname))
1986 % (filename, patchname))
1987 util.rename(originpath, self.join(patchname))
1987 util.rename(originpath, self.join(patchname))
1988 else:
1988 else:
1989 patchname = filename
1989 patchname = filename
1990
1990
1991 else:
1991 else:
1992 if filename == '-' and not patchname:
1992 if filename == '-' and not patchname:
1993 raise util.Abort(_('need --name to import a patch from -'))
1993 raise util.Abort(_('need --name to import a patch from -'))
1994 elif not patchname:
1994 elif not patchname:
1995 patchname = normname(os.path.basename(filename.rstrip('/')))
1995 patchname = normname(os.path.basename(filename.rstrip('/')))
1996 self.checkpatchname(patchname, force)
1996 self.checkpatchname(patchname, force)
1997 try:
1997 try:
1998 if filename == '-':
1998 if filename == '-':
1999 text = self.ui.fin.read()
1999 text = self.ui.fin.read()
2000 else:
2000 else:
2001 fp = url.open(self.ui, filename)
2001 fp = url.open(self.ui, filename)
2002 text = fp.read()
2002 text = fp.read()
2003 fp.close()
2003 fp.close()
2004 except (OSError, IOError):
2004 except (OSError, IOError):
2005 raise util.Abort(_("unable to read file %s") % filename)
2005 raise util.Abort(_("unable to read file %s") % filename)
2006 patchf = self.opener(patchname, "w")
2006 patchf = self.opener(patchname, "w")
2007 patchf.write(text)
2007 patchf.write(text)
2008 patchf.close()
2008 patchf.close()
2009 if not force:
2009 if not force:
2010 checkseries(patchname)
2010 checkseries(patchname)
2011 if patchname not in self.series:
2011 if patchname not in self.series:
2012 index = self.fullseriesend() + i
2012 index = self.fullseriesend() + i
2013 self.fullseries[index:index] = [patchname]
2013 self.fullseries[index:index] = [patchname]
2014 self.parseseries()
2014 self.parseseries()
2015 self.seriesdirty = True
2015 self.seriesdirty = True
2016 self.ui.warn(_("adding %s to series file\n") % patchname)
2016 self.ui.warn(_("adding %s to series file\n") % patchname)
2017 self.added.append(patchname)
2017 self.added.append(patchname)
2018 imported.append(patchname)
2018 imported.append(patchname)
2019 patchname = None
2019 patchname = None
2020
2020
2021 self.removeundo(repo)
2021 self.removeundo(repo)
2022 return imported
2022 return imported
2023
2023
2024 def fixkeepchangesopts(ui, opts):
2024 def fixkeepchangesopts(ui, opts):
2025 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2025 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2026 or opts.get('exact')):
2026 or opts.get('exact')):
2027 return opts
2027 return opts
2028 opts = dict(opts)
2028 opts = dict(opts)
2029 opts['keep_changes'] = True
2029 opts['keep_changes'] = True
2030 return opts
2030 return opts
2031
2031
2032 @command("qdelete|qremove|qrm",
2032 @command("qdelete|qremove|qrm",
2033 [('k', 'keep', None, _('keep patch file')),
2033 [('k', 'keep', None, _('keep patch file')),
2034 ('r', 'rev', [],
2034 ('r', 'rev', [],
2035 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2035 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2036 _('hg qdelete [-k] [PATCH]...'))
2036 _('hg qdelete [-k] [PATCH]...'))
2037 def delete(ui, repo, *patches, **opts):
2037 def delete(ui, repo, *patches, **opts):
2038 """remove patches from queue
2038 """remove patches from queue
2039
2039
2040 The patches must not be applied, and at least one patch is required. Exact
2040 The patches must not be applied, and at least one patch is required. Exact
2041 patch identifiers must be given. With -k/--keep, the patch files are
2041 patch identifiers must be given. With -k/--keep, the patch files are
2042 preserved in the patch directory.
2042 preserved in the patch directory.
2043
2043
2044 To stop managing a patch and move it into permanent history,
2044 To stop managing a patch and move it into permanent history,
2045 use the :hg:`qfinish` command."""
2045 use the :hg:`qfinish` command."""
2046 q = repo.mq
2046 q = repo.mq
2047 q.delete(repo, patches, opts)
2047 q.delete(repo, patches, opts)
2048 q.savedirty()
2048 q.savedirty()
2049 return 0
2049 return 0
2050
2050
2051 @command("qapplied",
2051 @command("qapplied",
2052 [('1', 'last', None, _('show only the preceding applied patch'))
2052 [('1', 'last', None, _('show only the preceding applied patch'))
2053 ] + seriesopts,
2053 ] + seriesopts,
2054 _('hg qapplied [-1] [-s] [PATCH]'))
2054 _('hg qapplied [-1] [-s] [PATCH]'))
2055 def applied(ui, repo, patch=None, **opts):
2055 def applied(ui, repo, patch=None, **opts):
2056 """print the patches already applied
2056 """print the patches already applied
2057
2057
2058 Returns 0 on success."""
2058 Returns 0 on success."""
2059
2059
2060 q = repo.mq
2060 q = repo.mq
2061
2061
2062 if patch:
2062 if patch:
2063 if patch not in q.series:
2063 if patch not in q.series:
2064 raise util.Abort(_("patch %s is not in series file") % patch)
2064 raise util.Abort(_("patch %s is not in series file") % patch)
2065 end = q.series.index(patch) + 1
2065 end = q.series.index(patch) + 1
2066 else:
2066 else:
2067 end = q.seriesend(True)
2067 end = q.seriesend(True)
2068
2068
2069 if opts.get('last') and not end:
2069 if opts.get('last') and not end:
2070 ui.write(_("no patches applied\n"))
2070 ui.write(_("no patches applied\n"))
2071 return 1
2071 return 1
2072 elif opts.get('last') and end == 1:
2072 elif opts.get('last') and end == 1:
2073 ui.write(_("only one patch applied\n"))
2073 ui.write(_("only one patch applied\n"))
2074 return 1
2074 return 1
2075 elif opts.get('last'):
2075 elif opts.get('last'):
2076 start = end - 2
2076 start = end - 2
2077 end = 1
2077 end = 1
2078 else:
2078 else:
2079 start = 0
2079 start = 0
2080
2080
2081 q.qseries(repo, length=end, start=start, status='A',
2081 q.qseries(repo, length=end, start=start, status='A',
2082 summary=opts.get('summary'))
2082 summary=opts.get('summary'))
2083
2083
2084
2084
2085 @command("qunapplied",
2085 @command("qunapplied",
2086 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2086 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2087 _('hg qunapplied [-1] [-s] [PATCH]'))
2087 _('hg qunapplied [-1] [-s] [PATCH]'))
2088 def unapplied(ui, repo, patch=None, **opts):
2088 def unapplied(ui, repo, patch=None, **opts):
2089 """print the patches not yet applied
2089 """print the patches not yet applied
2090
2090
2091 Returns 0 on success."""
2091 Returns 0 on success."""
2092
2092
2093 q = repo.mq
2093 q = repo.mq
2094 if patch:
2094 if patch:
2095 if patch not in q.series:
2095 if patch not in q.series:
2096 raise util.Abort(_("patch %s is not in series file") % patch)
2096 raise util.Abort(_("patch %s is not in series file") % patch)
2097 start = q.series.index(patch) + 1
2097 start = q.series.index(patch) + 1
2098 else:
2098 else:
2099 start = q.seriesend(True)
2099 start = q.seriesend(True)
2100
2100
2101 if start == len(q.series) and opts.get('first'):
2101 if start == len(q.series) and opts.get('first'):
2102 ui.write(_("all patches applied\n"))
2102 ui.write(_("all patches applied\n"))
2103 return 1
2103 return 1
2104
2104
2105 length = opts.get('first') and 1 or None
2105 length = opts.get('first') and 1 or None
2106 q.qseries(repo, start=start, length=length, status='U',
2106 q.qseries(repo, start=start, length=length, status='U',
2107 summary=opts.get('summary'))
2107 summary=opts.get('summary'))
2108
2108
2109 @command("qimport",
2109 @command("qimport",
2110 [('e', 'existing', None, _('import file in patch directory')),
2110 [('e', 'existing', None, _('import file in patch directory')),
2111 ('n', 'name', '',
2111 ('n', 'name', '',
2112 _('name of patch file'), _('NAME')),
2112 _('name of patch file'), _('NAME')),
2113 ('f', 'force', None, _('overwrite existing files')),
2113 ('f', 'force', None, _('overwrite existing files')),
2114 ('r', 'rev', [],
2114 ('r', 'rev', [],
2115 _('place existing revisions under mq control'), _('REV')),
2115 _('place existing revisions under mq control'), _('REV')),
2116 ('g', 'git', None, _('use git extended diff format')),
2116 ('g', 'git', None, _('use git extended diff format')),
2117 ('P', 'push', None, _('qpush after importing'))],
2117 ('P', 'push', None, _('qpush after importing'))],
2118 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2118 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2119 def qimport(ui, repo, *filename, **opts):
2119 def qimport(ui, repo, *filename, **opts):
2120 """import a patch or existing changeset
2120 """import a patch or existing changeset
2121
2121
2122 The patch is inserted into the series after the last applied
2122 The patch is inserted into the series after the last applied
2123 patch. If no patches have been applied, qimport prepends the patch
2123 patch. If no patches have been applied, qimport prepends the patch
2124 to the series.
2124 to the series.
2125
2125
2126 The patch will have the same name as its source file unless you
2126 The patch will have the same name as its source file unless you
2127 give it a new one with -n/--name.
2127 give it a new one with -n/--name.
2128
2128
2129 You can register an existing patch inside the patch directory with
2129 You can register an existing patch inside the patch directory with
2130 the -e/--existing flag.
2130 the -e/--existing flag.
2131
2131
2132 With -f/--force, an existing patch of the same name will be
2132 With -f/--force, an existing patch of the same name will be
2133 overwritten.
2133 overwritten.
2134
2134
2135 An existing changeset may be placed under mq control with -r/--rev
2135 An existing changeset may be placed under mq control with -r/--rev
2136 (e.g. qimport --rev tip -n patch will place tip under mq control).
2136 (e.g. qimport --rev tip -n patch will place tip under mq control).
2137 With -g/--git, patches imported with --rev will use the git diff
2137 With -g/--git, patches imported with --rev will use the git diff
2138 format. See the diffs help topic for information on why this is
2138 format. See the diffs help topic for information on why this is
2139 important for preserving rename/copy information and permission
2139 important for preserving rename/copy information and permission
2140 changes. Use :hg:`qfinish` to remove changesets from mq control.
2140 changes. Use :hg:`qfinish` to remove changesets from mq control.
2141
2141
2142 To import a patch from standard input, pass - as the patch file.
2142 To import a patch from standard input, pass - as the patch file.
2143 When importing from standard input, a patch name must be specified
2143 When importing from standard input, a patch name must be specified
2144 using the --name flag.
2144 using the --name flag.
2145
2145
2146 To import an existing patch while renaming it::
2146 To import an existing patch while renaming it::
2147
2147
2148 hg qimport -e existing-patch -n new-name
2148 hg qimport -e existing-patch -n new-name
2149
2149
2150 Returns 0 if import succeeded.
2150 Returns 0 if import succeeded.
2151 """
2151 """
2152 lock = repo.lock() # cause this may move phase
2152 lock = repo.lock() # cause this may move phase
2153 try:
2153 try:
2154 q = repo.mq
2154 q = repo.mq
2155 try:
2155 try:
2156 imported = q.qimport(
2156 imported = q.qimport(
2157 repo, filename, patchname=opts.get('name'),
2157 repo, filename, patchname=opts.get('name'),
2158 existing=opts.get('existing'), force=opts.get('force'),
2158 existing=opts.get('existing'), force=opts.get('force'),
2159 rev=opts.get('rev'), git=opts.get('git'))
2159 rev=opts.get('rev'), git=opts.get('git'))
2160 finally:
2160 finally:
2161 q.savedirty()
2161 q.savedirty()
2162 finally:
2162 finally:
2163 lock.release()
2163 lock.release()
2164
2164
2165 if imported and opts.get('push') and not opts.get('rev'):
2165 if imported and opts.get('push') and not opts.get('rev'):
2166 return q.push(repo, imported[-1])
2166 return q.push(repo, imported[-1])
2167 return 0
2167 return 0
2168
2168
2169 def qinit(ui, repo, create):
2169 def qinit(ui, repo, create):
2170 """initialize a new queue repository
2170 """initialize a new queue repository
2171
2171
2172 This command also creates a series file for ordering patches, and
2172 This command also creates a series file for ordering patches, and
2173 an mq-specific .hgignore file in the queue repository, to exclude
2173 an mq-specific .hgignore file in the queue repository, to exclude
2174 the status and guards files (these contain mostly transient state).
2174 the status and guards files (these contain mostly transient state).
2175
2175
2176 Returns 0 if initialization succeeded."""
2176 Returns 0 if initialization succeeded."""
2177 q = repo.mq
2177 q = repo.mq
2178 r = q.init(repo, create)
2178 r = q.init(repo, create)
2179 q.savedirty()
2179 q.savedirty()
2180 if r:
2180 if r:
2181 if not os.path.exists(r.wjoin('.hgignore')):
2181 if not os.path.exists(r.wjoin('.hgignore')):
2182 fp = r.wopener('.hgignore', 'w')
2182 fp = r.wopener('.hgignore', 'w')
2183 fp.write('^\\.hg\n')
2183 fp.write('^\\.hg\n')
2184 fp.write('^\\.mq\n')
2184 fp.write('^\\.mq\n')
2185 fp.write('syntax: glob\n')
2185 fp.write('syntax: glob\n')
2186 fp.write('status\n')
2186 fp.write('status\n')
2187 fp.write('guards\n')
2187 fp.write('guards\n')
2188 fp.close()
2188 fp.close()
2189 if not os.path.exists(r.wjoin('series')):
2189 if not os.path.exists(r.wjoin('series')):
2190 r.wopener('series', 'w').close()
2190 r.wopener('series', 'w').close()
2191 r[None].add(['.hgignore', 'series'])
2191 r[None].add(['.hgignore', 'series'])
2192 commands.add(ui, r)
2192 commands.add(ui, r)
2193 return 0
2193 return 0
2194
2194
2195 @command("^qinit",
2195 @command("^qinit",
2196 [('c', 'create-repo', None, _('create queue repository'))],
2196 [('c', 'create-repo', None, _('create queue repository'))],
2197 _('hg qinit [-c]'))
2197 _('hg qinit [-c]'))
2198 def init(ui, repo, **opts):
2198 def init(ui, repo, **opts):
2199 """init a new queue repository (DEPRECATED)
2199 """init a new queue repository (DEPRECATED)
2200
2200
2201 The queue repository is unversioned by default. If
2201 The queue repository is unversioned by default. If
2202 -c/--create-repo is specified, qinit will create a separate nested
2202 -c/--create-repo is specified, qinit will create a separate nested
2203 repository for patches (qinit -c may also be run later to convert
2203 repository for patches (qinit -c may also be run later to convert
2204 an unversioned patch repository into a versioned one). You can use
2204 an unversioned patch repository into a versioned one). You can use
2205 qcommit to commit changes to this queue repository.
2205 qcommit to commit changes to this queue repository.
2206
2206
2207 This command is deprecated. Without -c, it's implied by other relevant
2207 This command is deprecated. Without -c, it's implied by other relevant
2208 commands. With -c, use :hg:`init --mq` instead."""
2208 commands. With -c, use :hg:`init --mq` instead."""
2209 return qinit(ui, repo, create=opts.get('create_repo'))
2209 return qinit(ui, repo, create=opts.get('create_repo'))
2210
2210
2211 @command("qclone",
2211 @command("qclone",
2212 [('', 'pull', None, _('use pull protocol to copy metadata')),
2212 [('', 'pull', None, _('use pull protocol to copy metadata')),
2213 ('U', 'noupdate', None,
2213 ('U', 'noupdate', None,
2214 _('do not update the new working directories')),
2214 _('do not update the new working directories')),
2215 ('', 'uncompressed', None,
2215 ('', 'uncompressed', None,
2216 _('use uncompressed transfer (fast over LAN)')),
2216 _('use uncompressed transfer (fast over LAN)')),
2217 ('p', 'patches', '',
2217 ('p', 'patches', '',
2218 _('location of source patch repository'), _('REPO')),
2218 _('location of source patch repository'), _('REPO')),
2219 ] + commands.remoteopts,
2219 ] + commands.remoteopts,
2220 _('hg qclone [OPTION]... SOURCE [DEST]'))
2220 _('hg qclone [OPTION]... SOURCE [DEST]'))
2221 def clone(ui, source, dest=None, **opts):
2221 def clone(ui, source, dest=None, **opts):
2222 '''clone main and patch repository at same time
2222 '''clone main and patch repository at same time
2223
2223
2224 If source is local, destination will have no patches applied. If
2224 If source is local, destination will have no patches applied. If
2225 source is remote, this command can not check if patches are
2225 source is remote, this command can not check if patches are
2226 applied in source, so cannot guarantee that patches are not
2226 applied in source, so cannot guarantee that patches are not
2227 applied in destination. If you clone remote repository, be sure
2227 applied in destination. If you clone remote repository, be sure
2228 before that it has no patches applied.
2228 before that it has no patches applied.
2229
2229
2230 Source patch repository is looked for in <src>/.hg/patches by
2230 Source patch repository is looked for in <src>/.hg/patches by
2231 default. Use -p <url> to change.
2231 default. Use -p <url> to change.
2232
2232
2233 The patch directory must be a nested Mercurial repository, as
2233 The patch directory must be a nested Mercurial repository, as
2234 would be created by :hg:`init --mq`.
2234 would be created by :hg:`init --mq`.
2235
2235
2236 Return 0 on success.
2236 Return 0 on success.
2237 '''
2237 '''
2238 def patchdir(repo):
2238 def patchdir(repo):
2239 """compute a patch repo url from a repo object"""
2239 """compute a patch repo url from a repo object"""
2240 url = repo.url()
2240 url = repo.url()
2241 if url.endswith('/'):
2241 if url.endswith('/'):
2242 url = url[:-1]
2242 url = url[:-1]
2243 return url + '/.hg/patches'
2243 return url + '/.hg/patches'
2244
2244
2245 # main repo (destination and sources)
2245 # main repo (destination and sources)
2246 if dest is None:
2246 if dest is None:
2247 dest = hg.defaultdest(source)
2247 dest = hg.defaultdest(source)
2248 sr = hg.peer(ui, opts, ui.expandpath(source))
2248 sr = hg.peer(ui, opts, ui.expandpath(source))
2249
2249
2250 # patches repo (source only)
2250 # patches repo (source only)
2251 if opts.get('patches'):
2251 if opts.get('patches'):
2252 patchespath = ui.expandpath(opts.get('patches'))
2252 patchespath = ui.expandpath(opts.get('patches'))
2253 else:
2253 else:
2254 patchespath = patchdir(sr)
2254 patchespath = patchdir(sr)
2255 try:
2255 try:
2256 hg.peer(ui, opts, patchespath)
2256 hg.peer(ui, opts, patchespath)
2257 except error.RepoError:
2257 except error.RepoError:
2258 raise util.Abort(_('versioned patch repository not found'
2258 raise util.Abort(_('versioned patch repository not found'
2259 ' (see init --mq)'))
2259 ' (see init --mq)'))
2260 qbase, destrev = None, None
2260 qbase, destrev = None, None
2261 if sr.local():
2261 if sr.local():
2262 repo = sr.local()
2262 repo = sr.local()
2263 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2263 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2264 qbase = repo.mq.applied[0].node
2264 qbase = repo.mq.applied[0].node
2265 if not hg.islocal(dest):
2265 if not hg.islocal(dest):
2266 heads = set(repo.heads())
2266 heads = set(repo.heads())
2267 destrev = list(heads.difference(repo.heads(qbase)))
2267 destrev = list(heads.difference(repo.heads(qbase)))
2268 destrev.append(repo.changelog.parents(qbase)[0])
2268 destrev.append(repo.changelog.parents(qbase)[0])
2269 elif sr.capable('lookup'):
2269 elif sr.capable('lookup'):
2270 try:
2270 try:
2271 qbase = sr.lookup('qbase')
2271 qbase = sr.lookup('qbase')
2272 except error.RepoError:
2272 except error.RepoError:
2273 pass
2273 pass
2274
2274
2275 ui.note(_('cloning main repository\n'))
2275 ui.note(_('cloning main repository\n'))
2276 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2276 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2277 pull=opts.get('pull'),
2277 pull=opts.get('pull'),
2278 rev=destrev,
2278 rev=destrev,
2279 update=False,
2279 update=False,
2280 stream=opts.get('uncompressed'))
2280 stream=opts.get('uncompressed'))
2281
2281
2282 ui.note(_('cloning patch repository\n'))
2282 ui.note(_('cloning patch repository\n'))
2283 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2283 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2284 pull=opts.get('pull'), update=not opts.get('noupdate'),
2284 pull=opts.get('pull'), update=not opts.get('noupdate'),
2285 stream=opts.get('uncompressed'))
2285 stream=opts.get('uncompressed'))
2286
2286
2287 if dr.local():
2287 if dr.local():
2288 repo = dr.local()
2288 repo = dr.local()
2289 if qbase:
2289 if qbase:
2290 ui.note(_('stripping applied patches from destination '
2290 ui.note(_('stripping applied patches from destination '
2291 'repository\n'))
2291 'repository\n'))
2292 repo.mq.strip(repo, [qbase], update=False, backup=None)
2292 repo.mq.strip(repo, [qbase], update=False, backup=None)
2293 if not opts.get('noupdate'):
2293 if not opts.get('noupdate'):
2294 ui.note(_('updating destination repository\n'))
2294 ui.note(_('updating destination repository\n'))
2295 hg.update(repo, repo.changelog.tip())
2295 hg.update(repo, repo.changelog.tip())
2296
2296
2297 @command("qcommit|qci",
2297 @command("qcommit|qci",
2298 commands.table["^commit|ci"][1],
2298 commands.table["^commit|ci"][1],
2299 _('hg qcommit [OPTION]... [FILE]...'))
2299 _('hg qcommit [OPTION]... [FILE]...'))
2300 def commit(ui, repo, *pats, **opts):
2300 def commit(ui, repo, *pats, **opts):
2301 """commit changes in the queue repository (DEPRECATED)
2301 """commit changes in the queue repository (DEPRECATED)
2302
2302
2303 This command is deprecated; use :hg:`commit --mq` instead."""
2303 This command is deprecated; use :hg:`commit --mq` instead."""
2304 q = repo.mq
2304 q = repo.mq
2305 r = q.qrepo()
2305 r = q.qrepo()
2306 if not r:
2306 if not r:
2307 raise util.Abort('no queue repository')
2307 raise util.Abort('no queue repository')
2308 commands.commit(r.ui, r, *pats, **opts)
2308 commands.commit(r.ui, r, *pats, **opts)
2309
2309
2310 @command("qseries",
2310 @command("qseries",
2311 [('m', 'missing', None, _('print patches not in series')),
2311 [('m', 'missing', None, _('print patches not in series')),
2312 ] + seriesopts,
2312 ] + seriesopts,
2313 _('hg qseries [-ms]'))
2313 _('hg qseries [-ms]'))
2314 def series(ui, repo, **opts):
2314 def series(ui, repo, **opts):
2315 """print the entire series file
2315 """print the entire series file
2316
2316
2317 Returns 0 on success."""
2317 Returns 0 on success."""
2318 repo.mq.qseries(repo, missing=opts.get('missing'),
2318 repo.mq.qseries(repo, missing=opts.get('missing'),
2319 summary=opts.get('summary'))
2319 summary=opts.get('summary'))
2320 return 0
2320 return 0
2321
2321
2322 @command("qtop", seriesopts, _('hg qtop [-s]'))
2322 @command("qtop", seriesopts, _('hg qtop [-s]'))
2323 def top(ui, repo, **opts):
2323 def top(ui, repo, **opts):
2324 """print the name of the current patch
2324 """print the name of the current patch
2325
2325
2326 Returns 0 on success."""
2326 Returns 0 on success."""
2327 q = repo.mq
2327 q = repo.mq
2328 t = q.applied and q.seriesend(True) or 0
2328 t = q.applied and q.seriesend(True) or 0
2329 if t:
2329 if t:
2330 q.qseries(repo, start=t - 1, length=1, status='A',
2330 q.qseries(repo, start=t - 1, length=1, status='A',
2331 summary=opts.get('summary'))
2331 summary=opts.get('summary'))
2332 else:
2332 else:
2333 ui.write(_("no patches applied\n"))
2333 ui.write(_("no patches applied\n"))
2334 return 1
2334 return 1
2335
2335
2336 @command("qnext", seriesopts, _('hg qnext [-s]'))
2336 @command("qnext", seriesopts, _('hg qnext [-s]'))
2337 def next(ui, repo, **opts):
2337 def next(ui, repo, **opts):
2338 """print the name of the next pushable patch
2338 """print the name of the next pushable patch
2339
2339
2340 Returns 0 on success."""
2340 Returns 0 on success."""
2341 q = repo.mq
2341 q = repo.mq
2342 end = q.seriesend()
2342 end = q.seriesend()
2343 if end == len(q.series):
2343 if end == len(q.series):
2344 ui.write(_("all patches applied\n"))
2344 ui.write(_("all patches applied\n"))
2345 return 1
2345 return 1
2346 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2346 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2347
2347
2348 @command("qprev", seriesopts, _('hg qprev [-s]'))
2348 @command("qprev", seriesopts, _('hg qprev [-s]'))
2349 def prev(ui, repo, **opts):
2349 def prev(ui, repo, **opts):
2350 """print the name of the preceding applied patch
2350 """print the name of the preceding applied patch
2351
2351
2352 Returns 0 on success."""
2352 Returns 0 on success."""
2353 q = repo.mq
2353 q = repo.mq
2354 l = len(q.applied)
2354 l = len(q.applied)
2355 if l == 1:
2355 if l == 1:
2356 ui.write(_("only one patch applied\n"))
2356 ui.write(_("only one patch applied\n"))
2357 return 1
2357 return 1
2358 if not l:
2358 if not l:
2359 ui.write(_("no patches applied\n"))
2359 ui.write(_("no patches applied\n"))
2360 return 1
2360 return 1
2361 idx = q.series.index(q.applied[-2].name)
2361 idx = q.series.index(q.applied[-2].name)
2362 q.qseries(repo, start=idx, length=1, status='A',
2362 q.qseries(repo, start=idx, length=1, status='A',
2363 summary=opts.get('summary'))
2363 summary=opts.get('summary'))
2364
2364
2365 def setupheaderopts(ui, opts):
2365 def setupheaderopts(ui, opts):
2366 if not opts.get('user') and opts.get('currentuser'):
2366 if not opts.get('user') and opts.get('currentuser'):
2367 opts['user'] = ui.username()
2367 opts['user'] = ui.username()
2368 if not opts.get('date') and opts.get('currentdate'):
2368 if not opts.get('date') and opts.get('currentdate'):
2369 opts['date'] = "%d %d" % util.makedate()
2369 opts['date'] = "%d %d" % util.makedate()
2370
2370
2371 @command("^qnew",
2371 @command("^qnew",
2372 [('e', 'edit', None, _('edit commit message')),
2372 [('e', 'edit', None, _('edit commit message')),
2373 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2373 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2374 ('g', 'git', None, _('use git extended diff format')),
2374 ('g', 'git', None, _('use git extended diff format')),
2375 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2375 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2376 ('u', 'user', '',
2376 ('u', 'user', '',
2377 _('add "From: <USER>" to patch'), _('USER')),
2377 _('add "From: <USER>" to patch'), _('USER')),
2378 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2378 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2379 ('d', 'date', '',
2379 ('d', 'date', '',
2380 _('add "Date: <DATE>" to patch'), _('DATE'))
2380 _('add "Date: <DATE>" to patch'), _('DATE'))
2381 ] + commands.walkopts + commands.commitopts,
2381 ] + commands.walkopts + commands.commitopts,
2382 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2382 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2383 def new(ui, repo, patch, *args, **opts):
2383 def new(ui, repo, patch, *args, **opts):
2384 """create a new patch
2384 """create a new patch
2385
2385
2386 qnew creates a new patch on top of the currently-applied patch (if
2386 qnew creates a new patch on top of the currently-applied patch (if
2387 any). The patch will be initialized with any outstanding changes
2387 any). The patch will be initialized with any outstanding changes
2388 in the working directory. You may also use -I/--include,
2388 in the working directory. You may also use -I/--include,
2389 -X/--exclude, and/or a list of files after the patch name to add
2389 -X/--exclude, and/or a list of files after the patch name to add
2390 only changes to matching files to the new patch, leaving the rest
2390 only changes to matching files to the new patch, leaving the rest
2391 as uncommitted modifications.
2391 as uncommitted modifications.
2392
2392
2393 -u/--user and -d/--date can be used to set the (given) user and
2393 -u/--user and -d/--date can be used to set the (given) user and
2394 date, respectively. -U/--currentuser and -D/--currentdate set user
2394 date, respectively. -U/--currentuser and -D/--currentdate set user
2395 to current user and date to current date.
2395 to current user and date to current date.
2396
2396
2397 -e/--edit, -m/--message or -l/--logfile set the patch header as
2397 -e/--edit, -m/--message or -l/--logfile set the patch header as
2398 well as the commit message. If none is specified, the header is
2398 well as the commit message. If none is specified, the header is
2399 empty and the commit message is '[mq]: PATCH'.
2399 empty and the commit message is '[mq]: PATCH'.
2400
2400
2401 Use the -g/--git option to keep the patch in the git extended diff
2401 Use the -g/--git option to keep the patch in the git extended diff
2402 format. Read the diffs help topic for more information on why this
2402 format. Read the diffs help topic for more information on why this
2403 is important for preserving permission changes and copy/rename
2403 is important for preserving permission changes and copy/rename
2404 information.
2404 information.
2405
2405
2406 Returns 0 on successful creation of a new patch.
2406 Returns 0 on successful creation of a new patch.
2407 """
2407 """
2408 msg = cmdutil.logmessage(ui, opts)
2408 msg = cmdutil.logmessage(ui, opts)
2409 def getmsg():
2409 def getmsg():
2410 return ui.edit(msg, opts.get('user') or ui.username())
2410 return ui.edit(msg, opts.get('user') or ui.username())
2411 q = repo.mq
2411 q = repo.mq
2412 opts['msg'] = msg
2412 opts['msg'] = msg
2413 if opts.get('edit'):
2413 if opts.get('edit'):
2414 opts['msg'] = getmsg
2414 opts['msg'] = getmsg
2415 else:
2415 else:
2416 opts['msg'] = msg
2416 opts['msg'] = msg
2417 setupheaderopts(ui, opts)
2417 setupheaderopts(ui, opts)
2418 q.new(repo, patch, *args, **opts)
2418 q.new(repo, patch, *args, **opts)
2419 q.savedirty()
2419 q.savedirty()
2420 return 0
2420 return 0
2421
2421
2422 @command("^qrefresh",
2422 @command("^qrefresh",
2423 [('e', 'edit', None, _('edit commit message')),
2423 [('e', 'edit', None, _('edit commit message')),
2424 ('g', 'git', None, _('use git extended diff format')),
2424 ('g', 'git', None, _('use git extended diff format')),
2425 ('s', 'short', None,
2425 ('s', 'short', None,
2426 _('refresh only files already in the patch and specified files')),
2426 _('refresh only files already in the patch and specified files')),
2427 ('U', 'currentuser', None,
2427 ('U', 'currentuser', None,
2428 _('add/update author field in patch with current user')),
2428 _('add/update author field in patch with current user')),
2429 ('u', 'user', '',
2429 ('u', 'user', '',
2430 _('add/update author field in patch with given user'), _('USER')),
2430 _('add/update author field in patch with given user'), _('USER')),
2431 ('D', 'currentdate', None,
2431 ('D', 'currentdate', None,
2432 _('add/update date field in patch with current date')),
2432 _('add/update date field in patch with current date')),
2433 ('d', 'date', '',
2433 ('d', 'date', '',
2434 _('add/update date field in patch with given date'), _('DATE'))
2434 _('add/update date field in patch with given date'), _('DATE'))
2435 ] + commands.walkopts + commands.commitopts,
2435 ] + commands.walkopts + commands.commitopts,
2436 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2436 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2437 def refresh(ui, repo, *pats, **opts):
2437 def refresh(ui, repo, *pats, **opts):
2438 """update the current patch
2438 """update the current patch
2439
2439
2440 If any file patterns are provided, the refreshed patch will
2440 If any file patterns are provided, the refreshed patch will
2441 contain only the modifications that match those patterns; the
2441 contain only the modifications that match those patterns; the
2442 remaining modifications will remain in the working directory.
2442 remaining modifications will remain in the working directory.
2443
2443
2444 If -s/--short is specified, files currently included in the patch
2444 If -s/--short is specified, files currently included in the patch
2445 will be refreshed just like matched files and remain in the patch.
2445 will be refreshed just like matched files and remain in the patch.
2446
2446
2447 If -e/--edit is specified, Mercurial will start your configured editor for
2447 If -e/--edit is specified, Mercurial will start your configured editor for
2448 you to enter a message. In case qrefresh fails, you will find a backup of
2448 you to enter a message. In case qrefresh fails, you will find a backup of
2449 your message in ``.hg/last-message.txt``.
2449 your message in ``.hg/last-message.txt``.
2450
2450
2451 hg add/remove/copy/rename work as usual, though you might want to
2451 hg add/remove/copy/rename work as usual, though you might want to
2452 use git-style patches (-g/--git or [diff] git=1) to track copies
2452 use git-style patches (-g/--git or [diff] git=1) to track copies
2453 and renames. See the diffs help topic for more information on the
2453 and renames. See the diffs help topic for more information on the
2454 git diff format.
2454 git diff format.
2455
2455
2456 Returns 0 on success.
2456 Returns 0 on success.
2457 """
2457 """
2458 q = repo.mq
2458 q = repo.mq
2459 message = cmdutil.logmessage(ui, opts)
2459 message = cmdutil.logmessage(ui, opts)
2460 if opts.get('edit'):
2460 if opts.get('edit'):
2461 if not q.applied:
2461 if not q.applied:
2462 ui.write(_("no patches applied\n"))
2462 ui.write(_("no patches applied\n"))
2463 return 1
2463 return 1
2464 if message:
2464 if message:
2465 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2465 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2466 patch = q.applied[-1].name
2466 patch = q.applied[-1].name
2467 ph = patchheader(q.join(patch), q.plainmode)
2467 ph = patchheader(q.join(patch), q.plainmode)
2468 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2468 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2469 # We don't want to lose the patch message if qrefresh fails (issue2062)
2469 # We don't want to lose the patch message if qrefresh fails (issue2062)
2470 repo.savecommitmessage(message)
2470 repo.savecommitmessage(message)
2471 setupheaderopts(ui, opts)
2471 setupheaderopts(ui, opts)
2472 wlock = repo.wlock()
2472 wlock = repo.wlock()
2473 try:
2473 try:
2474 ret = q.refresh(repo, pats, msg=message, **opts)
2474 ret = q.refresh(repo, pats, msg=message, **opts)
2475 q.savedirty()
2475 q.savedirty()
2476 return ret
2476 return ret
2477 finally:
2477 finally:
2478 wlock.release()
2478 wlock.release()
2479
2479
2480 @command("^qdiff",
2480 @command("^qdiff",
2481 commands.diffopts + commands.diffopts2 + commands.walkopts,
2481 commands.diffopts + commands.diffopts2 + commands.walkopts,
2482 _('hg qdiff [OPTION]... [FILE]...'))
2482 _('hg qdiff [OPTION]... [FILE]...'))
2483 def diff(ui, repo, *pats, **opts):
2483 def diff(ui, repo, *pats, **opts):
2484 """diff of the current patch and subsequent modifications
2484 """diff of the current patch and subsequent modifications
2485
2485
2486 Shows a diff which includes the current patch as well as any
2486 Shows a diff which includes the current patch as well as any
2487 changes which have been made in the working directory since the
2487 changes which have been made in the working directory since the
2488 last refresh (thus showing what the current patch would become
2488 last refresh (thus showing what the current patch would become
2489 after a qrefresh).
2489 after a qrefresh).
2490
2490
2491 Use :hg:`diff` if you only want to see the changes made since the
2491 Use :hg:`diff` if you only want to see the changes made since the
2492 last qrefresh, or :hg:`export qtip` if you want to see changes
2492 last qrefresh, or :hg:`export qtip` if you want to see changes
2493 made by the current patch without including changes made since the
2493 made by the current patch without including changes made since the
2494 qrefresh.
2494 qrefresh.
2495
2495
2496 Returns 0 on success.
2496 Returns 0 on success.
2497 """
2497 """
2498 repo.mq.diff(repo, pats, opts)
2498 repo.mq.diff(repo, pats, opts)
2499 return 0
2499 return 0
2500
2500
2501 @command('qfold',
2501 @command('qfold',
2502 [('e', 'edit', None, _('edit patch header')),
2502 [('e', 'edit', None, _('edit patch header')),
2503 ('k', 'keep', None, _('keep folded patch files')),
2503 ('k', 'keep', None, _('keep folded patch files')),
2504 ] + commands.commitopts,
2504 ] + commands.commitopts,
2505 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2505 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2506 def fold(ui, repo, *files, **opts):
2506 def fold(ui, repo, *files, **opts):
2507 """fold the named patches into the current patch
2507 """fold the named patches into the current patch
2508
2508
2509 Patches must not yet be applied. Each patch will be successively
2509 Patches must not yet be applied. Each patch will be successively
2510 applied to the current patch in the order given. If all the
2510 applied to the current patch in the order given. If all the
2511 patches apply successfully, the current patch will be refreshed
2511 patches apply successfully, the current patch will be refreshed
2512 with the new cumulative patch, and the folded patches will be
2512 with the new cumulative patch, and the folded patches will be
2513 deleted. With -k/--keep, the folded patch files will not be
2513 deleted. With -k/--keep, the folded patch files will not be
2514 removed afterwards.
2514 removed afterwards.
2515
2515
2516 The header for each folded patch will be concatenated with the
2516 The header for each folded patch will be concatenated with the
2517 current patch header, separated by a line of ``* * *``.
2517 current patch header, separated by a line of ``* * *``.
2518
2518
2519 Returns 0 on success."""
2519 Returns 0 on success."""
2520 q = repo.mq
2520 q = repo.mq
2521 if not files:
2521 if not files:
2522 raise util.Abort(_('qfold requires at least one patch name'))
2522 raise util.Abort(_('qfold requires at least one patch name'))
2523 if not q.checktoppatch(repo)[0]:
2523 if not q.checktoppatch(repo)[0]:
2524 raise util.Abort(_('no patches applied'))
2524 raise util.Abort(_('no patches applied'))
2525 q.checklocalchanges(repo)
2525 q.checklocalchanges(repo)
2526
2526
2527 message = cmdutil.logmessage(ui, opts)
2527 message = cmdutil.logmessage(ui, opts)
2528 if opts.get('edit'):
2528 if opts.get('edit'):
2529 if message:
2529 if message:
2530 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2530 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2531
2531
2532 parent = q.lookup('qtip')
2532 parent = q.lookup('qtip')
2533 patches = []
2533 patches = []
2534 messages = []
2534 messages = []
2535 for f in files:
2535 for f in files:
2536 p = q.lookup(f)
2536 p = q.lookup(f)
2537 if p in patches or p == parent:
2537 if p in patches or p == parent:
2538 ui.warn(_('skipping already folded patch %s\n') % p)
2538 ui.warn(_('skipping already folded patch %s\n') % p)
2539 if q.isapplied(p):
2539 if q.isapplied(p):
2540 raise util.Abort(_('qfold cannot fold already applied patch %s')
2540 raise util.Abort(_('qfold cannot fold already applied patch %s')
2541 % p)
2541 % p)
2542 patches.append(p)
2542 patches.append(p)
2543
2543
2544 for p in patches:
2544 for p in patches:
2545 if not message:
2545 if not message:
2546 ph = patchheader(q.join(p), q.plainmode)
2546 ph = patchheader(q.join(p), q.plainmode)
2547 if ph.message:
2547 if ph.message:
2548 messages.append(ph.message)
2548 messages.append(ph.message)
2549 pf = q.join(p)
2549 pf = q.join(p)
2550 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2550 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2551 if not patchsuccess:
2551 if not patchsuccess:
2552 raise util.Abort(_('error folding patch %s') % p)
2552 raise util.Abort(_('error folding patch %s') % p)
2553
2553
2554 if not message:
2554 if not message:
2555 ph = patchheader(q.join(parent), q.plainmode)
2555 ph = patchheader(q.join(parent), q.plainmode)
2556 message, user = ph.message, ph.user
2556 message, user = ph.message, ph.user
2557 for msg in messages:
2557 for msg in messages:
2558 message.append('* * *')
2558 message.append('* * *')
2559 message.extend(msg)
2559 message.extend(msg)
2560 message = '\n'.join(message)
2560 message = '\n'.join(message)
2561
2561
2562 if opts.get('edit'):
2562 if opts.get('edit'):
2563 message = ui.edit(message, user or ui.username())
2563 message = ui.edit(message, user or ui.username())
2564
2564
2565 diffopts = q.patchopts(q.diffopts(), *patches)
2565 diffopts = q.patchopts(q.diffopts(), *patches)
2566 wlock = repo.wlock()
2566 wlock = repo.wlock()
2567 try:
2567 try:
2568 q.refresh(repo, msg=message, git=diffopts.git)
2568 q.refresh(repo, msg=message, git=diffopts.git)
2569 q.delete(repo, patches, opts)
2569 q.delete(repo, patches, opts)
2570 q.savedirty()
2570 q.savedirty()
2571 finally:
2571 finally:
2572 wlock.release()
2572 wlock.release()
2573
2573
2574 @command("qgoto",
2574 @command("qgoto",
2575 [('', 'keep-changes', None,
2575 [('', 'keep-changes', None,
2576 _('tolerate non-conflicting local changes')),
2576 _('tolerate non-conflicting local changes')),
2577 ('f', 'force', None, _('overwrite any local changes')),
2577 ('f', 'force', None, _('overwrite any local changes')),
2578 ('', 'no-backup', None, _('do not save backup copies of files'))],
2578 ('', 'no-backup', None, _('do not save backup copies of files'))],
2579 _('hg qgoto [OPTION]... PATCH'))
2579 _('hg qgoto [OPTION]... PATCH'))
2580 def goto(ui, repo, patch, **opts):
2580 def goto(ui, repo, patch, **opts):
2581 '''push or pop patches until named patch is at top of stack
2581 '''push or pop patches until named patch is at top of stack
2582
2582
2583 Returns 0 on success.'''
2583 Returns 0 on success.'''
2584 opts = fixkeepchangesopts(ui, opts)
2584 opts = fixkeepchangesopts(ui, opts)
2585 q = repo.mq
2585 q = repo.mq
2586 patch = q.lookup(patch)
2586 patch = q.lookup(patch)
2587 nobackup = opts.get('no_backup')
2587 nobackup = opts.get('no_backup')
2588 keepchanges = opts.get('keep_changes')
2588 keepchanges = opts.get('keep_changes')
2589 if q.isapplied(patch):
2589 if q.isapplied(patch):
2590 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2590 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2591 keepchanges=keepchanges)
2591 keepchanges=keepchanges)
2592 else:
2592 else:
2593 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2593 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2594 keepchanges=keepchanges)
2594 keepchanges=keepchanges)
2595 q.savedirty()
2595 q.savedirty()
2596 return ret
2596 return ret
2597
2597
2598 @command("qguard",
2598 @command("qguard",
2599 [('l', 'list', None, _('list all patches and guards')),
2599 [('l', 'list', None, _('list all patches and guards')),
2600 ('n', 'none', None, _('drop all guards'))],
2600 ('n', 'none', None, _('drop all guards'))],
2601 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2601 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2602 def guard(ui, repo, *args, **opts):
2602 def guard(ui, repo, *args, **opts):
2603 '''set or print guards for a patch
2603 '''set or print guards for a patch
2604
2604
2605 Guards control whether a patch can be pushed. A patch with no
2605 Guards control whether a patch can be pushed. A patch with no
2606 guards is always pushed. A patch with a positive guard ("+foo") is
2606 guards is always pushed. A patch with a positive guard ("+foo") is
2607 pushed only if the :hg:`qselect` command has activated it. A patch with
2607 pushed only if the :hg:`qselect` command has activated it. A patch with
2608 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2608 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2609 has activated it.
2609 has activated it.
2610
2610
2611 With no arguments, print the currently active guards.
2611 With no arguments, print the currently active guards.
2612 With arguments, set guards for the named patch.
2612 With arguments, set guards for the named patch.
2613
2613
2614 .. note::
2614 .. note::
2615 Specifying negative guards now requires '--'.
2615 Specifying negative guards now requires '--'.
2616
2616
2617 To set guards on another patch::
2617 To set guards on another patch::
2618
2618
2619 hg qguard other.patch -- +2.6.17 -stable
2619 hg qguard other.patch -- +2.6.17 -stable
2620
2620
2621 Returns 0 on success.
2621 Returns 0 on success.
2622 '''
2622 '''
2623 def status(idx):
2623 def status(idx):
2624 guards = q.seriesguards[idx] or ['unguarded']
2624 guards = q.seriesguards[idx] or ['unguarded']
2625 if q.series[idx] in applied:
2625 if q.series[idx] in applied:
2626 state = 'applied'
2626 state = 'applied'
2627 elif q.pushable(idx)[0]:
2627 elif q.pushable(idx)[0]:
2628 state = 'unapplied'
2628 state = 'unapplied'
2629 else:
2629 else:
2630 state = 'guarded'
2630 state = 'guarded'
2631 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2631 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2632 ui.write('%s: ' % ui.label(q.series[idx], label))
2632 ui.write('%s: ' % ui.label(q.series[idx], label))
2633
2633
2634 for i, guard in enumerate(guards):
2634 for i, guard in enumerate(guards):
2635 if guard.startswith('+'):
2635 if guard.startswith('+'):
2636 ui.write(guard, label='qguard.positive')
2636 ui.write(guard, label='qguard.positive')
2637 elif guard.startswith('-'):
2637 elif guard.startswith('-'):
2638 ui.write(guard, label='qguard.negative')
2638 ui.write(guard, label='qguard.negative')
2639 else:
2639 else:
2640 ui.write(guard, label='qguard.unguarded')
2640 ui.write(guard, label='qguard.unguarded')
2641 if i != len(guards) - 1:
2641 if i != len(guards) - 1:
2642 ui.write(' ')
2642 ui.write(' ')
2643 ui.write('\n')
2643 ui.write('\n')
2644 q = repo.mq
2644 q = repo.mq
2645 applied = set(p.name for p in q.applied)
2645 applied = set(p.name for p in q.applied)
2646 patch = None
2646 patch = None
2647 args = list(args)
2647 args = list(args)
2648 if opts.get('list'):
2648 if opts.get('list'):
2649 if args or opts.get('none'):
2649 if args or opts.get('none'):
2650 raise util.Abort(_('cannot mix -l/--list with options or '
2650 raise util.Abort(_('cannot mix -l/--list with options or '
2651 'arguments'))
2651 'arguments'))
2652 for i in xrange(len(q.series)):
2652 for i in xrange(len(q.series)):
2653 status(i)
2653 status(i)
2654 return
2654 return
2655 if not args or args[0][0:1] in '-+':
2655 if not args or args[0][0:1] in '-+':
2656 if not q.applied:
2656 if not q.applied:
2657 raise util.Abort(_('no patches applied'))
2657 raise util.Abort(_('no patches applied'))
2658 patch = q.applied[-1].name
2658 patch = q.applied[-1].name
2659 if patch is None and args[0][0:1] not in '-+':
2659 if patch is None and args[0][0:1] not in '-+':
2660 patch = args.pop(0)
2660 patch = args.pop(0)
2661 if patch is None:
2661 if patch is None:
2662 raise util.Abort(_('no patch to work with'))
2662 raise util.Abort(_('no patch to work with'))
2663 if args or opts.get('none'):
2663 if args or opts.get('none'):
2664 idx = q.findseries(patch)
2664 idx = q.findseries(patch)
2665 if idx is None:
2665 if idx is None:
2666 raise util.Abort(_('no patch named %s') % patch)
2666 raise util.Abort(_('no patch named %s') % patch)
2667 q.setguards(idx, args)
2667 q.setguards(idx, args)
2668 q.savedirty()
2668 q.savedirty()
2669 else:
2669 else:
2670 status(q.series.index(q.lookup(patch)))
2670 status(q.series.index(q.lookup(patch)))
2671
2671
2672 @command("qheader", [], _('hg qheader [PATCH]'))
2672 @command("qheader", [], _('hg qheader [PATCH]'))
2673 def header(ui, repo, patch=None):
2673 def header(ui, repo, patch=None):
2674 """print the header of the topmost or specified patch
2674 """print the header of the topmost or specified patch
2675
2675
2676 Returns 0 on success."""
2676 Returns 0 on success."""
2677 q = repo.mq
2677 q = repo.mq
2678
2678
2679 if patch:
2679 if patch:
2680 patch = q.lookup(patch)
2680 patch = q.lookup(patch)
2681 else:
2681 else:
2682 if not q.applied:
2682 if not q.applied:
2683 ui.write(_('no patches applied\n'))
2683 ui.write(_('no patches applied\n'))
2684 return 1
2684 return 1
2685 patch = q.lookup('qtip')
2685 patch = q.lookup('qtip')
2686 ph = patchheader(q.join(patch), q.plainmode)
2686 ph = patchheader(q.join(patch), q.plainmode)
2687
2687
2688 ui.write('\n'.join(ph.message) + '\n')
2688 ui.write('\n'.join(ph.message) + '\n')
2689
2689
2690 def lastsavename(path):
2690 def lastsavename(path):
2691 (directory, base) = os.path.split(path)
2691 (directory, base) = os.path.split(path)
2692 names = os.listdir(directory)
2692 names = os.listdir(directory)
2693 namere = re.compile("%s.([0-9]+)" % base)
2693 namere = re.compile("%s.([0-9]+)" % base)
2694 maxindex = None
2694 maxindex = None
2695 maxname = None
2695 maxname = None
2696 for f in names:
2696 for f in names:
2697 m = namere.match(f)
2697 m = namere.match(f)
2698 if m:
2698 if m:
2699 index = int(m.group(1))
2699 index = int(m.group(1))
2700 if maxindex is None or index > maxindex:
2700 if maxindex is None or index > maxindex:
2701 maxindex = index
2701 maxindex = index
2702 maxname = f
2702 maxname = f
2703 if maxname:
2703 if maxname:
2704 return (os.path.join(directory, maxname), maxindex)
2704 return (os.path.join(directory, maxname), maxindex)
2705 return (None, None)
2705 return (None, None)
2706
2706
2707 def savename(path):
2707 def savename(path):
2708 (last, index) = lastsavename(path)
2708 (last, index) = lastsavename(path)
2709 if last is None:
2709 if last is None:
2710 index = 0
2710 index = 0
2711 newpath = path + ".%d" % (index + 1)
2711 newpath = path + ".%d" % (index + 1)
2712 return newpath
2712 return newpath
2713
2713
2714 @command("^qpush",
2714 @command("^qpush",
2715 [('', 'keep-changes', None,
2715 [('', 'keep-changes', None,
2716 _('tolerate non-conflicting local changes')),
2716 _('tolerate non-conflicting local changes')),
2717 ('f', 'force', None, _('apply on top of local changes')),
2717 ('f', 'force', None, _('apply on top of local changes')),
2718 ('e', 'exact', None,
2718 ('e', 'exact', None,
2719 _('apply the target patch to its recorded parent')),
2719 _('apply the target patch to its recorded parent')),
2720 ('l', 'list', None, _('list patch name in commit text')),
2720 ('l', 'list', None, _('list patch name in commit text')),
2721 ('a', 'all', None, _('apply all patches')),
2721 ('a', 'all', None, _('apply all patches')),
2722 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2722 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2723 ('n', 'name', '',
2723 ('n', 'name', '',
2724 _('merge queue name (DEPRECATED)'), _('NAME')),
2724 _('merge queue name (DEPRECATED)'), _('NAME')),
2725 ('', 'move', None,
2725 ('', 'move', None,
2726 _('reorder patch series and apply only the patch')),
2726 _('reorder patch series and apply only the patch')),
2727 ('', 'no-backup', None, _('do not save backup copies of files'))],
2727 ('', 'no-backup', None, _('do not save backup copies of files'))],
2728 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2728 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2729 def push(ui, repo, patch=None, **opts):
2729 def push(ui, repo, patch=None, **opts):
2730 """push the next patch onto the stack
2730 """push the next patch onto the stack
2731
2731
2732 By default, abort if the working directory contains uncommitted
2732 By default, abort if the working directory contains uncommitted
2733 changes. With --keep-changes, abort only if the uncommitted files
2733 changes. With --keep-changes, abort only if the uncommitted files
2734 overlap with patched files. With -f/--force, backup and patch over
2734 overlap with patched files. With -f/--force, backup and patch over
2735 uncommitted changes.
2735 uncommitted changes.
2736
2736
2737 Return 0 on success.
2737 Return 0 on success.
2738 """
2738 """
2739 q = repo.mq
2739 q = repo.mq
2740 mergeq = None
2740 mergeq = None
2741
2741
2742 opts = fixkeepchangesopts(ui, opts)
2742 opts = fixkeepchangesopts(ui, opts)
2743 if opts.get('merge'):
2743 if opts.get('merge'):
2744 if opts.get('name'):
2744 if opts.get('name'):
2745 newpath = repo.join(opts.get('name'))
2745 newpath = repo.join(opts.get('name'))
2746 else:
2746 else:
2747 newpath, i = lastsavename(q.path)
2747 newpath, i = lastsavename(q.path)
2748 if not newpath:
2748 if not newpath:
2749 ui.warn(_("no saved queues found, please use -n\n"))
2749 ui.warn(_("no saved queues found, please use -n\n"))
2750 return 1
2750 return 1
2751 mergeq = queue(ui, repo.path, newpath)
2751 mergeq = queue(ui, repo.path, newpath)
2752 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2752 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2753 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2753 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2754 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2754 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2755 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2755 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2756 keepchanges=opts.get('keep_changes'))
2756 keepchanges=opts.get('keep_changes'))
2757 return ret
2757 return ret
2758
2758
2759 @command("^qpop",
2759 @command("^qpop",
2760 [('a', 'all', None, _('pop all patches')),
2760 [('a', 'all', None, _('pop all patches')),
2761 ('n', 'name', '',
2761 ('n', 'name', '',
2762 _('queue name to pop (DEPRECATED)'), _('NAME')),
2762 _('queue name to pop (DEPRECATED)'), _('NAME')),
2763 ('', 'keep-changes', None,
2763 ('', 'keep-changes', None,
2764 _('tolerate non-conflicting local changes')),
2764 _('tolerate non-conflicting local changes')),
2765 ('f', 'force', None, _('forget any local changes to patched files')),
2765 ('f', 'force', None, _('forget any local changes to patched files')),
2766 ('', 'no-backup', None, _('do not save backup copies of files'))],
2766 ('', 'no-backup', None, _('do not save backup copies of files'))],
2767 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2767 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2768 def pop(ui, repo, patch=None, **opts):
2768 def pop(ui, repo, patch=None, **opts):
2769 """pop the current patch off the stack
2769 """pop the current patch off the stack
2770
2770
2771 Without argument, pops off the top of the patch stack. If given a
2771 Without argument, pops off the top of the patch stack. If given a
2772 patch name, keeps popping off patches until the named patch is at
2772 patch name, keeps popping off patches until the named patch is at
2773 the top of the stack.
2773 the top of the stack.
2774
2774
2775 By default, abort if the working directory contains uncommitted
2775 By default, abort if the working directory contains uncommitted
2776 changes. With --keep-changes, abort only if the uncommitted files
2776 changes. With --keep-changes, abort only if the uncommitted files
2777 overlap with patched files. With -f/--force, backup and discard
2777 overlap with patched files. With -f/--force, backup and discard
2778 changes made to such files.
2778 changes made to such files.
2779
2779
2780 Return 0 on success.
2780 Return 0 on success.
2781 """
2781 """
2782 opts = fixkeepchangesopts(ui, opts)
2782 opts = fixkeepchangesopts(ui, opts)
2783 localupdate = True
2783 localupdate = True
2784 if opts.get('name'):
2784 if opts.get('name'):
2785 q = queue(ui, repo.path, repo.join(opts.get('name')))
2785 q = queue(ui, repo.path, repo.join(opts.get('name')))
2786 ui.warn(_('using patch queue: %s\n') % q.path)
2786 ui.warn(_('using patch queue: %s\n') % q.path)
2787 localupdate = False
2787 localupdate = False
2788 else:
2788 else:
2789 q = repo.mq
2789 q = repo.mq
2790 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2790 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2791 all=opts.get('all'), nobackup=opts.get('no_backup'),
2791 all=opts.get('all'), nobackup=opts.get('no_backup'),
2792 keepchanges=opts.get('keep_changes'))
2792 keepchanges=opts.get('keep_changes'))
2793 q.savedirty()
2793 q.savedirty()
2794 return ret
2794 return ret
2795
2795
2796 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2796 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2797 def rename(ui, repo, patch, name=None, **opts):
2797 def rename(ui, repo, patch, name=None, **opts):
2798 """rename a patch
2798 """rename a patch
2799
2799
2800 With one argument, renames the current patch to PATCH1.
2800 With one argument, renames the current patch to PATCH1.
2801 With two arguments, renames PATCH1 to PATCH2.
2801 With two arguments, renames PATCH1 to PATCH2.
2802
2802
2803 Returns 0 on success."""
2803 Returns 0 on success."""
2804 q = repo.mq
2804 q = repo.mq
2805 if not name:
2805 if not name:
2806 name = patch
2806 name = patch
2807 patch = None
2807 patch = None
2808
2808
2809 if patch:
2809 if patch:
2810 patch = q.lookup(patch)
2810 patch = q.lookup(patch)
2811 else:
2811 else:
2812 if not q.applied:
2812 if not q.applied:
2813 ui.write(_('no patches applied\n'))
2813 ui.write(_('no patches applied\n'))
2814 return
2814 return
2815 patch = q.lookup('qtip')
2815 patch = q.lookup('qtip')
2816 absdest = q.join(name)
2816 absdest = q.join(name)
2817 if os.path.isdir(absdest):
2817 if os.path.isdir(absdest):
2818 name = normname(os.path.join(name, os.path.basename(patch)))
2818 name = normname(os.path.join(name, os.path.basename(patch)))
2819 absdest = q.join(name)
2819 absdest = q.join(name)
2820 q.checkpatchname(name)
2820 q.checkpatchname(name)
2821
2821
2822 ui.note(_('renaming %s to %s\n') % (patch, name))
2822 ui.note(_('renaming %s to %s\n') % (patch, name))
2823 i = q.findseries(patch)
2823 i = q.findseries(patch)
2824 guards = q.guard_re.findall(q.fullseries[i])
2824 guards = q.guard_re.findall(q.fullseries[i])
2825 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2825 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2826 q.parseseries()
2826 q.parseseries()
2827 q.seriesdirty = True
2827 q.seriesdirty = True
2828
2828
2829 info = q.isapplied(patch)
2829 info = q.isapplied(patch)
2830 if info:
2830 if info:
2831 q.applied[info[0]] = statusentry(info[1], name)
2831 q.applied[info[0]] = statusentry(info[1], name)
2832 q.applieddirty = True
2832 q.applieddirty = True
2833
2833
2834 destdir = os.path.dirname(absdest)
2834 destdir = os.path.dirname(absdest)
2835 if not os.path.isdir(destdir):
2835 if not os.path.isdir(destdir):
2836 os.makedirs(destdir)
2836 os.makedirs(destdir)
2837 util.rename(q.join(patch), absdest)
2837 util.rename(q.join(patch), absdest)
2838 r = q.qrepo()
2838 r = q.qrepo()
2839 if r and patch in r.dirstate:
2839 if r and patch in r.dirstate:
2840 wctx = r[None]
2840 wctx = r[None]
2841 wlock = r.wlock()
2841 wlock = r.wlock()
2842 try:
2842 try:
2843 if r.dirstate[patch] == 'a':
2843 if r.dirstate[patch] == 'a':
2844 r.dirstate.drop(patch)
2844 r.dirstate.drop(patch)
2845 r.dirstate.add(name)
2845 r.dirstate.add(name)
2846 else:
2846 else:
2847 wctx.copy(patch, name)
2847 wctx.copy(patch, name)
2848 wctx.forget([patch])
2848 wctx.forget([patch])
2849 finally:
2849 finally:
2850 wlock.release()
2850 wlock.release()
2851
2851
2852 q.savedirty()
2852 q.savedirty()
2853
2853
2854 @command("qrestore",
2854 @command("qrestore",
2855 [('d', 'delete', None, _('delete save entry')),
2855 [('d', 'delete', None, _('delete save entry')),
2856 ('u', 'update', None, _('update queue working directory'))],
2856 ('u', 'update', None, _('update queue working directory'))],
2857 _('hg qrestore [-d] [-u] REV'))
2857 _('hg qrestore [-d] [-u] REV'))
2858 def restore(ui, repo, rev, **opts):
2858 def restore(ui, repo, rev, **opts):
2859 """restore the queue state saved by a revision (DEPRECATED)
2859 """restore the queue state saved by a revision (DEPRECATED)
2860
2860
2861 This command is deprecated, use :hg:`rebase` instead."""
2861 This command is deprecated, use :hg:`rebase` instead."""
2862 rev = repo.lookup(rev)
2862 rev = repo.lookup(rev)
2863 q = repo.mq
2863 q = repo.mq
2864 q.restore(repo, rev, delete=opts.get('delete'),
2864 q.restore(repo, rev, delete=opts.get('delete'),
2865 qupdate=opts.get('update'))
2865 qupdate=opts.get('update'))
2866 q.savedirty()
2866 q.savedirty()
2867 return 0
2867 return 0
2868
2868
2869 @command("qsave",
2869 @command("qsave",
2870 [('c', 'copy', None, _('copy patch directory')),
2870 [('c', 'copy', None, _('copy patch directory')),
2871 ('n', 'name', '',
2871 ('n', 'name', '',
2872 _('copy directory name'), _('NAME')),
2872 _('copy directory name'), _('NAME')),
2873 ('e', 'empty', None, _('clear queue status file')),
2873 ('e', 'empty', None, _('clear queue status file')),
2874 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2874 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2875 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2875 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2876 def save(ui, repo, **opts):
2876 def save(ui, repo, **opts):
2877 """save current queue state (DEPRECATED)
2877 """save current queue state (DEPRECATED)
2878
2878
2879 This command is deprecated, use :hg:`rebase` instead."""
2879 This command is deprecated, use :hg:`rebase` instead."""
2880 q = repo.mq
2880 q = repo.mq
2881 message = cmdutil.logmessage(ui, opts)
2881 message = cmdutil.logmessage(ui, opts)
2882 ret = q.save(repo, msg=message)
2882 ret = q.save(repo, msg=message)
2883 if ret:
2883 if ret:
2884 return ret
2884 return ret
2885 q.savedirty() # save to .hg/patches before copying
2885 q.savedirty() # save to .hg/patches before copying
2886 if opts.get('copy'):
2886 if opts.get('copy'):
2887 path = q.path
2887 path = q.path
2888 if opts.get('name'):
2888 if opts.get('name'):
2889 newpath = os.path.join(q.basepath, opts.get('name'))
2889 newpath = os.path.join(q.basepath, opts.get('name'))
2890 if os.path.exists(newpath):
2890 if os.path.exists(newpath):
2891 if not os.path.isdir(newpath):
2891 if not os.path.isdir(newpath):
2892 raise util.Abort(_('destination %s exists and is not '
2892 raise util.Abort(_('destination %s exists and is not '
2893 'a directory') % newpath)
2893 'a directory') % newpath)
2894 if not opts.get('force'):
2894 if not opts.get('force'):
2895 raise util.Abort(_('destination %s exists, '
2895 raise util.Abort(_('destination %s exists, '
2896 'use -f to force') % newpath)
2896 'use -f to force') % newpath)
2897 else:
2897 else:
2898 newpath = savename(path)
2898 newpath = savename(path)
2899 ui.warn(_("copy %s to %s\n") % (path, newpath))
2899 ui.warn(_("copy %s to %s\n") % (path, newpath))
2900 util.copyfiles(path, newpath)
2900 util.copyfiles(path, newpath)
2901 if opts.get('empty'):
2901 if opts.get('empty'):
2902 del q.applied[:]
2902 del q.applied[:]
2903 q.applieddirty = True
2903 q.applieddirty = True
2904 q.savedirty()
2904 q.savedirty()
2905 return 0
2905 return 0
2906
2906
2907 @command("strip",
2907 @command("strip",
2908 [
2908 [
2909 ('r', 'rev', [], _('strip specified revision (optional, '
2909 ('r', 'rev', [], _('strip specified revision (optional, '
2910 'can specify revisions without this '
2910 'can specify revisions without this '
2911 'option)'), _('REV')),
2911 'option)'), _('REV')),
2912 ('f', 'force', None, _('force removal of changesets, discard '
2912 ('f', 'force', None, _('force removal of changesets, discard '
2913 'uncommitted changes (no backup)')),
2913 'uncommitted changes (no backup)')),
2914 ('b', 'backup', None, _('bundle only changesets with local revision'
2914 ('b', 'backup', None, _('bundle only changesets with local revision'
2915 ' number greater than REV which are not'
2915 ' number greater than REV which are not'
2916 ' descendants of REV (DEPRECATED)')),
2916 ' descendants of REV (DEPRECATED)')),
2917 ('', 'no-backup', None, _('no backups')),
2917 ('', 'no-backup', None, _('no backups')),
2918 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2918 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2919 ('n', '', None, _('ignored (DEPRECATED)')),
2919 ('n', '', None, _('ignored (DEPRECATED)')),
2920 ('k', 'keep', None, _("do not modify working copy during strip")),
2920 ('k', 'keep', None, _("do not modify working copy during strip")),
2921 ('B', 'bookmark', '', _("remove revs only reachable from given"
2921 ('B', 'bookmark', '', _("remove revs only reachable from given"
2922 " bookmark"))],
2922 " bookmark"))],
2923 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
2923 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
2924 def strip(ui, repo, *revs, **opts):
2924 def strip(ui, repo, *revs, **opts):
2925 """strip changesets and all their descendants from the repository
2925 """strip changesets and all their descendants from the repository
2926
2926
2927 The strip command removes the specified changesets and all their
2927 The strip command removes the specified changesets and all their
2928 descendants. If the working directory has uncommitted changes, the
2928 descendants. If the working directory has uncommitted changes, the
2929 operation is aborted unless the --force flag is supplied, in which
2929 operation is aborted unless the --force flag is supplied, in which
2930 case changes will be discarded.
2930 case changes will be discarded.
2931
2931
2932 If a parent of the working directory is stripped, then the working
2932 If a parent of the working directory is stripped, then the working
2933 directory will automatically be updated to the most recent
2933 directory will automatically be updated to the most recent
2934 available ancestor of the stripped parent after the operation
2934 available ancestor of the stripped parent after the operation
2935 completes.
2935 completes.
2936
2936
2937 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2937 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2938 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2938 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2939 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2939 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2940 where BUNDLE is the bundle file created by the strip. Note that
2940 where BUNDLE is the bundle file created by the strip. Note that
2941 the local revision numbers will in general be different after the
2941 the local revision numbers will in general be different after the
2942 restore.
2942 restore.
2943
2943
2944 Use the --no-backup option to discard the backup bundle once the
2944 Use the --no-backup option to discard the backup bundle once the
2945 operation completes.
2945 operation completes.
2946
2946
2947 Strip is not a history-rewriting operation and can be used on
2947 Strip is not a history-rewriting operation and can be used on
2948 changesets in the public phase. But if the stripped changesets have
2948 changesets in the public phase. But if the stripped changesets have
2949 been pushed to a remote repository you will likely pull them again.
2949 been pushed to a remote repository you will likely pull them again.
2950
2950
2951 Return 0 on success.
2951 Return 0 on success.
2952 """
2952 """
2953 backup = 'all'
2953 backup = 'all'
2954 if opts.get('backup'):
2954 if opts.get('backup'):
2955 backup = 'strip'
2955 backup = 'strip'
2956 elif opts.get('no_backup') or opts.get('nobackup'):
2956 elif opts.get('no_backup') or opts.get('nobackup'):
2957 backup = 'none'
2957 backup = 'none'
2958
2958
2959 cl = repo.changelog
2959 cl = repo.changelog
2960 revs = list(revs) + opts.get('rev')
2960 revs = list(revs) + opts.get('rev')
2961 revs = set(scmutil.revrange(repo, revs))
2961 revs = set(scmutil.revrange(repo, revs))
2962
2962
2963 if opts.get('bookmark'):
2963 if opts.get('bookmark'):
2964 mark = opts.get('bookmark')
2964 mark = opts.get('bookmark')
2965 marks = repo._bookmarks
2965 marks = repo._bookmarks
2966 if mark not in marks:
2966 if mark not in marks:
2967 raise util.Abort(_("bookmark '%s' not found") % mark)
2967 raise util.Abort(_("bookmark '%s' not found") % mark)
2968
2968
2969 # If the requested bookmark is not the only one pointing to a
2969 # If the requested bookmark is not the only one pointing to a
2970 # a revision we have to only delete the bookmark and not strip
2970 # a revision we have to only delete the bookmark and not strip
2971 # anything. revsets cannot detect that case.
2971 # anything. revsets cannot detect that case.
2972 uniquebm = True
2972 uniquebm = True
2973 for m, n in marks.iteritems():
2973 for m, n in marks.iteritems():
2974 if m != mark and n == repo[mark].node():
2974 if m != mark and n == repo[mark].node():
2975 uniquebm = False
2975 uniquebm = False
2976 break
2976 break
2977 if uniquebm:
2977 if uniquebm:
2978 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
2978 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
2979 "ancestors(head() and not bookmark(%s)) - "
2979 "ancestors(head() and not bookmark(%s)) - "
2980 "ancestors(bookmark() and not bookmark(%s))",
2980 "ancestors(bookmark() and not bookmark(%s))",
2981 mark, mark, mark)
2981 mark, mark, mark)
2982 revs.update(set(rsrevs))
2982 revs.update(set(rsrevs))
2983 if not revs:
2983 if not revs:
2984 del marks[mark]
2984 del marks[mark]
2985 repo._writebookmarks(mark)
2985 repo._writebookmarks(mark)
2986 ui.write(_("bookmark '%s' deleted\n") % mark)
2986 ui.write(_("bookmark '%s' deleted\n") % mark)
2987
2987
2988 if not revs:
2988 if not revs:
2989 raise util.Abort(_('empty revision set'))
2989 raise util.Abort(_('empty revision set'))
2990
2990
2991 descendants = set(cl.descendants(revs))
2991 descendants = set(cl.descendants(revs))
2992 strippedrevs = revs.union(descendants)
2992 strippedrevs = revs.union(descendants)
2993 roots = revs.difference(descendants)
2993 roots = revs.difference(descendants)
2994
2994
2995 update = False
2995 update = False
2996 # if one of the wdir parent is stripped we'll need
2996 # if one of the wdir parent is stripped we'll need
2997 # to update away to an earlier revision
2997 # to update away to an earlier revision
2998 for p in repo.dirstate.parents():
2998 for p in repo.dirstate.parents():
2999 if p != nullid and cl.rev(p) in strippedrevs:
2999 if p != nullid and cl.rev(p) in strippedrevs:
3000 update = True
3000 update = True
3001 break
3001 break
3002
3002
3003 rootnodes = set(cl.node(r) for r in roots)
3003 rootnodes = set(cl.node(r) for r in roots)
3004
3004
3005 q = repo.mq
3005 q = repo.mq
3006 if q.applied:
3006 if q.applied:
3007 # refresh queue state if we're about to strip
3007 # refresh queue state if we're about to strip
3008 # applied patches
3008 # applied patches
3009 if cl.rev(repo.lookup('qtip')) in strippedrevs:
3009 if cl.rev(repo.lookup('qtip')) in strippedrevs:
3010 q.applieddirty = True
3010 q.applieddirty = True
3011 start = 0
3011 start = 0
3012 end = len(q.applied)
3012 end = len(q.applied)
3013 for i, statusentry in enumerate(q.applied):
3013 for i, statusentry in enumerate(q.applied):
3014 if statusentry.node in rootnodes:
3014 if statusentry.node in rootnodes:
3015 # if one of the stripped roots is an applied
3015 # if one of the stripped roots is an applied
3016 # patch, only part of the queue is stripped
3016 # patch, only part of the queue is stripped
3017 start = i
3017 start = i
3018 break
3018 break
3019 del q.applied[start:end]
3019 del q.applied[start:end]
3020 q.savedirty()
3020 q.savedirty()
3021
3021
3022 revs = list(rootnodes)
3022 revs = list(rootnodes)
3023 if update and opts.get('keep'):
3023 if update and opts.get('keep'):
3024 wlock = repo.wlock()
3024 wlock = repo.wlock()
3025 try:
3025 try:
3026 urev = repo.mq.qparents(repo, revs[0])
3026 urev = repo.mq.qparents(repo, revs[0])
3027 repo.dirstate.rebuild(urev, repo[urev].manifest())
3027 repo.dirstate.rebuild(urev, repo[urev].manifest())
3028 repo.dirstate.write()
3028 repo.dirstate.write()
3029 update = False
3029 update = False
3030 finally:
3030 finally:
3031 wlock.release()
3031 wlock.release()
3032
3032
3033 if opts.get('bookmark'):
3033 if opts.get('bookmark'):
3034 del marks[mark]
3034 del marks[mark]
3035 repo._writebookmarks(marks)
3035 repo._writebookmarks(marks)
3036 ui.write(_("bookmark '%s' deleted\n") % mark)
3036 ui.write(_("bookmark '%s' deleted\n") % mark)
3037
3037
3038 repo.mq.strip(repo, revs, backup=backup, update=update,
3038 repo.mq.strip(repo, revs, backup=backup, update=update,
3039 force=opts.get('force'))
3039 force=opts.get('force'))
3040
3040
3041 return 0
3041 return 0
3042
3042
3043 @command("qselect",
3043 @command("qselect",
3044 [('n', 'none', None, _('disable all guards')),
3044 [('n', 'none', None, _('disable all guards')),
3045 ('s', 'series', None, _('list all guards in series file')),
3045 ('s', 'series', None, _('list all guards in series file')),
3046 ('', 'pop', None, _('pop to before first guarded applied patch')),
3046 ('', 'pop', None, _('pop to before first guarded applied patch')),
3047 ('', 'reapply', None, _('pop, then reapply patches'))],
3047 ('', 'reapply', None, _('pop, then reapply patches'))],
3048 _('hg qselect [OPTION]... [GUARD]...'))
3048 _('hg qselect [OPTION]... [GUARD]...'))
3049 def select(ui, repo, *args, **opts):
3049 def select(ui, repo, *args, **opts):
3050 '''set or print guarded patches to push
3050 '''set or print guarded patches to push
3051
3051
3052 Use the :hg:`qguard` command to set or print guards on patch, then use
3052 Use the :hg:`qguard` command to set or print guards on patch, then use
3053 qselect to tell mq which guards to use. A patch will be pushed if
3053 qselect to tell mq which guards to use. A patch will be pushed if
3054 it has no guards or any positive guards match the currently
3054 it has no guards or any positive guards match the currently
3055 selected guard, but will not be pushed if any negative guards
3055 selected guard, but will not be pushed if any negative guards
3056 match the current guard. For example::
3056 match the current guard. For example::
3057
3057
3058 qguard foo.patch -- -stable (negative guard)
3058 qguard foo.patch -- -stable (negative guard)
3059 qguard bar.patch +stable (positive guard)
3059 qguard bar.patch +stable (positive guard)
3060 qselect stable
3060 qselect stable
3061
3061
3062 This activates the "stable" guard. mq will skip foo.patch (because
3062 This activates the "stable" guard. mq will skip foo.patch (because
3063 it has a negative match) but push bar.patch (because it has a
3063 it has a negative match) but push bar.patch (because it has a
3064 positive match).
3064 positive match).
3065
3065
3066 With no arguments, prints the currently active guards.
3066 With no arguments, prints the currently active guards.
3067 With one argument, sets the active guard.
3067 With one argument, sets the active guard.
3068
3068
3069 Use -n/--none to deactivate guards (no other arguments needed).
3069 Use -n/--none to deactivate guards (no other arguments needed).
3070 When no guards are active, patches with positive guards are
3070 When no guards are active, patches with positive guards are
3071 skipped and patches with negative guards are pushed.
3071 skipped and patches with negative guards are pushed.
3072
3072
3073 qselect can change the guards on applied patches. It does not pop
3073 qselect can change the guards on applied patches. It does not pop
3074 guarded patches by default. Use --pop to pop back to the last
3074 guarded patches by default. Use --pop to pop back to the last
3075 applied patch that is not guarded. Use --reapply (which implies
3075 applied patch that is not guarded. Use --reapply (which implies
3076 --pop) to push back to the current patch afterwards, but skip
3076 --pop) to push back to the current patch afterwards, but skip
3077 guarded patches.
3077 guarded patches.
3078
3078
3079 Use -s/--series to print a list of all guards in the series file
3079 Use -s/--series to print a list of all guards in the series file
3080 (no other arguments needed). Use -v for more information.
3080 (no other arguments needed). Use -v for more information.
3081
3081
3082 Returns 0 on success.'''
3082 Returns 0 on success.'''
3083
3083
3084 q = repo.mq
3084 q = repo.mq
3085 guards = q.active()
3085 guards = q.active()
3086 if args or opts.get('none'):
3086 if args or opts.get('none'):
3087 old_unapplied = q.unapplied(repo)
3087 old_unapplied = q.unapplied(repo)
3088 old_guarded = [i for i in xrange(len(q.applied)) if
3088 old_guarded = [i for i in xrange(len(q.applied)) if
3089 not q.pushable(i)[0]]
3089 not q.pushable(i)[0]]
3090 q.setactive(args)
3090 q.setactive(args)
3091 q.savedirty()
3091 q.savedirty()
3092 if not args:
3092 if not args:
3093 ui.status(_('guards deactivated\n'))
3093 ui.status(_('guards deactivated\n'))
3094 if not opts.get('pop') and not opts.get('reapply'):
3094 if not opts.get('pop') and not opts.get('reapply'):
3095 unapplied = q.unapplied(repo)
3095 unapplied = q.unapplied(repo)
3096 guarded = [i for i in xrange(len(q.applied))
3096 guarded = [i for i in xrange(len(q.applied))
3097 if not q.pushable(i)[0]]
3097 if not q.pushable(i)[0]]
3098 if len(unapplied) != len(old_unapplied):
3098 if len(unapplied) != len(old_unapplied):
3099 ui.status(_('number of unguarded, unapplied patches has '
3099 ui.status(_('number of unguarded, unapplied patches has '
3100 'changed from %d to %d\n') %
3100 'changed from %d to %d\n') %
3101 (len(old_unapplied), len(unapplied)))
3101 (len(old_unapplied), len(unapplied)))
3102 if len(guarded) != len(old_guarded):
3102 if len(guarded) != len(old_guarded):
3103 ui.status(_('number of guarded, applied patches has changed '
3103 ui.status(_('number of guarded, applied patches has changed '
3104 'from %d to %d\n') %
3104 'from %d to %d\n') %
3105 (len(old_guarded), len(guarded)))
3105 (len(old_guarded), len(guarded)))
3106 elif opts.get('series'):
3106 elif opts.get('series'):
3107 guards = {}
3107 guards = {}
3108 noguards = 0
3108 noguards = 0
3109 for gs in q.seriesguards:
3109 for gs in q.seriesguards:
3110 if not gs:
3110 if not gs:
3111 noguards += 1
3111 noguards += 1
3112 for g in gs:
3112 for g in gs:
3113 guards.setdefault(g, 0)
3113 guards.setdefault(g, 0)
3114 guards[g] += 1
3114 guards[g] += 1
3115 if ui.verbose:
3115 if ui.verbose:
3116 guards['NONE'] = noguards
3116 guards['NONE'] = noguards
3117 guards = guards.items()
3117 guards = guards.items()
3118 guards.sort(key=lambda x: x[0][1:])
3118 guards.sort(key=lambda x: x[0][1:])
3119 if guards:
3119 if guards:
3120 ui.note(_('guards in series file:\n'))
3120 ui.note(_('guards in series file:\n'))
3121 for guard, count in guards:
3121 for guard, count in guards:
3122 ui.note('%2d ' % count)
3122 ui.note('%2d ' % count)
3123 ui.write(guard, '\n')
3123 ui.write(guard, '\n')
3124 else:
3124 else:
3125 ui.note(_('no guards in series file\n'))
3125 ui.note(_('no guards in series file\n'))
3126 else:
3126 else:
3127 if guards:
3127 if guards:
3128 ui.note(_('active guards:\n'))
3128 ui.note(_('active guards:\n'))
3129 for g in guards:
3129 for g in guards:
3130 ui.write(g, '\n')
3130 ui.write(g, '\n')
3131 else:
3131 else:
3132 ui.write(_('no active guards\n'))
3132 ui.write(_('no active guards\n'))
3133 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3133 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3134 popped = False
3134 popped = False
3135 if opts.get('pop') or opts.get('reapply'):
3135 if opts.get('pop') or opts.get('reapply'):
3136 for i in xrange(len(q.applied)):
3136 for i in xrange(len(q.applied)):
3137 pushable, reason = q.pushable(i)
3137 pushable, reason = q.pushable(i)
3138 if not pushable:
3138 if not pushable:
3139 ui.status(_('popping guarded patches\n'))
3139 ui.status(_('popping guarded patches\n'))
3140 popped = True
3140 popped = True
3141 if i == 0:
3141 if i == 0:
3142 q.pop(repo, all=True)
3142 q.pop(repo, all=True)
3143 else:
3143 else:
3144 q.pop(repo, str(i - 1))
3144 q.pop(repo, str(i - 1))
3145 break
3145 break
3146 if popped:
3146 if popped:
3147 try:
3147 try:
3148 if reapply:
3148 if reapply:
3149 ui.status(_('reapplying unguarded patches\n'))
3149 ui.status(_('reapplying unguarded patches\n'))
3150 q.push(repo, reapply)
3150 q.push(repo, reapply)
3151 finally:
3151 finally:
3152 q.savedirty()
3152 q.savedirty()
3153
3153
3154 @command("qfinish",
3154 @command("qfinish",
3155 [('a', 'applied', None, _('finish all applied changesets'))],
3155 [('a', 'applied', None, _('finish all applied changesets'))],
3156 _('hg qfinish [-a] [REV]...'))
3156 _('hg qfinish [-a] [REV]...'))
3157 def finish(ui, repo, *revrange, **opts):
3157 def finish(ui, repo, *revrange, **opts):
3158 """move applied patches into repository history
3158 """move applied patches into repository history
3159
3159
3160 Finishes the specified revisions (corresponding to applied
3160 Finishes the specified revisions (corresponding to applied
3161 patches) by moving them out of mq control into regular repository
3161 patches) by moving them out of mq control into regular repository
3162 history.
3162 history.
3163
3163
3164 Accepts a revision range or the -a/--applied option. If --applied
3164 Accepts a revision range or the -a/--applied option. If --applied
3165 is specified, all applied mq revisions are removed from mq
3165 is specified, all applied mq revisions are removed from mq
3166 control. Otherwise, the given revisions must be at the base of the
3166 control. Otherwise, the given revisions must be at the base of the
3167 stack of applied patches.
3167 stack of applied patches.
3168
3168
3169 This can be especially useful if your changes have been applied to
3169 This can be especially useful if your changes have been applied to
3170 an upstream repository, or if you are about to push your changes
3170 an upstream repository, or if you are about to push your changes
3171 to upstream.
3171 to upstream.
3172
3172
3173 Returns 0 on success.
3173 Returns 0 on success.
3174 """
3174 """
3175 if not opts.get('applied') and not revrange:
3175 if not opts.get('applied') and not revrange:
3176 raise util.Abort(_('no revisions specified'))
3176 raise util.Abort(_('no revisions specified'))
3177 elif opts.get('applied'):
3177 elif opts.get('applied'):
3178 revrange = ('qbase::qtip',) + revrange
3178 revrange = ('qbase::qtip',) + revrange
3179
3179
3180 q = repo.mq
3180 q = repo.mq
3181 if not q.applied:
3181 if not q.applied:
3182 ui.status(_('no patches applied\n'))
3182 ui.status(_('no patches applied\n'))
3183 return 0
3183 return 0
3184
3184
3185 revs = scmutil.revrange(repo, revrange)
3185 revs = scmutil.revrange(repo, revrange)
3186 if repo['.'].rev() in revs and repo[None].files():
3186 if repo['.'].rev() in revs and repo[None].files():
3187 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3187 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3188 # queue.finish may changes phases but leave the responsibility to lock the
3188 # queue.finish may changes phases but leave the responsibility to lock the
3189 # repo to the caller to avoid deadlock with wlock. This command code is
3189 # repo to the caller to avoid deadlock with wlock. This command code is
3190 # responsibility for this locking.
3190 # responsibility for this locking.
3191 lock = repo.lock()
3191 lock = repo.lock()
3192 try:
3192 try:
3193 q.finish(repo, revs)
3193 q.finish(repo, revs)
3194 q.savedirty()
3194 q.savedirty()
3195 finally:
3195 finally:
3196 lock.release()
3196 lock.release()
3197 return 0
3197 return 0
3198
3198
3199 @command("qqueue",
3199 @command("qqueue",
3200 [('l', 'list', False, _('list all available queues')),
3200 [('l', 'list', False, _('list all available queues')),
3201 ('', 'active', False, _('print name of active queue')),
3201 ('', 'active', False, _('print name of active queue')),
3202 ('c', 'create', False, _('create new queue')),
3202 ('c', 'create', False, _('create new queue')),
3203 ('', 'rename', False, _('rename active queue')),
3203 ('', 'rename', False, _('rename active queue')),
3204 ('', 'delete', False, _('delete reference to queue')),
3204 ('', 'delete', False, _('delete reference to queue')),
3205 ('', 'purge', False, _('delete queue, and remove patch dir')),
3205 ('', 'purge', False, _('delete queue, and remove patch dir')),
3206 ],
3206 ],
3207 _('[OPTION] [QUEUE]'))
3207 _('[OPTION] [QUEUE]'))
3208 def qqueue(ui, repo, name=None, **opts):
3208 def qqueue(ui, repo, name=None, **opts):
3209 '''manage multiple patch queues
3209 '''manage multiple patch queues
3210
3210
3211 Supports switching between different patch queues, as well as creating
3211 Supports switching between different patch queues, as well as creating
3212 new patch queues and deleting existing ones.
3212 new patch queues and deleting existing ones.
3213
3213
3214 Omitting a queue name or specifying -l/--list will show you the registered
3214 Omitting a queue name or specifying -l/--list will show you the registered
3215 queues - by default the "normal" patches queue is registered. The currently
3215 queues - by default the "normal" patches queue is registered. The currently
3216 active queue will be marked with "(active)". Specifying --active will print
3216 active queue will be marked with "(active)". Specifying --active will print
3217 only the name of the active queue.
3217 only the name of the active queue.
3218
3218
3219 To create a new queue, use -c/--create. The queue is automatically made
3219 To create a new queue, use -c/--create. The queue is automatically made
3220 active, except in the case where there are applied patches from the
3220 active, except in the case where there are applied patches from the
3221 currently active queue in the repository. Then the queue will only be
3221 currently active queue in the repository. Then the queue will only be
3222 created and switching will fail.
3222 created and switching will fail.
3223
3223
3224 To delete an existing queue, use --delete. You cannot delete the currently
3224 To delete an existing queue, use --delete. You cannot delete the currently
3225 active queue.
3225 active queue.
3226
3226
3227 Returns 0 on success.
3227 Returns 0 on success.
3228 '''
3228 '''
3229 q = repo.mq
3229 q = repo.mq
3230 _defaultqueue = 'patches'
3230 _defaultqueue = 'patches'
3231 _allqueues = 'patches.queues'
3231 _allqueues = 'patches.queues'
3232 _activequeue = 'patches.queue'
3232 _activequeue = 'patches.queue'
3233
3233
3234 def _getcurrent():
3234 def _getcurrent():
3235 cur = os.path.basename(q.path)
3235 cur = os.path.basename(q.path)
3236 if cur.startswith('patches-'):
3236 if cur.startswith('patches-'):
3237 cur = cur[8:]
3237 cur = cur[8:]
3238 return cur
3238 return cur
3239
3239
3240 def _noqueues():
3240 def _noqueues():
3241 try:
3241 try:
3242 fh = repo.opener(_allqueues, 'r')
3242 fh = repo.opener(_allqueues, 'r')
3243 fh.close()
3243 fh.close()
3244 except IOError:
3244 except IOError:
3245 return True
3245 return True
3246
3246
3247 return False
3247 return False
3248
3248
3249 def _getqueues():
3249 def _getqueues():
3250 current = _getcurrent()
3250 current = _getcurrent()
3251
3251
3252 try:
3252 try:
3253 fh = repo.opener(_allqueues, 'r')
3253 fh = repo.opener(_allqueues, 'r')
3254 queues = [queue.strip() for queue in fh if queue.strip()]
3254 queues = [queue.strip() for queue in fh if queue.strip()]
3255 fh.close()
3255 fh.close()
3256 if current not in queues:
3256 if current not in queues:
3257 queues.append(current)
3257 queues.append(current)
3258 except IOError:
3258 except IOError:
3259 queues = [_defaultqueue]
3259 queues = [_defaultqueue]
3260
3260
3261 return sorted(queues)
3261 return sorted(queues)
3262
3262
3263 def _setactive(name):
3263 def _setactive(name):
3264 if q.applied:
3264 if q.applied:
3265 raise util.Abort(_('patches applied - cannot set new queue active'))
3265 raise util.Abort(_('patches applied - cannot set new queue active'))
3266 _setactivenocheck(name)
3266 _setactivenocheck(name)
3267
3267
3268 def _setactivenocheck(name):
3268 def _setactivenocheck(name):
3269 fh = repo.opener(_activequeue, 'w')
3269 fh = repo.opener(_activequeue, 'w')
3270 if name != 'patches':
3270 if name != 'patches':
3271 fh.write(name)
3271 fh.write(name)
3272 fh.close()
3272 fh.close()
3273
3273
3274 def _addqueue(name):
3274 def _addqueue(name):
3275 fh = repo.opener(_allqueues, 'a')
3275 fh = repo.opener(_allqueues, 'a')
3276 fh.write('%s\n' % (name,))
3276 fh.write('%s\n' % (name,))
3277 fh.close()
3277 fh.close()
3278
3278
3279 def _queuedir(name):
3279 def _queuedir(name):
3280 if name == 'patches':
3280 if name == 'patches':
3281 return repo.join('patches')
3281 return repo.join('patches')
3282 else:
3282 else:
3283 return repo.join('patches-' + name)
3283 return repo.join('patches-' + name)
3284
3284
3285 def _validname(name):
3285 def _validname(name):
3286 for n in name:
3286 for n in name:
3287 if n in ':\\/.':
3287 if n in ':\\/.':
3288 return False
3288 return False
3289 return True
3289 return True
3290
3290
3291 def _delete(name):
3291 def _delete(name):
3292 if name not in existing:
3292 if name not in existing:
3293 raise util.Abort(_('cannot delete queue that does not exist'))
3293 raise util.Abort(_('cannot delete queue that does not exist'))
3294
3294
3295 current = _getcurrent()
3295 current = _getcurrent()
3296
3296
3297 if name == current:
3297 if name == current:
3298 raise util.Abort(_('cannot delete currently active queue'))
3298 raise util.Abort(_('cannot delete currently active queue'))
3299
3299
3300 fh = repo.opener('patches.queues.new', 'w')
3300 fh = repo.opener('patches.queues.new', 'w')
3301 for queue in existing:
3301 for queue in existing:
3302 if queue == name:
3302 if queue == name:
3303 continue
3303 continue
3304 fh.write('%s\n' % (queue,))
3304 fh.write('%s\n' % (queue,))
3305 fh.close()
3305 fh.close()
3306 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3306 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3307
3307
3308 if not name or opts.get('list') or opts.get('active'):
3308 if not name or opts.get('list') or opts.get('active'):
3309 current = _getcurrent()
3309 current = _getcurrent()
3310 if opts.get('active'):
3310 if opts.get('active'):
3311 ui.write('%s\n' % (current,))
3311 ui.write('%s\n' % (current,))
3312 return
3312 return
3313 for queue in _getqueues():
3313 for queue in _getqueues():
3314 ui.write('%s' % (queue,))
3314 ui.write('%s' % (queue,))
3315 if queue == current and not ui.quiet:
3315 if queue == current and not ui.quiet:
3316 ui.write(_(' (active)\n'))
3316 ui.write(_(' (active)\n'))
3317 else:
3317 else:
3318 ui.write('\n')
3318 ui.write('\n')
3319 return
3319 return
3320
3320
3321 if not _validname(name):
3321 if not _validname(name):
3322 raise util.Abort(
3322 raise util.Abort(
3323 _('invalid queue name, may not contain the characters ":\\/."'))
3323 _('invalid queue name, may not contain the characters ":\\/."'))
3324
3324
3325 existing = _getqueues()
3325 existing = _getqueues()
3326
3326
3327 if opts.get('create'):
3327 if opts.get('create'):
3328 if name in existing:
3328 if name in existing:
3329 raise util.Abort(_('queue "%s" already exists') % name)
3329 raise util.Abort(_('queue "%s" already exists') % name)
3330 if _noqueues():
3330 if _noqueues():
3331 _addqueue(_defaultqueue)
3331 _addqueue(_defaultqueue)
3332 _addqueue(name)
3332 _addqueue(name)
3333 _setactive(name)
3333 _setactive(name)
3334 elif opts.get('rename'):
3334 elif opts.get('rename'):
3335 current = _getcurrent()
3335 current = _getcurrent()
3336 if name == current:
3336 if name == current:
3337 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3337 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3338 if name in existing:
3338 if name in existing:
3339 raise util.Abort(_('queue "%s" already exists') % name)
3339 raise util.Abort(_('queue "%s" already exists') % name)
3340
3340
3341 olddir = _queuedir(current)
3341 olddir = _queuedir(current)
3342 newdir = _queuedir(name)
3342 newdir = _queuedir(name)
3343
3343
3344 if os.path.exists(newdir):
3344 if os.path.exists(newdir):
3345 raise util.Abort(_('non-queue directory "%s" already exists') %
3345 raise util.Abort(_('non-queue directory "%s" already exists') %
3346 newdir)
3346 newdir)
3347
3347
3348 fh = repo.opener('patches.queues.new', 'w')
3348 fh = repo.opener('patches.queues.new', 'w')
3349 for queue in existing:
3349 for queue in existing:
3350 if queue == current:
3350 if queue == current:
3351 fh.write('%s\n' % (name,))
3351 fh.write('%s\n' % (name,))
3352 if os.path.exists(olddir):
3352 if os.path.exists(olddir):
3353 util.rename(olddir, newdir)
3353 util.rename(olddir, newdir)
3354 else:
3354 else:
3355 fh.write('%s\n' % (queue,))
3355 fh.write('%s\n' % (queue,))
3356 fh.close()
3356 fh.close()
3357 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3357 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3358 _setactivenocheck(name)
3358 _setactivenocheck(name)
3359 elif opts.get('delete'):
3359 elif opts.get('delete'):
3360 _delete(name)
3360 _delete(name)
3361 elif opts.get('purge'):
3361 elif opts.get('purge'):
3362 if name in existing:
3362 if name in existing:
3363 _delete(name)
3363 _delete(name)
3364 qdir = _queuedir(name)
3364 qdir = _queuedir(name)
3365 if os.path.exists(qdir):
3365 if os.path.exists(qdir):
3366 shutil.rmtree(qdir)
3366 shutil.rmtree(qdir)
3367 else:
3367 else:
3368 if name not in existing:
3368 if name not in existing:
3369 raise util.Abort(_('use --create to create a new queue'))
3369 raise util.Abort(_('use --create to create a new queue'))
3370 _setactive(name)
3370 _setactive(name)
3371
3371
3372 def mqphasedefaults(repo, roots):
3372 def mqphasedefaults(repo, roots):
3373 """callback used to set mq changeset as secret when no phase data exists"""
3373 """callback used to set mq changeset as secret when no phase data exists"""
3374 if repo.mq.applied:
3374 if repo.mq.applied:
3375 if repo.ui.configbool('mq', 'secret', False):
3375 if repo.ui.configbool('mq', 'secret', False):
3376 mqphase = phases.secret
3376 mqphase = phases.secret
3377 else:
3377 else:
3378 mqphase = phases.draft
3378 mqphase = phases.draft
3379 qbase = repo[repo.mq.applied[0].node]
3379 qbase = repo[repo.mq.applied[0].node]
3380 roots[mqphase].add(qbase.node())
3380 roots[mqphase].add(qbase.node())
3381 return roots
3381 return roots
3382
3382
3383 def reposetup(ui, repo):
3383 def reposetup(ui, repo):
3384 class mqrepo(repo.__class__):
3384 class mqrepo(repo.__class__):
3385 @util.propertycache
3385 @util.propertycache
3386 def mq(self):
3386 def mq(self):
3387 return queue(self.ui, self.path)
3387 return queue(self.ui, self.path)
3388
3388
3389 def abortifwdirpatched(self, errmsg, force=False):
3389 def abortifwdirpatched(self, errmsg, force=False):
3390 if self.mq.applied and not force:
3390 if self.mq.applied and not force:
3391 parents = self.dirstate.parents()
3391 parents = self.dirstate.parents()
3392 patches = [s.node for s in self.mq.applied]
3392 patches = [s.node for s in self.mq.applied]
3393 if parents[0] in patches or parents[1] in patches:
3393 if parents[0] in patches or parents[1] in patches:
3394 raise util.Abort(errmsg)
3394 raise util.Abort(errmsg)
3395
3395
3396 def commit(self, text="", user=None, date=None, match=None,
3396 def commit(self, text="", user=None, date=None, match=None,
3397 force=False, editor=False, extra={}):
3397 force=False, editor=False, extra={}):
3398 self.abortifwdirpatched(
3398 self.abortifwdirpatched(
3399 _('cannot commit over an applied mq patch'),
3399 _('cannot commit over an applied mq patch'),
3400 force)
3400 force)
3401
3401
3402 return super(mqrepo, self).commit(text, user, date, match, force,
3402 return super(mqrepo, self).commit(text, user, date, match, force,
3403 editor, extra)
3403 editor, extra)
3404
3404
3405 def checkpush(self, force, revs):
3405 def checkpush(self, force, revs):
3406 if self.mq.applied and not force:
3406 if self.mq.applied and not force:
3407 outapplied = [e.node for e in self.mq.applied]
3407 outapplied = [e.node for e in self.mq.applied]
3408 if revs:
3408 if revs:
3409 # Assume applied patches have no non-patch descendants and
3409 # Assume applied patches have no non-patch descendants and
3410 # are not on remote already. Filtering any changeset not
3410 # are not on remote already. Filtering any changeset not
3411 # pushed.
3411 # pushed.
3412 heads = set(revs)
3412 heads = set(revs)
3413 for node in reversed(outapplied):
3413 for node in reversed(outapplied):
3414 if node in heads:
3414 if node in heads:
3415 break
3415 break
3416 else:
3416 else:
3417 outapplied.pop()
3417 outapplied.pop()
3418 # looking for pushed and shared changeset
3418 # looking for pushed and shared changeset
3419 for node in outapplied:
3419 for node in outapplied:
3420 if repo[node].phase() < phases.secret:
3420 if repo[node].phase() < phases.secret:
3421 raise util.Abort(_('source has mq patches applied'))
3421 raise util.Abort(_('source has mq patches applied'))
3422 # no non-secret patches pushed
3422 # no non-secret patches pushed
3423 super(mqrepo, self).checkpush(force, revs)
3423 super(mqrepo, self).checkpush(force, revs)
3424
3424
3425 def _findtags(self):
3425 def _findtags(self):
3426 '''augment tags from base class with patch tags'''
3426 '''augment tags from base class with patch tags'''
3427 result = super(mqrepo, self)._findtags()
3427 result = super(mqrepo, self)._findtags()
3428
3428
3429 q = self.mq
3429 q = self.mq
3430 if not q.applied:
3430 if not q.applied:
3431 return result
3431 return result
3432
3432
3433 mqtags = [(patch.node, patch.name) for patch in q.applied]
3433 mqtags = [(patch.node, patch.name) for patch in q.applied]
3434
3434
3435 try:
3435 try:
3436 self.changelog.rev(mqtags[-1][0])
3436 self.changelog.rev(mqtags[-1][0])
3437 except error.LookupError:
3437 except error.LookupError:
3438 self.ui.warn(_('mq status file refers to unknown node %s\n')
3438 self.ui.warn(_('mq status file refers to unknown node %s\n')
3439 % short(mqtags[-1][0]))
3439 % short(mqtags[-1][0]))
3440 return result
3440 return result
3441
3441
3442 mqtags.append((mqtags[-1][0], 'qtip'))
3442 mqtags.append((mqtags[-1][0], 'qtip'))
3443 mqtags.append((mqtags[0][0], 'qbase'))
3443 mqtags.append((mqtags[0][0], 'qbase'))
3444 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3444 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3445 tags = result[0]
3445 tags = result[0]
3446 for patch in mqtags:
3446 for patch in mqtags:
3447 if patch[1] in tags:
3447 if patch[1] in tags:
3448 self.ui.warn(_('tag %s overrides mq patch of the same '
3448 self.ui.warn(_('tag %s overrides mq patch of the same '
3449 'name\n') % patch[1])
3449 'name\n') % patch[1])
3450 else:
3450 else:
3451 tags[patch[1]] = patch[0]
3451 tags[patch[1]] = patch[0]
3452
3452
3453 return result
3453 return result
3454
3454
3455 def _branchtags(self, partial, lrev):
3455 def _branchtags(self, partial, lrev):
3456 q = self.mq
3456 q = self.mq
3457 cl = self.changelog
3457 cl = self.changelog
3458 qbase = None
3458 qbase = None
3459 if not q.applied:
3459 if not q.applied:
3460 if getattr(self, '_committingpatch', False):
3460 if getattr(self, '_committingpatch', False):
3461 # Committing a new patch, must be tip
3461 # Committing a new patch, must be tip
3462 qbase = len(cl) - 1
3462 qbase = len(cl) - 1
3463 else:
3463 else:
3464 qbasenode = q.applied[0].node
3464 qbasenode = q.applied[0].node
3465 try:
3465 try:
3466 qbase = cl.rev(qbasenode)
3466 qbase = cl.rev(qbasenode)
3467 except error.LookupError:
3467 except error.LookupError:
3468 self.ui.warn(_('mq status file refers to unknown node %s\n')
3468 self.ui.warn(_('mq status file refers to unknown node %s\n')
3469 % short(qbasenode))
3469 % short(qbasenode))
3470 if qbase is None:
3470 if qbase is None:
3471 return super(mqrepo, self)._branchtags(partial, lrev)
3471 return super(mqrepo, self)._branchtags(partial, lrev)
3472
3472
3473 start = lrev + 1
3473 start = lrev + 1
3474 if start < qbase:
3474 if start < qbase:
3475 # update the cache (excluding the patches) and save it
3475 # update the cache (excluding the patches) and save it
3476 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3476 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3477 self._updatebranchcache(partial, ctxgen)
3477 self._updatebranchcache(partial, ctxgen)
3478 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3478 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3479 start = qbase
3479 start = qbase
3480 # if start = qbase, the cache is as updated as it should be.
3480 # if start = qbase, the cache is as updated as it should be.
3481 # if start > qbase, the cache includes (part of) the patches.
3481 # if start > qbase, the cache includes (part of) the patches.
3482 # we might as well use it, but we won't save it.
3482 # we might as well use it, but we won't save it.
3483
3483
3484 # update the cache up to the tip
3484 # update the cache up to the tip
3485 ctxgen = (self[r] for r in xrange(start, len(cl)))
3485 ctxgen = (self[r] for r in xrange(start, len(cl)))
3486 self._updatebranchcache(partial, ctxgen)
3486 self._updatebranchcache(partial, ctxgen)
3487
3487
3488 return partial
3488 return partial
3489
3489
3490 if repo.local():
3490 if repo.local():
3491 repo.__class__ = mqrepo
3491 repo.__class__ = mqrepo
3492
3492
3493 repo._phasedefaults.append(mqphasedefaults)
3493 repo._phasedefaults.append(mqphasedefaults)
3494
3494
3495 def mqimport(orig, ui, repo, *args, **kwargs):
3495 def mqimport(orig, ui, repo, *args, **kwargs):
3496 if (util.safehasattr(repo, 'abortifwdirpatched')
3496 if (util.safehasattr(repo, 'abortifwdirpatched')
3497 and not kwargs.get('no_commit', False)):
3497 and not kwargs.get('no_commit', False)):
3498 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3498 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3499 kwargs.get('force'))
3499 kwargs.get('force'))
3500 return orig(ui, repo, *args, **kwargs)
3500 return orig(ui, repo, *args, **kwargs)
3501
3501
3502 def mqinit(orig, ui, *args, **kwargs):
3502 def mqinit(orig, ui, *args, **kwargs):
3503 mq = kwargs.pop('mq', None)
3503 mq = kwargs.pop('mq', None)
3504
3504
3505 if not mq:
3505 if not mq:
3506 return orig(ui, *args, **kwargs)
3506 return orig(ui, *args, **kwargs)
3507
3507
3508 if args:
3508 if args:
3509 repopath = args[0]
3509 repopath = args[0]
3510 if not hg.islocal(repopath):
3510 if not hg.islocal(repopath):
3511 raise util.Abort(_('only a local queue repository '
3511 raise util.Abort(_('only a local queue repository '
3512 'may be initialized'))
3512 'may be initialized'))
3513 else:
3513 else:
3514 repopath = cmdutil.findrepo(os.getcwd())
3514 repopath = cmdutil.findrepo(os.getcwd())
3515 if not repopath:
3515 if not repopath:
3516 raise util.Abort(_('there is no Mercurial repository here '
3516 raise util.Abort(_('there is no Mercurial repository here '
3517 '(.hg not found)'))
3517 '(.hg not found)'))
3518 repo = hg.repository(ui, repopath)
3518 repo = hg.repository(ui, repopath)
3519 return qinit(ui, repo, True)
3519 return qinit(ui, repo, True)
3520
3520
3521 def mqcommand(orig, ui, repo, *args, **kwargs):
3521 def mqcommand(orig, ui, repo, *args, **kwargs):
3522 """Add --mq option to operate on patch repository instead of main"""
3522 """Add --mq option to operate on patch repository instead of main"""
3523
3523
3524 # some commands do not like getting unknown options
3524 # some commands do not like getting unknown options
3525 mq = kwargs.pop('mq', None)
3525 mq = kwargs.pop('mq', None)
3526
3526
3527 if not mq:
3527 if not mq:
3528 return orig(ui, repo, *args, **kwargs)
3528 return orig(ui, repo, *args, **kwargs)
3529
3529
3530 q = repo.mq
3530 q = repo.mq
3531 r = q.qrepo()
3531 r = q.qrepo()
3532 if not r:
3532 if not r:
3533 raise util.Abort(_('no queue repository'))
3533 raise util.Abort(_('no queue repository'))
3534 return orig(r.ui, r, *args, **kwargs)
3534 return orig(r.ui, r, *args, **kwargs)
3535
3535
3536 def summary(orig, ui, repo, *args, **kwargs):
3536 def summary(orig, ui, repo, *args, **kwargs):
3537 r = orig(ui, repo, *args, **kwargs)
3537 r = orig(ui, repo, *args, **kwargs)
3538 q = repo.mq
3538 q = repo.mq
3539 m = []
3539 m = []
3540 a, u = len(q.applied), len(q.unapplied(repo))
3540 a, u = len(q.applied), len(q.unapplied(repo))
3541 if a:
3541 if a:
3542 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3542 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3543 if u:
3543 if u:
3544 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3544 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3545 if m:
3545 if m:
3546 ui.write("mq: %s\n" % ', '.join(m))
3546 ui.write("mq: %s\n" % ', '.join(m))
3547 else:
3547 else:
3548 ui.note(_("mq: (empty queue)\n"))
3548 ui.note(_("mq: (empty queue)\n"))
3549 return r
3549 return r
3550
3550
3551 def revsetmq(repo, subset, x):
3551 def revsetmq(repo, subset, x):
3552 """``mq()``
3552 """``mq()``
3553 Changesets managed by MQ.
3553 Changesets managed by MQ.
3554 """
3554 """
3555 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3555 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3556 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3556 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3557 return [r for r in subset if r in applied]
3557 return [r for r in subset if r in applied]
3558
3558
3559 # tell hggettext to extract docstrings from these functions:
3559 # tell hggettext to extract docstrings from these functions:
3560 i18nfunctions = [revsetmq]
3560 i18nfunctions = [revsetmq]
3561
3561
3562 def extsetup(ui):
3562 def extsetup(ui):
3563 # Ensure mq wrappers are called first, regardless of extension load order by
3563 # Ensure mq wrappers are called first, regardless of extension load order by
3564 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3564 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3565 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3565 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3566
3566
3567 extensions.wrapcommand(commands.table, 'import', mqimport)
3567 extensions.wrapcommand(commands.table, 'import', mqimport)
3568 extensions.wrapcommand(commands.table, 'summary', summary)
3568 extensions.wrapcommand(commands.table, 'summary', summary)
3569
3569
3570 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3570 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3571 entry[1].extend(mqopt)
3571 entry[1].extend(mqopt)
3572
3572
3573 nowrap = set(commands.norepo.split(" "))
3573 nowrap = set(commands.norepo.split(" "))
3574
3574
3575 def dotable(cmdtable):
3575 def dotable(cmdtable):
3576 for cmd in cmdtable.keys():
3576 for cmd in cmdtable.keys():
3577 cmd = cmdutil.parsealiases(cmd)[0]
3577 cmd = cmdutil.parsealiases(cmd)[0]
3578 if cmd in nowrap:
3578 if cmd in nowrap:
3579 continue
3579 continue
3580 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3580 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3581 entry[1].extend(mqopt)
3581 entry[1].extend(mqopt)
3582
3582
3583 dotable(commands.table)
3583 dotable(commands.table)
3584
3584
3585 for extname, extmodule in extensions.extensions():
3585 for extname, extmodule in extensions.extensions():
3586 if extmodule.__file__ != __file__:
3586 if extmodule.__file__ != __file__:
3587 dotable(getattr(extmodule, 'cmdtable', {}))
3587 dotable(getattr(extmodule, 'cmdtable', {}))
3588
3588
3589 revset.symbols['mq'] = revsetmq
3589 revset.symbols['mq'] = revsetmq
3590
3590
3591 colortable = {'qguard.negative': 'red',
3591 colortable = {'qguard.negative': 'red',
3592 'qguard.positive': 'yellow',
3592 'qguard.positive': 'yellow',
3593 'qguard.unguarded': 'green',
3593 'qguard.unguarded': 'green',
3594 'qseries.applied': 'blue bold underline',
3594 'qseries.applied': 'blue bold underline',
3595 'qseries.guarded': 'black bold',
3595 'qseries.guarded': 'black bold',
3596 'qseries.missing': 'red bold',
3596 'qseries.missing': 'red bold',
3597 'qseries.unapplied': 'black bold'}
3597 'qseries.unapplied': 'black bold'}
@@ -1,666 +1,666 b''
1 # record.py
1 # record.py
2 #
2 #
3 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
3 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''commands to interactively select changes for commit/qrefresh'''
8 '''commands to interactively select changes for commit/qrefresh'''
9
9
10 from mercurial.i18n import gettext, _
10 from mercurial.i18n import gettext, _
11 from mercurial import cmdutil, commands, extensions, hg, mdiff, patch
11 from mercurial import cmdutil, commands, extensions, hg, mdiff, patch
12 from mercurial import util
12 from mercurial import util
13 import copy, cStringIO, errno, os, re, shutil, tempfile
13 import copy, cStringIO, errno, os, re, shutil, tempfile
14
14
15 cmdtable = {}
15 cmdtable = {}
16 command = cmdutil.command(cmdtable)
16 command = cmdutil.command(cmdtable)
17 testedwith = 'internal'
17 testedwith = 'internal'
18
18
19 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
19 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
20
20
21 diffopts = [
21 diffopts = [
22 ('w', 'ignore-all-space', False,
22 ('w', 'ignore-all-space', False,
23 _('ignore white space when comparing lines')),
23 _('ignore white space when comparing lines')),
24 ('b', 'ignore-space-change', None,
24 ('b', 'ignore-space-change', None,
25 _('ignore changes in the amount of white space')),
25 _('ignore changes in the amount of white space')),
26 ('B', 'ignore-blank-lines', None,
26 ('B', 'ignore-blank-lines', None,
27 _('ignore changes whose lines are all blank')),
27 _('ignore changes whose lines are all blank')),
28 ]
28 ]
29
29
30 def scanpatch(fp):
30 def scanpatch(fp):
31 """like patch.iterhunks, but yield different events
31 """like patch.iterhunks, but yield different events
32
32
33 - ('file', [header_lines + fromfile + tofile])
33 - ('file', [header_lines + fromfile + tofile])
34 - ('context', [context_lines])
34 - ('context', [context_lines])
35 - ('hunk', [hunk_lines])
35 - ('hunk', [hunk_lines])
36 - ('range', (-start,len, +start,len, diffp))
36 - ('range', (-start,len, +start,len, proc))
37 """
37 """
38 lr = patch.linereader(fp)
38 lr = patch.linereader(fp)
39
39
40 def scanwhile(first, p):
40 def scanwhile(first, p):
41 """scan lr while predicate holds"""
41 """scan lr while predicate holds"""
42 lines = [first]
42 lines = [first]
43 while True:
43 while True:
44 line = lr.readline()
44 line = lr.readline()
45 if not line:
45 if not line:
46 break
46 break
47 if p(line):
47 if p(line):
48 lines.append(line)
48 lines.append(line)
49 else:
49 else:
50 lr.push(line)
50 lr.push(line)
51 break
51 break
52 return lines
52 return lines
53
53
54 while True:
54 while True:
55 line = lr.readline()
55 line = lr.readline()
56 if not line:
56 if not line:
57 break
57 break
58 if line.startswith('diff --git a/') or line.startswith('diff -r '):
58 if line.startswith('diff --git a/') or line.startswith('diff -r '):
59 def notheader(line):
59 def notheader(line):
60 s = line.split(None, 1)
60 s = line.split(None, 1)
61 return not s or s[0] not in ('---', 'diff')
61 return not s or s[0] not in ('---', 'diff')
62 header = scanwhile(line, notheader)
62 header = scanwhile(line, notheader)
63 fromfile = lr.readline()
63 fromfile = lr.readline()
64 if fromfile.startswith('---'):
64 if fromfile.startswith('---'):
65 tofile = lr.readline()
65 tofile = lr.readline()
66 header += [fromfile, tofile]
66 header += [fromfile, tofile]
67 else:
67 else:
68 lr.push(fromfile)
68 lr.push(fromfile)
69 yield 'file', header
69 yield 'file', header
70 elif line[0] == ' ':
70 elif line[0] == ' ':
71 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
71 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
72 elif line[0] in '-+':
72 elif line[0] in '-+':
73 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
73 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
74 else:
74 else:
75 m = lines_re.match(line)
75 m = lines_re.match(line)
76 if m:
76 if m:
77 yield 'range', m.groups()
77 yield 'range', m.groups()
78 else:
78 else:
79 raise patch.PatchError('unknown patch content: %r' % line)
79 raise patch.PatchError('unknown patch content: %r' % line)
80
80
81 class header(object):
81 class header(object):
82 """patch header
82 """patch header
83
83
84 XXX shouldn't we move this to mercurial/patch.py ?
84 XXX shouldn't we move this to mercurial/patch.py ?
85 """
85 """
86 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
86 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
87 diff_re = re.compile('diff -r .* (.*)$')
87 diff_re = re.compile('diff -r .* (.*)$')
88 allhunks_re = re.compile('(?:index|new file|deleted file) ')
88 allhunks_re = re.compile('(?:index|new file|deleted file) ')
89 pretty_re = re.compile('(?:new file|deleted file) ')
89 pretty_re = re.compile('(?:new file|deleted file) ')
90 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
90 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
91
91
92 def __init__(self, header):
92 def __init__(self, header):
93 self.header = header
93 self.header = header
94 self.hunks = []
94 self.hunks = []
95
95
96 def binary(self):
96 def binary(self):
97 return util.any(h.startswith('index ') for h in self.header)
97 return util.any(h.startswith('index ') for h in self.header)
98
98
99 def pretty(self, fp):
99 def pretty(self, fp):
100 for h in self.header:
100 for h in self.header:
101 if h.startswith('index '):
101 if h.startswith('index '):
102 fp.write(_('this modifies a binary file (all or nothing)\n'))
102 fp.write(_('this modifies a binary file (all or nothing)\n'))
103 break
103 break
104 if self.pretty_re.match(h):
104 if self.pretty_re.match(h):
105 fp.write(h)
105 fp.write(h)
106 if self.binary():
106 if self.binary():
107 fp.write(_('this is a binary file\n'))
107 fp.write(_('this is a binary file\n'))
108 break
108 break
109 if h.startswith('---'):
109 if h.startswith('---'):
110 fp.write(_('%d hunks, %d lines changed\n') %
110 fp.write(_('%d hunks, %d lines changed\n') %
111 (len(self.hunks),
111 (len(self.hunks),
112 sum([max(h.added, h.removed) for h in self.hunks])))
112 sum([max(h.added, h.removed) for h in self.hunks])))
113 break
113 break
114 fp.write(h)
114 fp.write(h)
115
115
116 def write(self, fp):
116 def write(self, fp):
117 fp.write(''.join(self.header))
117 fp.write(''.join(self.header))
118
118
119 def allhunks(self):
119 def allhunks(self):
120 return util.any(self.allhunks_re.match(h) for h in self.header)
120 return util.any(self.allhunks_re.match(h) for h in self.header)
121
121
122 def files(self):
122 def files(self):
123 match = self.diffgit_re.match(self.header[0])
123 match = self.diffgit_re.match(self.header[0])
124 if match:
124 if match:
125 fromfile, tofile = match.groups()
125 fromfile, tofile = match.groups()
126 if fromfile == tofile:
126 if fromfile == tofile:
127 return [fromfile]
127 return [fromfile]
128 return [fromfile, tofile]
128 return [fromfile, tofile]
129 else:
129 else:
130 return self.diff_re.match(self.header[0]).groups()
130 return self.diff_re.match(self.header[0]).groups()
131
131
132 def filename(self):
132 def filename(self):
133 return self.files()[-1]
133 return self.files()[-1]
134
134
135 def __repr__(self):
135 def __repr__(self):
136 return '<header %s>' % (' '.join(map(repr, self.files())))
136 return '<header %s>' % (' '.join(map(repr, self.files())))
137
137
138 def special(self):
138 def special(self):
139 return util.any(self.special_re.match(h) for h in self.header)
139 return util.any(self.special_re.match(h) for h in self.header)
140
140
141 def countchanges(hunk):
141 def countchanges(hunk):
142 """hunk -> (n+,n-)"""
142 """hunk -> (n+,n-)"""
143 add = len([h for h in hunk if h[0] == '+'])
143 add = len([h for h in hunk if h[0] == '+'])
144 rem = len([h for h in hunk if h[0] == '-'])
144 rem = len([h for h in hunk if h[0] == '-'])
145 return add, rem
145 return add, rem
146
146
147 class hunk(object):
147 class hunk(object):
148 """patch hunk
148 """patch hunk
149
149
150 XXX shouldn't we merge this with patch.hunk ?
150 XXX shouldn't we merge this with patch.hunk ?
151 """
151 """
152 maxcontext = 3
152 maxcontext = 3
153
153
154 def __init__(self, header, fromline, toline, proc, before, hunk, after):
154 def __init__(self, header, fromline, toline, proc, before, hunk, after):
155 def trimcontext(number, lines):
155 def trimcontext(number, lines):
156 delta = len(lines) - self.maxcontext
156 delta = len(lines) - self.maxcontext
157 if False and delta > 0:
157 if False and delta > 0:
158 return number + delta, lines[:self.maxcontext]
158 return number + delta, lines[:self.maxcontext]
159 return number, lines
159 return number, lines
160
160
161 self.header = header
161 self.header = header
162 self.fromline, self.before = trimcontext(fromline, before)
162 self.fromline, self.before = trimcontext(fromline, before)
163 self.toline, self.after = trimcontext(toline, after)
163 self.toline, self.after = trimcontext(toline, after)
164 self.proc = proc
164 self.proc = proc
165 self.hunk = hunk
165 self.hunk = hunk
166 self.added, self.removed = countchanges(self.hunk)
166 self.added, self.removed = countchanges(self.hunk)
167
167
168 def write(self, fp):
168 def write(self, fp):
169 delta = len(self.before) + len(self.after)
169 delta = len(self.before) + len(self.after)
170 if self.after and self.after[-1] == '\\ No newline at end of file\n':
170 if self.after and self.after[-1] == '\\ No newline at end of file\n':
171 delta -= 1
171 delta -= 1
172 fromlen = delta + self.removed
172 fromlen = delta + self.removed
173 tolen = delta + self.added
173 tolen = delta + self.added
174 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
174 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
175 (self.fromline, fromlen, self.toline, tolen,
175 (self.fromline, fromlen, self.toline, tolen,
176 self.proc and (' ' + self.proc)))
176 self.proc and (' ' + self.proc)))
177 fp.write(''.join(self.before + self.hunk + self.after))
177 fp.write(''.join(self.before + self.hunk + self.after))
178
178
179 pretty = write
179 pretty = write
180
180
181 def filename(self):
181 def filename(self):
182 return self.header.filename()
182 return self.header.filename()
183
183
184 def __repr__(self):
184 def __repr__(self):
185 return '<hunk %r@%d>' % (self.filename(), self.fromline)
185 return '<hunk %r@%d>' % (self.filename(), self.fromline)
186
186
187 def parsepatch(fp):
187 def parsepatch(fp):
188 """patch -> [] of headers -> [] of hunks """
188 """patch -> [] of headers -> [] of hunks """
189 class parser(object):
189 class parser(object):
190 """patch parsing state machine"""
190 """patch parsing state machine"""
191 def __init__(self):
191 def __init__(self):
192 self.fromline = 0
192 self.fromline = 0
193 self.toline = 0
193 self.toline = 0
194 self.proc = ''
194 self.proc = ''
195 self.header = None
195 self.header = None
196 self.context = []
196 self.context = []
197 self.before = []
197 self.before = []
198 self.hunk = []
198 self.hunk = []
199 self.headers = []
199 self.headers = []
200
200
201 def addrange(self, limits):
201 def addrange(self, limits):
202 fromstart, fromend, tostart, toend, proc = limits
202 fromstart, fromend, tostart, toend, proc = limits
203 self.fromline = int(fromstart)
203 self.fromline = int(fromstart)
204 self.toline = int(tostart)
204 self.toline = int(tostart)
205 self.proc = proc
205 self.proc = proc
206
206
207 def addcontext(self, context):
207 def addcontext(self, context):
208 if self.hunk:
208 if self.hunk:
209 h = hunk(self.header, self.fromline, self.toline, self.proc,
209 h = hunk(self.header, self.fromline, self.toline, self.proc,
210 self.before, self.hunk, context)
210 self.before, self.hunk, context)
211 self.header.hunks.append(h)
211 self.header.hunks.append(h)
212 self.fromline += len(self.before) + h.removed
212 self.fromline += len(self.before) + h.removed
213 self.toline += len(self.before) + h.added
213 self.toline += len(self.before) + h.added
214 self.before = []
214 self.before = []
215 self.hunk = []
215 self.hunk = []
216 self.proc = ''
216 self.proc = ''
217 self.context = context
217 self.context = context
218
218
219 def addhunk(self, hunk):
219 def addhunk(self, hunk):
220 if self.context:
220 if self.context:
221 self.before = self.context
221 self.before = self.context
222 self.context = []
222 self.context = []
223 self.hunk = hunk
223 self.hunk = hunk
224
224
225 def newfile(self, hdr):
225 def newfile(self, hdr):
226 self.addcontext([])
226 self.addcontext([])
227 h = header(hdr)
227 h = header(hdr)
228 self.headers.append(h)
228 self.headers.append(h)
229 self.header = h
229 self.header = h
230
230
231 def finished(self):
231 def finished(self):
232 self.addcontext([])
232 self.addcontext([])
233 return self.headers
233 return self.headers
234
234
235 transitions = {
235 transitions = {
236 'file': {'context': addcontext,
236 'file': {'context': addcontext,
237 'file': newfile,
237 'file': newfile,
238 'hunk': addhunk,
238 'hunk': addhunk,
239 'range': addrange},
239 'range': addrange},
240 'context': {'file': newfile,
240 'context': {'file': newfile,
241 'hunk': addhunk,
241 'hunk': addhunk,
242 'range': addrange},
242 'range': addrange},
243 'hunk': {'context': addcontext,
243 'hunk': {'context': addcontext,
244 'file': newfile,
244 'file': newfile,
245 'range': addrange},
245 'range': addrange},
246 'range': {'context': addcontext,
246 'range': {'context': addcontext,
247 'hunk': addhunk},
247 'hunk': addhunk},
248 }
248 }
249
249
250 p = parser()
250 p = parser()
251
251
252 state = 'context'
252 state = 'context'
253 for newstate, data in scanpatch(fp):
253 for newstate, data in scanpatch(fp):
254 try:
254 try:
255 p.transitions[state][newstate](p, data)
255 p.transitions[state][newstate](p, data)
256 except KeyError:
256 except KeyError:
257 raise patch.PatchError('unhandled transition: %s -> %s' %
257 raise patch.PatchError('unhandled transition: %s -> %s' %
258 (state, newstate))
258 (state, newstate))
259 state = newstate
259 state = newstate
260 return p.finished()
260 return p.finished()
261
261
262 def filterpatch(ui, headers):
262 def filterpatch(ui, headers):
263 """Interactively filter patch chunks into applied-only chunks"""
263 """Interactively filter patch chunks into applied-only chunks"""
264
264
265 def prompt(skipfile, skipall, query, chunk):
265 def prompt(skipfile, skipall, query, chunk):
266 """prompt query, and process base inputs
266 """prompt query, and process base inputs
267
267
268 - y/n for the rest of file
268 - y/n for the rest of file
269 - y/n for the rest
269 - y/n for the rest
270 - ? (help)
270 - ? (help)
271 - q (quit)
271 - q (quit)
272
272
273 Return True/False and possibly updated skipfile and skipall.
273 Return True/False and possibly updated skipfile and skipall.
274 """
274 """
275 newpatches = None
275 newpatches = None
276 if skipall is not None:
276 if skipall is not None:
277 return skipall, skipfile, skipall, newpatches
277 return skipall, skipfile, skipall, newpatches
278 if skipfile is not None:
278 if skipfile is not None:
279 return skipfile, skipfile, skipall, newpatches
279 return skipfile, skipfile, skipall, newpatches
280 while True:
280 while True:
281 resps = _('[Ynesfdaq?]')
281 resps = _('[Ynesfdaq?]')
282 choices = (_('&Yes, record this change'),
282 choices = (_('&Yes, record this change'),
283 _('&No, skip this change'),
283 _('&No, skip this change'),
284 _('&Edit the change manually'),
284 _('&Edit the change manually'),
285 _('&Skip remaining changes to this file'),
285 _('&Skip remaining changes to this file'),
286 _('Record remaining changes to this &file'),
286 _('Record remaining changes to this &file'),
287 _('&Done, skip remaining changes and files'),
287 _('&Done, skip remaining changes and files'),
288 _('Record &all changes to all remaining files'),
288 _('Record &all changes to all remaining files'),
289 _('&Quit, recording no changes'),
289 _('&Quit, recording no changes'),
290 _('&?'))
290 _('&?'))
291 r = ui.promptchoice("%s %s" % (query, resps), choices)
291 r = ui.promptchoice("%s %s" % (query, resps), choices)
292 ui.write("\n")
292 ui.write("\n")
293 if r == 8: # ?
293 if r == 8: # ?
294 doc = gettext(record.__doc__)
294 doc = gettext(record.__doc__)
295 c = doc.find('::') + 2
295 c = doc.find('::') + 2
296 for l in doc[c:].splitlines():
296 for l in doc[c:].splitlines():
297 if l.startswith(' '):
297 if l.startswith(' '):
298 ui.write(l.strip(), '\n')
298 ui.write(l.strip(), '\n')
299 continue
299 continue
300 elif r == 0: # yes
300 elif r == 0: # yes
301 ret = True
301 ret = True
302 elif r == 1: # no
302 elif r == 1: # no
303 ret = False
303 ret = False
304 elif r == 2: # Edit patch
304 elif r == 2: # Edit patch
305 if chunk is None:
305 if chunk is None:
306 ui.write(_('cannot edit patch for whole file'))
306 ui.write(_('cannot edit patch for whole file'))
307 ui.write("\n")
307 ui.write("\n")
308 continue
308 continue
309 if chunk.header.binary():
309 if chunk.header.binary():
310 ui.write(_('cannot edit patch for binary file'))
310 ui.write(_('cannot edit patch for binary file'))
311 ui.write("\n")
311 ui.write("\n")
312 continue
312 continue
313 # Patch comment based on the Git one (based on comment at end of
313 # Patch comment based on the Git one (based on comment at end of
314 # http://mercurial.selenic.com/wiki/RecordExtension)
314 # http://mercurial.selenic.com/wiki/RecordExtension)
315 phelp = '---' + _("""
315 phelp = '---' + _("""
316 To remove '-' lines, make them ' ' lines (context).
316 To remove '-' lines, make them ' ' lines (context).
317 To remove '+' lines, delete them.
317 To remove '+' lines, delete them.
318 Lines starting with # will be removed from the patch.
318 Lines starting with # will be removed from the patch.
319
319
320 If the patch applies cleanly, the edited hunk will immediately be
320 If the patch applies cleanly, the edited hunk will immediately be
321 added to the record list. If it does not apply cleanly, a rejects
321 added to the record list. If it does not apply cleanly, a rejects
322 file will be generated: you can use that when you try again. If
322 file will be generated: you can use that when you try again. If
323 all lines of the hunk are removed, then the edit is aborted and
323 all lines of the hunk are removed, then the edit is aborted and
324 the hunk is left unchanged.
324 the hunk is left unchanged.
325 """)
325 """)
326 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
326 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
327 suffix=".diff", text=True)
327 suffix=".diff", text=True)
328 ncpatchfp = None
328 ncpatchfp = None
329 try:
329 try:
330 # Write the initial patch
330 # Write the initial patch
331 f = os.fdopen(patchfd, "w")
331 f = os.fdopen(patchfd, "w")
332 chunk.header.write(f)
332 chunk.header.write(f)
333 chunk.write(f)
333 chunk.write(f)
334 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
334 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
335 f.close()
335 f.close()
336 # Start the editor and wait for it to complete
336 # Start the editor and wait for it to complete
337 editor = ui.geteditor()
337 editor = ui.geteditor()
338 util.system("%s \"%s\"" % (editor, patchfn),
338 util.system("%s \"%s\"" % (editor, patchfn),
339 environ={'HGUSER': ui.username()},
339 environ={'HGUSER': ui.username()},
340 onerr=util.Abort, errprefix=_("edit failed"),
340 onerr=util.Abort, errprefix=_("edit failed"),
341 out=ui.fout)
341 out=ui.fout)
342 # Remove comment lines
342 # Remove comment lines
343 patchfp = open(patchfn)
343 patchfp = open(patchfn)
344 ncpatchfp = cStringIO.StringIO()
344 ncpatchfp = cStringIO.StringIO()
345 for line in patchfp:
345 for line in patchfp:
346 if not line.startswith('#'):
346 if not line.startswith('#'):
347 ncpatchfp.write(line)
347 ncpatchfp.write(line)
348 patchfp.close()
348 patchfp.close()
349 ncpatchfp.seek(0)
349 ncpatchfp.seek(0)
350 newpatches = parsepatch(ncpatchfp)
350 newpatches = parsepatch(ncpatchfp)
351 finally:
351 finally:
352 os.unlink(patchfn)
352 os.unlink(patchfn)
353 del ncpatchfp
353 del ncpatchfp
354 # Signal that the chunk shouldn't be applied as-is, but
354 # Signal that the chunk shouldn't be applied as-is, but
355 # provide the new patch to be used instead.
355 # provide the new patch to be used instead.
356 ret = False
356 ret = False
357 elif r == 3: # Skip
357 elif r == 3: # Skip
358 ret = skipfile = False
358 ret = skipfile = False
359 elif r == 4: # file (Record remaining)
359 elif r == 4: # file (Record remaining)
360 ret = skipfile = True
360 ret = skipfile = True
361 elif r == 5: # done, skip remaining
361 elif r == 5: # done, skip remaining
362 ret = skipall = False
362 ret = skipall = False
363 elif r == 6: # all
363 elif r == 6: # all
364 ret = skipall = True
364 ret = skipall = True
365 elif r == 7: # quit
365 elif r == 7: # quit
366 raise util.Abort(_('user quit'))
366 raise util.Abort(_('user quit'))
367 return ret, skipfile, skipall, newpatches
367 return ret, skipfile, skipall, newpatches
368
368
369 seen = set()
369 seen = set()
370 applied = {} # 'filename' -> [] of chunks
370 applied = {} # 'filename' -> [] of chunks
371 skipfile, skipall = None, None
371 skipfile, skipall = None, None
372 pos, total = 1, sum(len(h.hunks) for h in headers)
372 pos, total = 1, sum(len(h.hunks) for h in headers)
373 for h in headers:
373 for h in headers:
374 pos += len(h.hunks)
374 pos += len(h.hunks)
375 skipfile = None
375 skipfile = None
376 fixoffset = 0
376 fixoffset = 0
377 hdr = ''.join(h.header)
377 hdr = ''.join(h.header)
378 if hdr in seen:
378 if hdr in seen:
379 continue
379 continue
380 seen.add(hdr)
380 seen.add(hdr)
381 if skipall is None:
381 if skipall is None:
382 h.pretty(ui)
382 h.pretty(ui)
383 msg = (_('examine changes to %s?') %
383 msg = (_('examine changes to %s?') %
384 _(' and ').join("'%s'" % f for f in h.files()))
384 _(' and ').join("'%s'" % f for f in h.files()))
385 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
385 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
386 if not r:
386 if not r:
387 continue
387 continue
388 applied[h.filename()] = [h]
388 applied[h.filename()] = [h]
389 if h.allhunks():
389 if h.allhunks():
390 applied[h.filename()] += h.hunks
390 applied[h.filename()] += h.hunks
391 continue
391 continue
392 for i, chunk in enumerate(h.hunks):
392 for i, chunk in enumerate(h.hunks):
393 if skipfile is None and skipall is None:
393 if skipfile is None and skipall is None:
394 chunk.pretty(ui)
394 chunk.pretty(ui)
395 if total == 1:
395 if total == 1:
396 msg = _('record this change to %r?') % chunk.filename()
396 msg = _('record this change to %r?') % chunk.filename()
397 else:
397 else:
398 idx = pos - len(h.hunks) + i
398 idx = pos - len(h.hunks) + i
399 msg = _('record change %d/%d to %r?') % (idx, total,
399 msg = _('record change %d/%d to %r?') % (idx, total,
400 chunk.filename())
400 chunk.filename())
401 r, skipfile, skipall, newpatches = prompt(skipfile,
401 r, skipfile, skipall, newpatches = prompt(skipfile,
402 skipall, msg, chunk)
402 skipall, msg, chunk)
403 if r:
403 if r:
404 if fixoffset:
404 if fixoffset:
405 chunk = copy.copy(chunk)
405 chunk = copy.copy(chunk)
406 chunk.toline += fixoffset
406 chunk.toline += fixoffset
407 applied[chunk.filename()].append(chunk)
407 applied[chunk.filename()].append(chunk)
408 elif newpatches is not None:
408 elif newpatches is not None:
409 for newpatch in newpatches:
409 for newpatch in newpatches:
410 for newhunk in newpatch.hunks:
410 for newhunk in newpatch.hunks:
411 if fixoffset:
411 if fixoffset:
412 newhunk.toline += fixoffset
412 newhunk.toline += fixoffset
413 applied[newhunk.filename()].append(newhunk)
413 applied[newhunk.filename()].append(newhunk)
414 else:
414 else:
415 fixoffset += chunk.removed - chunk.added
415 fixoffset += chunk.removed - chunk.added
416 return sum([h for h in applied.itervalues()
416 return sum([h for h in applied.itervalues()
417 if h[0].special() or len(h) > 1], [])
417 if h[0].special() or len(h) > 1], [])
418
418
419 @command("record",
419 @command("record",
420 # same options as commit + white space diff options
420 # same options as commit + white space diff options
421 commands.table['^commit|ci'][1][:] + diffopts,
421 commands.table['^commit|ci'][1][:] + diffopts,
422 _('hg record [OPTION]... [FILE]...'))
422 _('hg record [OPTION]... [FILE]...'))
423 def record(ui, repo, *pats, **opts):
423 def record(ui, repo, *pats, **opts):
424 '''interactively select changes to commit
424 '''interactively select changes to commit
425
425
426 If a list of files is omitted, all changes reported by :hg:`status`
426 If a list of files is omitted, all changes reported by :hg:`status`
427 will be candidates for recording.
427 will be candidates for recording.
428
428
429 See :hg:`help dates` for a list of formats valid for -d/--date.
429 See :hg:`help dates` for a list of formats valid for -d/--date.
430
430
431 You will be prompted for whether to record changes to each
431 You will be prompted for whether to record changes to each
432 modified file, and for files with multiple changes, for each
432 modified file, and for files with multiple changes, for each
433 change to use. For each query, the following responses are
433 change to use. For each query, the following responses are
434 possible::
434 possible::
435
435
436 y - record this change
436 y - record this change
437 n - skip this change
437 n - skip this change
438 e - edit this change manually
438 e - edit this change manually
439
439
440 s - skip remaining changes to this file
440 s - skip remaining changes to this file
441 f - record remaining changes to this file
441 f - record remaining changes to this file
442
442
443 d - done, skip remaining changes and files
443 d - done, skip remaining changes and files
444 a - record all changes to all remaining files
444 a - record all changes to all remaining files
445 q - quit, recording no changes
445 q - quit, recording no changes
446
446
447 ? - display help
447 ? - display help
448
448
449 This command is not available when committing a merge.'''
449 This command is not available when committing a merge.'''
450
450
451 dorecord(ui, repo, commands.commit, 'commit', False, *pats, **opts)
451 dorecord(ui, repo, commands.commit, 'commit', False, *pats, **opts)
452
452
453 def qrefresh(origfn, ui, repo, *pats, **opts):
453 def qrefresh(origfn, ui, repo, *pats, **opts):
454 if not opts['interactive']:
454 if not opts['interactive']:
455 return origfn(ui, repo, *pats, **opts)
455 return origfn(ui, repo, *pats, **opts)
456
456
457 mq = extensions.find('mq')
457 mq = extensions.find('mq')
458
458
459 def committomq(ui, repo, *pats, **opts):
459 def committomq(ui, repo, *pats, **opts):
460 # At this point the working copy contains only changes that
460 # At this point the working copy contains only changes that
461 # were accepted. All other changes were reverted.
461 # were accepted. All other changes were reverted.
462 # We can't pass *pats here since qrefresh will undo all other
462 # We can't pass *pats here since qrefresh will undo all other
463 # changed files in the patch that aren't in pats.
463 # changed files in the patch that aren't in pats.
464 mq.refresh(ui, repo, **opts)
464 mq.refresh(ui, repo, **opts)
465
465
466 # backup all changed files
466 # backup all changed files
467 dorecord(ui, repo, committomq, 'qrefresh', True, *pats, **opts)
467 dorecord(ui, repo, committomq, 'qrefresh', True, *pats, **opts)
468
468
469 def qrecord(ui, repo, patch, *pats, **opts):
469 def qrecord(ui, repo, patch, *pats, **opts):
470 '''interactively record a new patch
470 '''interactively record a new patch
471
471
472 See :hg:`help qnew` & :hg:`help record` for more information and
472 See :hg:`help qnew` & :hg:`help record` for more information and
473 usage.
473 usage.
474 '''
474 '''
475
475
476 try:
476 try:
477 mq = extensions.find('mq')
477 mq = extensions.find('mq')
478 except KeyError:
478 except KeyError:
479 raise util.Abort(_("'mq' extension not loaded"))
479 raise util.Abort(_("'mq' extension not loaded"))
480
480
481 repo.mq.checkpatchname(patch)
481 repo.mq.checkpatchname(patch)
482
482
483 def committomq(ui, repo, *pats, **opts):
483 def committomq(ui, repo, *pats, **opts):
484 opts['checkname'] = False
484 opts['checkname'] = False
485 mq.new(ui, repo, patch, *pats, **opts)
485 mq.new(ui, repo, patch, *pats, **opts)
486
486
487 dorecord(ui, repo, committomq, 'qnew', False, *pats, **opts)
487 dorecord(ui, repo, committomq, 'qnew', False, *pats, **opts)
488
488
489 def qnew(origfn, ui, repo, patch, *args, **opts):
489 def qnew(origfn, ui, repo, patch, *args, **opts):
490 if opts['interactive']:
490 if opts['interactive']:
491 return qrecord(ui, repo, patch, *args, **opts)
491 return qrecord(ui, repo, patch, *args, **opts)
492 return origfn(ui, repo, patch, *args, **opts)
492 return origfn(ui, repo, patch, *args, **opts)
493
493
494 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, *pats, **opts):
494 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, *pats, **opts):
495 if not ui.interactive():
495 if not ui.interactive():
496 raise util.Abort(_('running non-interactively, use %s instead') %
496 raise util.Abort(_('running non-interactively, use %s instead') %
497 cmdsuggest)
497 cmdsuggest)
498
498
499 def recordfunc(ui, repo, message, match, opts):
499 def recordfunc(ui, repo, message, match, opts):
500 """This is generic record driver.
500 """This is generic record driver.
501
501
502 Its job is to interactively filter local changes, and
502 Its job is to interactively filter local changes, and
503 accordingly prepare working directory into a state in which the
503 accordingly prepare working directory into a state in which the
504 job can be delegated to a non-interactive commit command such as
504 job can be delegated to a non-interactive commit command such as
505 'commit' or 'qrefresh'.
505 'commit' or 'qrefresh'.
506
506
507 After the actual job is done by non-interactive command, the
507 After the actual job is done by non-interactive command, the
508 working directory is restored to its original state.
508 working directory is restored to its original state.
509
509
510 In the end we'll record interesting changes, and everything else
510 In the end we'll record interesting changes, and everything else
511 will be left in place, so the user can continue working.
511 will be left in place, so the user can continue working.
512 """
512 """
513
513
514 merge = len(repo[None].parents()) > 1
514 merge = len(repo[None].parents()) > 1
515 if merge:
515 if merge:
516 raise util.Abort(_('cannot partially commit a merge '
516 raise util.Abort(_('cannot partially commit a merge '
517 '(use "hg commit" instead)'))
517 '(use "hg commit" instead)'))
518
518
519 changes = repo.status(match=match)[:3]
519 changes = repo.status(match=match)[:3]
520 diffopts = mdiff.diffopts(
520 diffopts = mdiff.diffopts(
521 git=True, nodates=True,
521 git=True, nodates=True,
522 ignorews=opts.get('ignore_all_space'),
522 ignorews=opts.get('ignore_all_space'),
523 ignorewsamount=opts.get('ignore_space_change'),
523 ignorewsamount=opts.get('ignore_space_change'),
524 ignoreblanklines=opts.get('ignore_blank_lines'))
524 ignoreblanklines=opts.get('ignore_blank_lines'))
525 chunks = patch.diff(repo, changes=changes, opts=diffopts)
525 chunks = patch.diff(repo, changes=changes, opts=diffopts)
526 fp = cStringIO.StringIO()
526 fp = cStringIO.StringIO()
527 fp.write(''.join(chunks))
527 fp.write(''.join(chunks))
528 fp.seek(0)
528 fp.seek(0)
529
529
530 # 1. filter patch, so we have intending-to apply subset of it
530 # 1. filter patch, so we have intending-to apply subset of it
531 chunks = filterpatch(ui, parsepatch(fp))
531 chunks = filterpatch(ui, parsepatch(fp))
532 del fp
532 del fp
533
533
534 contenders = set()
534 contenders = set()
535 for h in chunks:
535 for h in chunks:
536 try:
536 try:
537 contenders.update(set(h.files()))
537 contenders.update(set(h.files()))
538 except AttributeError:
538 except AttributeError:
539 pass
539 pass
540
540
541 changed = changes[0] + changes[1] + changes[2]
541 changed = changes[0] + changes[1] + changes[2]
542 newfiles = [f for f in changed if f in contenders]
542 newfiles = [f for f in changed if f in contenders]
543 if not newfiles:
543 if not newfiles:
544 ui.status(_('no changes to record\n'))
544 ui.status(_('no changes to record\n'))
545 return 0
545 return 0
546
546
547 modified = set(changes[0])
547 modified = set(changes[0])
548
548
549 # 2. backup changed files, so we can restore them in the end
549 # 2. backup changed files, so we can restore them in the end
550 if backupall:
550 if backupall:
551 tobackup = changed
551 tobackup = changed
552 else:
552 else:
553 tobackup = [f for f in newfiles if f in modified]
553 tobackup = [f for f in newfiles if f in modified]
554
554
555 backups = {}
555 backups = {}
556 if tobackup:
556 if tobackup:
557 backupdir = repo.join('record-backups')
557 backupdir = repo.join('record-backups')
558 try:
558 try:
559 os.mkdir(backupdir)
559 os.mkdir(backupdir)
560 except OSError, err:
560 except OSError, err:
561 if err.errno != errno.EEXIST:
561 if err.errno != errno.EEXIST:
562 raise
562 raise
563 try:
563 try:
564 # backup continues
564 # backup continues
565 for f in tobackup:
565 for f in tobackup:
566 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
566 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
567 dir=backupdir)
567 dir=backupdir)
568 os.close(fd)
568 os.close(fd)
569 ui.debug('backup %r as %r\n' % (f, tmpname))
569 ui.debug('backup %r as %r\n' % (f, tmpname))
570 util.copyfile(repo.wjoin(f), tmpname)
570 util.copyfile(repo.wjoin(f), tmpname)
571 shutil.copystat(repo.wjoin(f), tmpname)
571 shutil.copystat(repo.wjoin(f), tmpname)
572 backups[f] = tmpname
572 backups[f] = tmpname
573
573
574 fp = cStringIO.StringIO()
574 fp = cStringIO.StringIO()
575 for c in chunks:
575 for c in chunks:
576 if c.filename() in backups:
576 if c.filename() in backups:
577 c.write(fp)
577 c.write(fp)
578 dopatch = fp.tell()
578 dopatch = fp.tell()
579 fp.seek(0)
579 fp.seek(0)
580
580
581 # 3a. apply filtered patch to clean repo (clean)
581 # 3a. apply filtered patch to clean repo (clean)
582 if backups:
582 if backups:
583 hg.revert(repo, repo.dirstate.p1(),
583 hg.revert(repo, repo.dirstate.p1(),
584 lambda key: key in backups)
584 lambda key: key in backups)
585
585
586 # 3b. (apply)
586 # 3b. (apply)
587 if dopatch:
587 if dopatch:
588 try:
588 try:
589 ui.debug('applying patch\n')
589 ui.debug('applying patch\n')
590 ui.debug(fp.getvalue())
590 ui.debug(fp.getvalue())
591 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
591 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
592 except patch.PatchError, err:
592 except patch.PatchError, err:
593 raise util.Abort(str(err))
593 raise util.Abort(str(err))
594 del fp
594 del fp
595
595
596 # 4. We prepared working directory according to filtered
596 # 4. We prepared working directory according to filtered
597 # patch. Now is the time to delegate the job to
597 # patch. Now is the time to delegate the job to
598 # commit/qrefresh or the like!
598 # commit/qrefresh or the like!
599
599
600 # it is important to first chdir to repo root -- we'll call
600 # it is important to first chdir to repo root -- we'll call
601 # a highlevel command with list of pathnames relative to
601 # a highlevel command with list of pathnames relative to
602 # repo root
602 # repo root
603 cwd = os.getcwd()
603 cwd = os.getcwd()
604 os.chdir(repo.root)
604 os.chdir(repo.root)
605 try:
605 try:
606 commitfunc(ui, repo, *newfiles, **opts)
606 commitfunc(ui, repo, *newfiles, **opts)
607 finally:
607 finally:
608 os.chdir(cwd)
608 os.chdir(cwd)
609
609
610 return 0
610 return 0
611 finally:
611 finally:
612 # 5. finally restore backed-up files
612 # 5. finally restore backed-up files
613 try:
613 try:
614 for realname, tmpname in backups.iteritems():
614 for realname, tmpname in backups.iteritems():
615 ui.debug('restoring %r to %r\n' % (tmpname, realname))
615 ui.debug('restoring %r to %r\n' % (tmpname, realname))
616 util.copyfile(tmpname, repo.wjoin(realname))
616 util.copyfile(tmpname, repo.wjoin(realname))
617 # Our calls to copystat() here and above are a
617 # Our calls to copystat() here and above are a
618 # hack to trick any editors that have f open that
618 # hack to trick any editors that have f open that
619 # we haven't modified them.
619 # we haven't modified them.
620 #
620 #
621 # Also note that this racy as an editor could
621 # Also note that this racy as an editor could
622 # notice the file's mtime before we've finished
622 # notice the file's mtime before we've finished
623 # writing it.
623 # writing it.
624 shutil.copystat(tmpname, repo.wjoin(realname))
624 shutil.copystat(tmpname, repo.wjoin(realname))
625 os.unlink(tmpname)
625 os.unlink(tmpname)
626 if tobackup:
626 if tobackup:
627 os.rmdir(backupdir)
627 os.rmdir(backupdir)
628 except OSError:
628 except OSError:
629 pass
629 pass
630
630
631 # wrap ui.write so diff output can be labeled/colorized
631 # wrap ui.write so diff output can be labeled/colorized
632 def wrapwrite(orig, *args, **kw):
632 def wrapwrite(orig, *args, **kw):
633 label = kw.pop('label', '')
633 label = kw.pop('label', '')
634 for chunk, l in patch.difflabel(lambda: args):
634 for chunk, l in patch.difflabel(lambda: args):
635 orig(chunk, label=label + l)
635 orig(chunk, label=label + l)
636 oldwrite = ui.write
636 oldwrite = ui.write
637 extensions.wrapfunction(ui, 'write', wrapwrite)
637 extensions.wrapfunction(ui, 'write', wrapwrite)
638 try:
638 try:
639 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
639 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
640 finally:
640 finally:
641 ui.write = oldwrite
641 ui.write = oldwrite
642
642
643 cmdtable["qrecord"] = \
643 cmdtable["qrecord"] = \
644 (qrecord, [], # placeholder until mq is available
644 (qrecord, [], # placeholder until mq is available
645 _('hg qrecord [OPTION]... PATCH [FILE]...'))
645 _('hg qrecord [OPTION]... PATCH [FILE]...'))
646
646
647 def uisetup(ui):
647 def uisetup(ui):
648 try:
648 try:
649 mq = extensions.find('mq')
649 mq = extensions.find('mq')
650 except KeyError:
650 except KeyError:
651 return
651 return
652
652
653 cmdtable["qrecord"] = \
653 cmdtable["qrecord"] = \
654 (qrecord,
654 (qrecord,
655 # same options as qnew, but copy them so we don't get
655 # same options as qnew, but copy them so we don't get
656 # -i/--interactive for qrecord and add white space diff options
656 # -i/--interactive for qrecord and add white space diff options
657 mq.cmdtable['^qnew'][1][:] + diffopts,
657 mq.cmdtable['^qnew'][1][:] + diffopts,
658 _('hg qrecord [OPTION]... PATCH [FILE]...'))
658 _('hg qrecord [OPTION]... PATCH [FILE]...'))
659
659
660 _wrapcmd('qnew', mq.cmdtable, qnew, _("interactively record a new patch"))
660 _wrapcmd('qnew', mq.cmdtable, qnew, _("interactively record a new patch"))
661 _wrapcmd('qrefresh', mq.cmdtable, qrefresh,
661 _wrapcmd('qrefresh', mq.cmdtable, qrefresh,
662 _("interactively select changes to refresh"))
662 _("interactively select changes to refresh"))
663
663
664 def _wrapcmd(cmd, table, wrapfn, msg):
664 def _wrapcmd(cmd, table, wrapfn, msg):
665 entry = extensions.wrapcommand(table, cmd, wrapfn)
665 entry = extensions.wrapcommand(table, cmd, wrapfn)
666 entry[1].append(('i', 'interactive', None, msg))
666 entry[1].append(('i', 'interactive', None, msg))
@@ -1,101 +1,101 b''
1 # Copyright 2009, Alexander Solovyov <piranha@piranha.org.ua>
1 # Copyright 2009, Alexander Solovyov <piranha@piranha.org.ua>
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 """extend schemes with shortcuts to repository swarms
6 """extend schemes with shortcuts to repository swarms
7
7
8 This extension allows you to specify shortcuts for parent URLs with a
8 This extension allows you to specify shortcuts for parent URLs with a
9 lot of repositories to act like a scheme, for example::
9 lot of repositories to act like a scheme, for example::
10
10
11 [schemes]
11 [schemes]
12 py = http://code.python.org/hg/
12 py = http://code.python.org/hg/
13
13
14 After that you can use it like::
14 After that you can use it like::
15
15
16 hg clone py://trunk/
16 hg clone py://trunk/
17
17
18 Additionally there is support for some more complex schemas, for
18 Additionally there is support for some more complex schemas, for
19 example used by Google Code::
19 example used by Google Code::
20
20
21 [schemes]
21 [schemes]
22 gcode = http://{1}.googlecode.com/hg/
22 gcode = http://{1}.googlecode.com/hg/
23
23
24 The syntax is taken from Mercurial templates, and you have unlimited
24 The syntax is taken from Mercurial templates, and you have unlimited
25 number of variables, starting with ``{1}`` and continuing with
25 number of variables, starting with ``{1}`` and continuing with
26 ``{2}``, ``{3}`` and so on. This variables will receive parts of URL
26 ``{2}``, ``{3}`` and so on. This variables will receive parts of URL
27 supplied, split by ``/``. Anything not specified as ``{part}`` will be
27 supplied, split by ``/``. Anything not specified as ``{part}`` will be
28 just appended to an URL.
28 just appended to an URL.
29
29
30 For convenience, the extension adds these schemes by default::
30 For convenience, the extension adds these schemes by default::
31
31
32 [schemes]
32 [schemes]
33 py = http://hg.python.org/
33 py = http://hg.python.org/
34 bb = https://bitbucket.org/
34 bb = https://bitbucket.org/
35 bb+ssh = ssh://hg@bitbucket.org/
35 bb+ssh = ssh://hg@bitbucket.org/
36 gcode = https://{1}.googlecode.com/hg/
36 gcode = https://{1}.googlecode.com/hg/
37 kiln = https://{1}.kilnhg.com/Repo/
37 kiln = https://{1}.kilnhg.com/Repo/
38
38
39 You can override a predefined scheme by defining a new scheme with the
39 You can override a predefined scheme by defining a new scheme with the
40 same name.
40 same name.
41 """
41 """
42
42
43 import os, re
43 import os, re
44 from mercurial import extensions, hg, templater, util
44 from mercurial import extensions, hg, templater, util
45 from mercurial.i18n import _
45 from mercurial.i18n import _
46
46
47 testedwith = 'internal'
47 testedwith = 'internal'
48
48
49
49
50 class ShortRepository(object):
50 class ShortRepository(object):
51 def __init__(self, url, scheme, templater):
51 def __init__(self, url, scheme, templater):
52 self.scheme = scheme
52 self.scheme = scheme
53 self.templater = templater
53 self.templater = templater
54 self.url = url
54 self.url = url
55 try:
55 try:
56 self.parts = max(map(int, re.findall(r'\{(\d+)\}', self.url)))
56 self.parts = max(map(int, re.findall(r'\{(\d+)\}', self.url)))
57 except ValueError:
57 except ValueError:
58 self.parts = 0
58 self.parts = 0
59
59
60 def __repr__(self):
60 def __repr__(self):
61 return '<ShortRepository: %s>' % self.scheme
61 return '<ShortRepository: %s>' % self.scheme
62
62
63 def instance(self, ui, url, create):
63 def instance(self, ui, url, create):
64 # Should this use urlmod.url(), or is manual parsing better?
64 # Should this use the util.url class, or is manual parsing better?
65 url = url.split('://', 1)[1]
65 url = url.split('://', 1)[1]
66 parts = url.split('/', self.parts)
66 parts = url.split('/', self.parts)
67 if len(parts) > self.parts:
67 if len(parts) > self.parts:
68 tail = parts[-1]
68 tail = parts[-1]
69 parts = parts[:-1]
69 parts = parts[:-1]
70 else:
70 else:
71 tail = ''
71 tail = ''
72 context = dict((str(i + 1), v) for i, v in enumerate(parts))
72 context = dict((str(i + 1), v) for i, v in enumerate(parts))
73 url = ''.join(self.templater.process(self.url, context)) + tail
73 url = ''.join(self.templater.process(self.url, context)) + tail
74 return hg._peerlookup(url).instance(ui, url, create)
74 return hg._peerlookup(url).instance(ui, url, create)
75
75
76 def hasdriveletter(orig, path):
76 def hasdriveletter(orig, path):
77 if path:
77 if path:
78 for scheme in schemes:
78 for scheme in schemes:
79 if path.startswith(scheme + ':'):
79 if path.startswith(scheme + ':'):
80 return False
80 return False
81 return orig(path)
81 return orig(path)
82
82
83 schemes = {
83 schemes = {
84 'py': 'http://hg.python.org/',
84 'py': 'http://hg.python.org/',
85 'bb': 'https://bitbucket.org/',
85 'bb': 'https://bitbucket.org/',
86 'bb+ssh': 'ssh://hg@bitbucket.org/',
86 'bb+ssh': 'ssh://hg@bitbucket.org/',
87 'gcode': 'https://{1}.googlecode.com/hg/',
87 'gcode': 'https://{1}.googlecode.com/hg/',
88 'kiln': 'https://{1}.kilnhg.com/Repo/'
88 'kiln': 'https://{1}.kilnhg.com/Repo/'
89 }
89 }
90
90
91 def extsetup(ui):
91 def extsetup(ui):
92 schemes.update(dict(ui.configitems('schemes')))
92 schemes.update(dict(ui.configitems('schemes')))
93 t = templater.engine(lambda x: x)
93 t = templater.engine(lambda x: x)
94 for scheme, url in schemes.items():
94 for scheme, url in schemes.items():
95 if (os.name == 'nt' and len(scheme) == 1 and scheme.isalpha()
95 if (os.name == 'nt' and len(scheme) == 1 and scheme.isalpha()
96 and os.path.exists('%s:\\' % scheme)):
96 and os.path.exists('%s:\\' % scheme)):
97 raise util.Abort(_('custom scheme %s:// conflicts with drive '
97 raise util.Abort(_('custom scheme %s:// conflicts with drive '
98 'letter %s:\\\n') % (scheme, scheme.upper()))
98 'letter %s:\\\n') % (scheme, scheme.upper()))
99 hg.schemes[scheme] = ShortRepository(url, scheme, t)
99 hg.schemes[scheme] = ShortRepository(url, scheme, t)
100
100
101 extensions.wrapfunction(util, 'hasdriveletter', hasdriveletter)
101 extensions.wrapfunction(util, 'hasdriveletter', hasdriveletter)
@@ -1,1582 +1,1582 b''
1 """ Multicast DNS Service Discovery for Python, v0.12
1 """ Multicast DNS Service Discovery for Python, v0.12
2 Copyright (C) 2003, Paul Scott-Murphy
2 Copyright (C) 2003, Paul Scott-Murphy
3
3
4 This module provides a framework for the use of DNS Service Discovery
4 This module provides a framework for the use of DNS Service Discovery
5 using IP multicast. It has been tested against the JRendezvous
5 using IP multicast. It has been tested against the JRendezvous
6 implementation from <a href="http://strangeberry.com">StrangeBerry</a>,
6 implementation from <a href="http://strangeberry.com">StrangeBerry</a>,
7 and against the mDNSResponder from Mac OS X 10.3.8.
7 and against the mDNSResponder from Mac OS X 10.3.8.
8
8
9 This library is free software; you can redistribute it and/or
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Lesser General Public
10 modify it under the terms of the GNU Lesser General Public
11 License as published by the Free Software Foundation; either
11 License as published by the Free Software Foundation; either
12 version 2.1 of the License, or (at your option) any later version.
12 version 2.1 of the License, or (at your option) any later version.
13
13
14 This library is distributed in the hope that it will be useful,
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
17 Lesser General Public License for more details.
18
18
19 You should have received a copy of the GNU Lesser General Public
19 You should have received a copy of the GNU Lesser General Public
20 License along with this library; if not, see
20 License along with this library; if not, see
21 <http://www.gnu.org/licenses/>.
21 <http://www.gnu.org/licenses/>.
22
22
23 """
23 """
24
24
25 """0.12 update - allow selection of binding interface
25 """0.12 update - allow selection of binding interface
26 typo fix - Thanks A. M. Kuchlingi
26 typo fix - Thanks A. M. Kuchlingi
27 removed all use of word 'Rendezvous' - this is an API change"""
27 removed all use of word 'Rendezvous' - this is an API change"""
28
28
29 """0.11 update - correction to comments for addListener method
29 """0.11 update - correction to comments for addListener method
30 support for new record types seen from OS X
30 support for new record types seen from OS X
31 - IPv6 address
31 - IPv6 address
32 - hostinfo
32 - hostinfo
33 ignore unknown DNS record types
33 ignore unknown DNS record types
34 fixes to name decoding
34 fixes to name decoding
35 works alongside other processes using port 5353 (e.g. on Mac OS X)
35 works alongside other processes using port 5353 (e.g. on Mac OS X)
36 tested against Mac OS X 10.3.2's mDNSResponder
36 tested against Mac OS X 10.3.2's mDNSResponder
37 corrections to removal of list entries for service browser"""
37 corrections to removal of list entries for service browser"""
38
38
39 """0.10 update - Jonathon Paisley contributed these corrections:
39 """0.10 update - Jonathon Paisley contributed these corrections:
40 always multicast replies, even when query is unicast
40 always multicast replies, even when query is unicast
41 correct a pointer encoding problem
41 correct a pointer encoding problem
42 can now write records in any order
42 can now write records in any order
43 traceback shown on failure
43 traceback shown on failure
44 better TXT record parsing
44 better TXT record parsing
45 server is now separate from name
45 server is now separate from name
46 can cancel a service browser
46 can cancel a service browser
47
47
48 modified some unit tests to accommodate these changes"""
48 modified some unit tests to accommodate these changes"""
49
49
50 """0.09 update - remove all records on service unregistration
50 """0.09 update - remove all records on service unregistration
51 fix DOS security problem with readName"""
51 fix DOS security problem with readName"""
52
52
53 """0.08 update - changed licensing to LGPL"""
53 """0.08 update - changed licensing to LGPL"""
54
54
55 """0.07 update - faster shutdown on engine
55 """0.07 update - faster shutdown on engine
56 pointer encoding of outgoing names
56 pointer encoding of outgoing names
57 ServiceBrowser now works
57 ServiceBrowser now works
58 new unit tests"""
58 new unit tests"""
59
59
60 """0.06 update - small improvements with unit tests
60 """0.06 update - small improvements with unit tests
61 added defined exception types
61 added defined exception types
62 new style objects
62 new style objects
63 fixed hostname/interface problem
63 fixed hostname/interface problem
64 fixed socket timeout problem
64 fixed socket timeout problem
65 fixed addServiceListener() typo bug
65 fixed addServiceListener() typo bug
66 using select() for socket reads
66 using select() for socket reads
67 tested on Debian unstable with Python 2.2.2"""
67 tested on Debian unstable with Python 2.2.2"""
68
68
69 """0.05 update - ensure case insensitivity on domain names
69 """0.05 update - ensure case insensitivity on domain names
70 support for unicast DNS queries"""
70 support for unicast DNS queries"""
71
71
72 """0.04 update - added some unit tests
72 """0.04 update - added some unit tests
73 added __ne__ adjuncts where required
73 added __ne__ adjuncts where required
74 ensure names end in '.local.'
74 ensure names end in '.local.'
75 timeout on receiving socket for clean shutdown"""
75 timeout on receiving socket for clean shutdown"""
76
76
77 __author__ = "Paul Scott-Murphy"
77 __author__ = "Paul Scott-Murphy"
78 __email__ = "paul at scott dash murphy dot com"
78 __email__ = "paul at scott dash murphy dot com"
79 __version__ = "0.12"
79 __version__ = "0.12"
80
80
81 import string
81 import string
82 import time
82 import time
83 import struct
83 import struct
84 import socket
84 import socket
85 import threading
85 import threading
86 import select
86 import select
87 import traceback
87 import traceback
88
88
89 __all__ = ["Zeroconf", "ServiceInfo", "ServiceBrowser"]
89 __all__ = ["Zeroconf", "ServiceInfo", "ServiceBrowser"]
90
90
91 # hook for threads
91 # hook for threads
92
92
93 globals()['_GLOBAL_DONE'] = 0
93 globals()['_GLOBAL_DONE'] = 0
94
94
95 # Some timing constants
95 # Some timing constants
96
96
97 _UNREGISTER_TIME = 125
97 _UNREGISTER_TIME = 125
98 _CHECK_TIME = 175
98 _CHECK_TIME = 175
99 _REGISTER_TIME = 225
99 _REGISTER_TIME = 225
100 _LISTENER_TIME = 200
100 _LISTENER_TIME = 200
101 _BROWSER_TIME = 500
101 _BROWSER_TIME = 500
102
102
103 # Some DNS constants
103 # Some DNS constants
104
104
105 _MDNS_ADDR = '224.0.0.251'
105 _MDNS_ADDR = '224.0.0.251'
106 _MDNS_PORT = 5353;
106 _MDNS_PORT = 5353;
107 _DNS_PORT = 53;
107 _DNS_PORT = 53;
108 _DNS_TTL = 60 * 60; # one hour default TTL
108 _DNS_TTL = 60 * 60; # one hour default TTL
109
109
110 _MAX_MSG_TYPICAL = 1460 # unused
110 _MAX_MSG_TYPICAL = 1460 # unused
111 _MAX_MSG_ABSOLUTE = 8972
111 _MAX_MSG_ABSOLUTE = 8972
112
112
113 _FLAGS_QR_MASK = 0x8000 # query response mask
113 _FLAGS_QR_MASK = 0x8000 # query response mask
114 _FLAGS_QR_QUERY = 0x0000 # query
114 _FLAGS_QR_QUERY = 0x0000 # query
115 _FLAGS_QR_RESPONSE = 0x8000 # response
115 _FLAGS_QR_RESPONSE = 0x8000 # response
116
116
117 _FLAGS_AA = 0x0400 # Authorative answer
117 _FLAGS_AA = 0x0400 # Authoritative answer
118 _FLAGS_TC = 0x0200 # Truncated
118 _FLAGS_TC = 0x0200 # Truncated
119 _FLAGS_RD = 0x0100 # Recursion desired
119 _FLAGS_RD = 0x0100 # Recursion desired
120 _FLAGS_RA = 0x8000 # Recursion available
120 _FLAGS_RA = 0x8000 # Recursion available
121
121
122 _FLAGS_Z = 0x0040 # Zero
122 _FLAGS_Z = 0x0040 # Zero
123 _FLAGS_AD = 0x0020 # Authentic data
123 _FLAGS_AD = 0x0020 # Authentic data
124 _FLAGS_CD = 0x0010 # Checking disabled
124 _FLAGS_CD = 0x0010 # Checking disabled
125
125
126 _CLASS_IN = 1
126 _CLASS_IN = 1
127 _CLASS_CS = 2
127 _CLASS_CS = 2
128 _CLASS_CH = 3
128 _CLASS_CH = 3
129 _CLASS_HS = 4
129 _CLASS_HS = 4
130 _CLASS_NONE = 254
130 _CLASS_NONE = 254
131 _CLASS_ANY = 255
131 _CLASS_ANY = 255
132 _CLASS_MASK = 0x7FFF
132 _CLASS_MASK = 0x7FFF
133 _CLASS_UNIQUE = 0x8000
133 _CLASS_UNIQUE = 0x8000
134
134
135 _TYPE_A = 1
135 _TYPE_A = 1
136 _TYPE_NS = 2
136 _TYPE_NS = 2
137 _TYPE_MD = 3
137 _TYPE_MD = 3
138 _TYPE_MF = 4
138 _TYPE_MF = 4
139 _TYPE_CNAME = 5
139 _TYPE_CNAME = 5
140 _TYPE_SOA = 6
140 _TYPE_SOA = 6
141 _TYPE_MB = 7
141 _TYPE_MB = 7
142 _TYPE_MG = 8
142 _TYPE_MG = 8
143 _TYPE_MR = 9
143 _TYPE_MR = 9
144 _TYPE_NULL = 10
144 _TYPE_NULL = 10
145 _TYPE_WKS = 11
145 _TYPE_WKS = 11
146 _TYPE_PTR = 12
146 _TYPE_PTR = 12
147 _TYPE_HINFO = 13
147 _TYPE_HINFO = 13
148 _TYPE_MINFO = 14
148 _TYPE_MINFO = 14
149 _TYPE_MX = 15
149 _TYPE_MX = 15
150 _TYPE_TXT = 16
150 _TYPE_TXT = 16
151 _TYPE_AAAA = 28
151 _TYPE_AAAA = 28
152 _TYPE_SRV = 33
152 _TYPE_SRV = 33
153 _TYPE_ANY = 255
153 _TYPE_ANY = 255
154
154
155 # Mapping constants to names
155 # Mapping constants to names
156
156
157 _CLASSES = { _CLASS_IN : "in",
157 _CLASSES = { _CLASS_IN : "in",
158 _CLASS_CS : "cs",
158 _CLASS_CS : "cs",
159 _CLASS_CH : "ch",
159 _CLASS_CH : "ch",
160 _CLASS_HS : "hs",
160 _CLASS_HS : "hs",
161 _CLASS_NONE : "none",
161 _CLASS_NONE : "none",
162 _CLASS_ANY : "any" }
162 _CLASS_ANY : "any" }
163
163
164 _TYPES = { _TYPE_A : "a",
164 _TYPES = { _TYPE_A : "a",
165 _TYPE_NS : "ns",
165 _TYPE_NS : "ns",
166 _TYPE_MD : "md",
166 _TYPE_MD : "md",
167 _TYPE_MF : "mf",
167 _TYPE_MF : "mf",
168 _TYPE_CNAME : "cname",
168 _TYPE_CNAME : "cname",
169 _TYPE_SOA : "soa",
169 _TYPE_SOA : "soa",
170 _TYPE_MB : "mb",
170 _TYPE_MB : "mb",
171 _TYPE_MG : "mg",
171 _TYPE_MG : "mg",
172 _TYPE_MR : "mr",
172 _TYPE_MR : "mr",
173 _TYPE_NULL : "null",
173 _TYPE_NULL : "null",
174 _TYPE_WKS : "wks",
174 _TYPE_WKS : "wks",
175 _TYPE_PTR : "ptr",
175 _TYPE_PTR : "ptr",
176 _TYPE_HINFO : "hinfo",
176 _TYPE_HINFO : "hinfo",
177 _TYPE_MINFO : "minfo",
177 _TYPE_MINFO : "minfo",
178 _TYPE_MX : "mx",
178 _TYPE_MX : "mx",
179 _TYPE_TXT : "txt",
179 _TYPE_TXT : "txt",
180 _TYPE_AAAA : "quada",
180 _TYPE_AAAA : "quada",
181 _TYPE_SRV : "srv",
181 _TYPE_SRV : "srv",
182 _TYPE_ANY : "any" }
182 _TYPE_ANY : "any" }
183
183
184 # utility functions
184 # utility functions
185
185
186 def currentTimeMillis():
186 def currentTimeMillis():
187 """Current system time in milliseconds"""
187 """Current system time in milliseconds"""
188 return time.time() * 1000
188 return time.time() * 1000
189
189
190 # Exceptions
190 # Exceptions
191
191
192 class NonLocalNameException(Exception):
192 class NonLocalNameException(Exception):
193 pass
193 pass
194
194
195 class NonUniqueNameException(Exception):
195 class NonUniqueNameException(Exception):
196 pass
196 pass
197
197
198 class NamePartTooLongException(Exception):
198 class NamePartTooLongException(Exception):
199 pass
199 pass
200
200
201 class AbstractMethodException(Exception):
201 class AbstractMethodException(Exception):
202 pass
202 pass
203
203
204 class BadTypeInNameException(Exception):
204 class BadTypeInNameException(Exception):
205 pass
205 pass
206
206
207 class BadDomainName(Exception):
207 class BadDomainName(Exception):
208 def __init__(self, pos):
208 def __init__(self, pos):
209 Exception.__init__(self, "at position %s" % pos)
209 Exception.__init__(self, "at position %s" % pos)
210
210
211 class BadDomainNameCircular(BadDomainName):
211 class BadDomainNameCircular(BadDomainName):
212 pass
212 pass
213
213
214 # implementation classes
214 # implementation classes
215
215
216 class DNSEntry(object):
216 class DNSEntry(object):
217 """A DNS entry"""
217 """A DNS entry"""
218
218
219 def __init__(self, name, type, clazz):
219 def __init__(self, name, type, clazz):
220 self.key = string.lower(name)
220 self.key = string.lower(name)
221 self.name = name
221 self.name = name
222 self.type = type
222 self.type = type
223 self.clazz = clazz & _CLASS_MASK
223 self.clazz = clazz & _CLASS_MASK
224 self.unique = (clazz & _CLASS_UNIQUE) != 0
224 self.unique = (clazz & _CLASS_UNIQUE) != 0
225
225
226 def __eq__(self, other):
226 def __eq__(self, other):
227 """Equality test on name, type, and class"""
227 """Equality test on name, type, and class"""
228 if isinstance(other, DNSEntry):
228 if isinstance(other, DNSEntry):
229 return self.name == other.name and self.type == other.type and self.clazz == other.clazz
229 return self.name == other.name and self.type == other.type and self.clazz == other.clazz
230 return 0
230 return 0
231
231
232 def __ne__(self, other):
232 def __ne__(self, other):
233 """Non-equality test"""
233 """Non-equality test"""
234 return not self.__eq__(other)
234 return not self.__eq__(other)
235
235
236 def getClazz(self, clazz):
236 def getClazz(self, clazz):
237 """Class accessor"""
237 """Class accessor"""
238 try:
238 try:
239 return _CLASSES[clazz]
239 return _CLASSES[clazz]
240 except KeyError:
240 except KeyError:
241 return "?(%s)" % (clazz)
241 return "?(%s)" % (clazz)
242
242
243 def getType(self, type):
243 def getType(self, type):
244 """Type accessor"""
244 """Type accessor"""
245 try:
245 try:
246 return _TYPES[type]
246 return _TYPES[type]
247 except KeyError:
247 except KeyError:
248 return "?(%s)" % (type)
248 return "?(%s)" % (type)
249
249
250 def toString(self, hdr, other):
250 def toString(self, hdr, other):
251 """String representation with additional information"""
251 """String representation with additional information"""
252 result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz))
252 result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz))
253 if self.unique:
253 if self.unique:
254 result += "-unique,"
254 result += "-unique,"
255 else:
255 else:
256 result += ","
256 result += ","
257 result += self.name
257 result += self.name
258 if other is not None:
258 if other is not None:
259 result += ",%s]" % (other)
259 result += ",%s]" % (other)
260 else:
260 else:
261 result += "]"
261 result += "]"
262 return result
262 return result
263
263
264 class DNSQuestion(DNSEntry):
264 class DNSQuestion(DNSEntry):
265 """A DNS question entry"""
265 """A DNS question entry"""
266
266
267 def __init__(self, name, type, clazz):
267 def __init__(self, name, type, clazz):
268 if not name.endswith(".local."):
268 if not name.endswith(".local."):
269 raise NonLocalNameException(name)
269 raise NonLocalNameException(name)
270 DNSEntry.__init__(self, name, type, clazz)
270 DNSEntry.__init__(self, name, type, clazz)
271
271
272 def answeredBy(self, rec):
272 def answeredBy(self, rec):
273 """Returns true if the question is answered by the record"""
273 """Returns true if the question is answered by the record"""
274 return self.clazz == rec.clazz and (self.type == rec.type or self.type == _TYPE_ANY) and self.name == rec.name
274 return self.clazz == rec.clazz and (self.type == rec.type or self.type == _TYPE_ANY) and self.name == rec.name
275
275
276 def __repr__(self):
276 def __repr__(self):
277 """String representation"""
277 """String representation"""
278 return DNSEntry.toString(self, "question", None)
278 return DNSEntry.toString(self, "question", None)
279
279
280
280
281 class DNSRecord(DNSEntry):
281 class DNSRecord(DNSEntry):
282 """A DNS record - like a DNS entry, but has a TTL"""
282 """A DNS record - like a DNS entry, but has a TTL"""
283
283
284 def __init__(self, name, type, clazz, ttl):
284 def __init__(self, name, type, clazz, ttl):
285 DNSEntry.__init__(self, name, type, clazz)
285 DNSEntry.__init__(self, name, type, clazz)
286 self.ttl = ttl
286 self.ttl = ttl
287 self.created = currentTimeMillis()
287 self.created = currentTimeMillis()
288
288
289 def __eq__(self, other):
289 def __eq__(self, other):
290 """Tests equality as per DNSRecord"""
290 """Tests equality as per DNSRecord"""
291 if isinstance(other, DNSRecord):
291 if isinstance(other, DNSRecord):
292 return DNSEntry.__eq__(self, other)
292 return DNSEntry.__eq__(self, other)
293 return 0
293 return 0
294
294
295 def suppressedBy(self, msg):
295 def suppressedBy(self, msg):
296 """Returns true if any answer in a message can suffice for the
296 """Returns true if any answer in a message can suffice for the
297 information held in this record."""
297 information held in this record."""
298 for record in msg.answers:
298 for record in msg.answers:
299 if self.suppressedByAnswer(record):
299 if self.suppressedByAnswer(record):
300 return 1
300 return 1
301 return 0
301 return 0
302
302
303 def suppressedByAnswer(self, other):
303 def suppressedByAnswer(self, other):
304 """Returns true if another record has same name, type and class,
304 """Returns true if another record has same name, type and class,
305 and if its TTL is at least half of this record's."""
305 and if its TTL is at least half of this record's."""
306 if self == other and other.ttl > (self.ttl / 2):
306 if self == other and other.ttl > (self.ttl / 2):
307 return 1
307 return 1
308 return 0
308 return 0
309
309
310 def getExpirationTime(self, percent):
310 def getExpirationTime(self, percent):
311 """Returns the time at which this record will have expired
311 """Returns the time at which this record will have expired
312 by a certain percentage."""
312 by a certain percentage."""
313 return self.created + (percent * self.ttl * 10)
313 return self.created + (percent * self.ttl * 10)
314
314
315 def getRemainingTTL(self, now):
315 def getRemainingTTL(self, now):
316 """Returns the remaining TTL in seconds."""
316 """Returns the remaining TTL in seconds."""
317 return max(0, (self.getExpirationTime(100) - now) / 1000)
317 return max(0, (self.getExpirationTime(100) - now) / 1000)
318
318
319 def isExpired(self, now):
319 def isExpired(self, now):
320 """Returns true if this record has expired."""
320 """Returns true if this record has expired."""
321 return self.getExpirationTime(100) <= now
321 return self.getExpirationTime(100) <= now
322
322
323 def isStale(self, now):
323 def isStale(self, now):
324 """Returns true if this record is at least half way expired."""
324 """Returns true if this record is at least half way expired."""
325 return self.getExpirationTime(50) <= now
325 return self.getExpirationTime(50) <= now
326
326
327 def resetTTL(self, other):
327 def resetTTL(self, other):
328 """Sets this record's TTL and created time to that of
328 """Sets this record's TTL and created time to that of
329 another record."""
329 another record."""
330 self.created = other.created
330 self.created = other.created
331 self.ttl = other.ttl
331 self.ttl = other.ttl
332
332
333 def write(self, out):
333 def write(self, out):
334 """Abstract method"""
334 """Abstract method"""
335 raise AbstractMethodException
335 raise AbstractMethodException
336
336
337 def toString(self, other):
337 def toString(self, other):
338 """String representation with additional information"""
338 """String representation with additional information"""
339 arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other)
339 arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other)
340 return DNSEntry.toString(self, "record", arg)
340 return DNSEntry.toString(self, "record", arg)
341
341
342 class DNSAddress(DNSRecord):
342 class DNSAddress(DNSRecord):
343 """A DNS address record"""
343 """A DNS address record"""
344
344
345 def __init__(self, name, type, clazz, ttl, address):
345 def __init__(self, name, type, clazz, ttl, address):
346 DNSRecord.__init__(self, name, type, clazz, ttl)
346 DNSRecord.__init__(self, name, type, clazz, ttl)
347 self.address = address
347 self.address = address
348
348
349 def write(self, out):
349 def write(self, out):
350 """Used in constructing an outgoing packet"""
350 """Used in constructing an outgoing packet"""
351 out.writeString(self.address, len(self.address))
351 out.writeString(self.address, len(self.address))
352
352
353 def __eq__(self, other):
353 def __eq__(self, other):
354 """Tests equality on address"""
354 """Tests equality on address"""
355 if isinstance(other, DNSAddress):
355 if isinstance(other, DNSAddress):
356 return self.address == other.address
356 return self.address == other.address
357 return 0
357 return 0
358
358
359 def __repr__(self):
359 def __repr__(self):
360 """String representation"""
360 """String representation"""
361 try:
361 try:
362 return socket.inet_ntoa(self.address)
362 return socket.inet_ntoa(self.address)
363 except Exception:
363 except Exception:
364 return self.address
364 return self.address
365
365
366 class DNSHinfo(DNSRecord):
366 class DNSHinfo(DNSRecord):
367 """A DNS host information record"""
367 """A DNS host information record"""
368
368
369 def __init__(self, name, type, clazz, ttl, cpu, os):
369 def __init__(self, name, type, clazz, ttl, cpu, os):
370 DNSRecord.__init__(self, name, type, clazz, ttl)
370 DNSRecord.__init__(self, name, type, clazz, ttl)
371 self.cpu = cpu
371 self.cpu = cpu
372 self.os = os
372 self.os = os
373
373
374 def write(self, out):
374 def write(self, out):
375 """Used in constructing an outgoing packet"""
375 """Used in constructing an outgoing packet"""
376 out.writeString(self.cpu, len(self.cpu))
376 out.writeString(self.cpu, len(self.cpu))
377 out.writeString(self.os, len(self.os))
377 out.writeString(self.os, len(self.os))
378
378
379 def __eq__(self, other):
379 def __eq__(self, other):
380 """Tests equality on cpu and os"""
380 """Tests equality on cpu and os"""
381 if isinstance(other, DNSHinfo):
381 if isinstance(other, DNSHinfo):
382 return self.cpu == other.cpu and self.os == other.os
382 return self.cpu == other.cpu and self.os == other.os
383 return 0
383 return 0
384
384
385 def __repr__(self):
385 def __repr__(self):
386 """String representation"""
386 """String representation"""
387 return self.cpu + " " + self.os
387 return self.cpu + " " + self.os
388
388
389 class DNSPointer(DNSRecord):
389 class DNSPointer(DNSRecord):
390 """A DNS pointer record"""
390 """A DNS pointer record"""
391
391
392 def __init__(self, name, type, clazz, ttl, alias):
392 def __init__(self, name, type, clazz, ttl, alias):
393 DNSRecord.__init__(self, name, type, clazz, ttl)
393 DNSRecord.__init__(self, name, type, clazz, ttl)
394 self.alias = alias
394 self.alias = alias
395
395
396 def write(self, out):
396 def write(self, out):
397 """Used in constructing an outgoing packet"""
397 """Used in constructing an outgoing packet"""
398 out.writeName(self.alias)
398 out.writeName(self.alias)
399
399
400 def __eq__(self, other):
400 def __eq__(self, other):
401 """Tests equality on alias"""
401 """Tests equality on alias"""
402 if isinstance(other, DNSPointer):
402 if isinstance(other, DNSPointer):
403 return self.alias == other.alias
403 return self.alias == other.alias
404 return 0
404 return 0
405
405
406 def __repr__(self):
406 def __repr__(self):
407 """String representation"""
407 """String representation"""
408 return self.toString(self.alias)
408 return self.toString(self.alias)
409
409
410 class DNSText(DNSRecord):
410 class DNSText(DNSRecord):
411 """A DNS text record"""
411 """A DNS text record"""
412
412
413 def __init__(self, name, type, clazz, ttl, text):
413 def __init__(self, name, type, clazz, ttl, text):
414 DNSRecord.__init__(self, name, type, clazz, ttl)
414 DNSRecord.__init__(self, name, type, clazz, ttl)
415 self.text = text
415 self.text = text
416
416
417 def write(self, out):
417 def write(self, out):
418 """Used in constructing an outgoing packet"""
418 """Used in constructing an outgoing packet"""
419 out.writeString(self.text, len(self.text))
419 out.writeString(self.text, len(self.text))
420
420
421 def __eq__(self, other):
421 def __eq__(self, other):
422 """Tests equality on text"""
422 """Tests equality on text"""
423 if isinstance(other, DNSText):
423 if isinstance(other, DNSText):
424 return self.text == other.text
424 return self.text == other.text
425 return 0
425 return 0
426
426
427 def __repr__(self):
427 def __repr__(self):
428 """String representation"""
428 """String representation"""
429 if len(self.text) > 10:
429 if len(self.text) > 10:
430 return self.toString(self.text[:7] + "...")
430 return self.toString(self.text[:7] + "...")
431 else:
431 else:
432 return self.toString(self.text)
432 return self.toString(self.text)
433
433
434 class DNSService(DNSRecord):
434 class DNSService(DNSRecord):
435 """A DNS service record"""
435 """A DNS service record"""
436
436
437 def __init__(self, name, type, clazz, ttl, priority, weight, port, server):
437 def __init__(self, name, type, clazz, ttl, priority, weight, port, server):
438 DNSRecord.__init__(self, name, type, clazz, ttl)
438 DNSRecord.__init__(self, name, type, clazz, ttl)
439 self.priority = priority
439 self.priority = priority
440 self.weight = weight
440 self.weight = weight
441 self.port = port
441 self.port = port
442 self.server = server
442 self.server = server
443
443
444 def write(self, out):
444 def write(self, out):
445 """Used in constructing an outgoing packet"""
445 """Used in constructing an outgoing packet"""
446 out.writeShort(self.priority)
446 out.writeShort(self.priority)
447 out.writeShort(self.weight)
447 out.writeShort(self.weight)
448 out.writeShort(self.port)
448 out.writeShort(self.port)
449 out.writeName(self.server)
449 out.writeName(self.server)
450
450
451 def __eq__(self, other):
451 def __eq__(self, other):
452 """Tests equality on priority, weight, port and server"""
452 """Tests equality on priority, weight, port and server"""
453 if isinstance(other, DNSService):
453 if isinstance(other, DNSService):
454 return self.priority == other.priority and self.weight == other.weight and self.port == other.port and self.server == other.server
454 return self.priority == other.priority and self.weight == other.weight and self.port == other.port and self.server == other.server
455 return 0
455 return 0
456
456
457 def __repr__(self):
457 def __repr__(self):
458 """String representation"""
458 """String representation"""
459 return self.toString("%s:%s" % (self.server, self.port))
459 return self.toString("%s:%s" % (self.server, self.port))
460
460
461 class DNSIncoming(object):
461 class DNSIncoming(object):
462 """Object representation of an incoming DNS packet"""
462 """Object representation of an incoming DNS packet"""
463
463
464 def __init__(self, data):
464 def __init__(self, data):
465 """Constructor from string holding bytes of packet"""
465 """Constructor from string holding bytes of packet"""
466 self.offset = 0
466 self.offset = 0
467 self.data = data
467 self.data = data
468 self.questions = []
468 self.questions = []
469 self.answers = []
469 self.answers = []
470 self.numQuestions = 0
470 self.numQuestions = 0
471 self.numAnswers = 0
471 self.numAnswers = 0
472 self.numAuthorities = 0
472 self.numAuthorities = 0
473 self.numAdditionals = 0
473 self.numAdditionals = 0
474
474
475 self.readHeader()
475 self.readHeader()
476 self.readQuestions()
476 self.readQuestions()
477 self.readOthers()
477 self.readOthers()
478
478
479 def readHeader(self):
479 def readHeader(self):
480 """Reads header portion of packet"""
480 """Reads header portion of packet"""
481 format = '!HHHHHH'
481 format = '!HHHHHH'
482 length = struct.calcsize(format)
482 length = struct.calcsize(format)
483 info = struct.unpack(format, self.data[self.offset:self.offset+length])
483 info = struct.unpack(format, self.data[self.offset:self.offset+length])
484 self.offset += length
484 self.offset += length
485
485
486 self.id = info[0]
486 self.id = info[0]
487 self.flags = info[1]
487 self.flags = info[1]
488 self.numQuestions = info[2]
488 self.numQuestions = info[2]
489 self.numAnswers = info[3]
489 self.numAnswers = info[3]
490 self.numAuthorities = info[4]
490 self.numAuthorities = info[4]
491 self.numAdditionals = info[5]
491 self.numAdditionals = info[5]
492
492
493 def readQuestions(self):
493 def readQuestions(self):
494 """Reads questions section of packet"""
494 """Reads questions section of packet"""
495 format = '!HH'
495 format = '!HH'
496 length = struct.calcsize(format)
496 length = struct.calcsize(format)
497 for i in range(0, self.numQuestions):
497 for i in range(0, self.numQuestions):
498 name = self.readName()
498 name = self.readName()
499 info = struct.unpack(format, self.data[self.offset:self.offset+length])
499 info = struct.unpack(format, self.data[self.offset:self.offset+length])
500 self.offset += length
500 self.offset += length
501
501
502 try:
502 try:
503 question = DNSQuestion(name, info[0], info[1])
503 question = DNSQuestion(name, info[0], info[1])
504 self.questions.append(question)
504 self.questions.append(question)
505 except NonLocalNameException:
505 except NonLocalNameException:
506 pass
506 pass
507
507
508 def readInt(self):
508 def readInt(self):
509 """Reads an integer from the packet"""
509 """Reads an integer from the packet"""
510 format = '!I'
510 format = '!I'
511 length = struct.calcsize(format)
511 length = struct.calcsize(format)
512 info = struct.unpack(format, self.data[self.offset:self.offset+length])
512 info = struct.unpack(format, self.data[self.offset:self.offset+length])
513 self.offset += length
513 self.offset += length
514 return info[0]
514 return info[0]
515
515
516 def readCharacterString(self):
516 def readCharacterString(self):
517 """Reads a character string from the packet"""
517 """Reads a character string from the packet"""
518 length = ord(self.data[self.offset])
518 length = ord(self.data[self.offset])
519 self.offset += 1
519 self.offset += 1
520 return self.readString(length)
520 return self.readString(length)
521
521
522 def readString(self, len):
522 def readString(self, len):
523 """Reads a string of a given length from the packet"""
523 """Reads a string of a given length from the packet"""
524 format = '!' + str(len) + 's'
524 format = '!' + str(len) + 's'
525 length = struct.calcsize(format)
525 length = struct.calcsize(format)
526 info = struct.unpack(format, self.data[self.offset:self.offset+length])
526 info = struct.unpack(format, self.data[self.offset:self.offset+length])
527 self.offset += length
527 self.offset += length
528 return info[0]
528 return info[0]
529
529
530 def readUnsignedShort(self):
530 def readUnsignedShort(self):
531 """Reads an unsigned short from the packet"""
531 """Reads an unsigned short from the packet"""
532 format = '!H'
532 format = '!H'
533 length = struct.calcsize(format)
533 length = struct.calcsize(format)
534 info = struct.unpack(format, self.data[self.offset:self.offset+length])
534 info = struct.unpack(format, self.data[self.offset:self.offset+length])
535 self.offset += length
535 self.offset += length
536 return info[0]
536 return info[0]
537
537
538 def readOthers(self):
538 def readOthers(self):
539 """Reads the answers, authorities and additionals section of the packet"""
539 """Reads the answers, authorities and additionals section of the packet"""
540 format = '!HHiH'
540 format = '!HHiH'
541 length = struct.calcsize(format)
541 length = struct.calcsize(format)
542 n = self.numAnswers + self.numAuthorities + self.numAdditionals
542 n = self.numAnswers + self.numAuthorities + self.numAdditionals
543 for i in range(0, n):
543 for i in range(0, n):
544 domain = self.readName()
544 domain = self.readName()
545 info = struct.unpack(format, self.data[self.offset:self.offset+length])
545 info = struct.unpack(format, self.data[self.offset:self.offset+length])
546 self.offset += length
546 self.offset += length
547
547
548 rec = None
548 rec = None
549 if info[0] == _TYPE_A:
549 if info[0] == _TYPE_A:
550 rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(4))
550 rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(4))
551 elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR:
551 elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR:
552 rec = DNSPointer(domain, info[0], info[1], info[2], self.readName())
552 rec = DNSPointer(domain, info[0], info[1], info[2], self.readName())
553 elif info[0] == _TYPE_TXT:
553 elif info[0] == _TYPE_TXT:
554 rec = DNSText(domain, info[0], info[1], info[2], self.readString(info[3]))
554 rec = DNSText(domain, info[0], info[1], info[2], self.readString(info[3]))
555 elif info[0] == _TYPE_SRV:
555 elif info[0] == _TYPE_SRV:
556 rec = DNSService(domain, info[0], info[1], info[2], self.readUnsignedShort(), self.readUnsignedShort(), self.readUnsignedShort(), self.readName())
556 rec = DNSService(domain, info[0], info[1], info[2], self.readUnsignedShort(), self.readUnsignedShort(), self.readUnsignedShort(), self.readName())
557 elif info[0] == _TYPE_HINFO:
557 elif info[0] == _TYPE_HINFO:
558 rec = DNSHinfo(domain, info[0], info[1], info[2], self.readCharacterString(), self.readCharacterString())
558 rec = DNSHinfo(domain, info[0], info[1], info[2], self.readCharacterString(), self.readCharacterString())
559 elif info[0] == _TYPE_AAAA:
559 elif info[0] == _TYPE_AAAA:
560 rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(16))
560 rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(16))
561 else:
561 else:
562 # Try to ignore types we don't know about
562 # Try to ignore types we don't know about
563 # this may mean the rest of the name is
563 # this may mean the rest of the name is
564 # unable to be parsed, and may show errors
564 # unable to be parsed, and may show errors
565 # so this is left for debugging. New types
565 # so this is left for debugging. New types
566 # encountered need to be parsed properly.
566 # encountered need to be parsed properly.
567 #
567 #
568 #print "UNKNOWN TYPE = " + str(info[0])
568 #print "UNKNOWN TYPE = " + str(info[0])
569 #raise BadTypeInNameException
569 #raise BadTypeInNameException
570 self.offset += info[3]
570 self.offset += info[3]
571
571
572 if rec is not None:
572 if rec is not None:
573 self.answers.append(rec)
573 self.answers.append(rec)
574
574
575 def isQuery(self):
575 def isQuery(self):
576 """Returns true if this is a query"""
576 """Returns true if this is a query"""
577 return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY
577 return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY
578
578
579 def isResponse(self):
579 def isResponse(self):
580 """Returns true if this is a response"""
580 """Returns true if this is a response"""
581 return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE
581 return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE
582
582
583 def readUTF(self, offset, len):
583 def readUTF(self, offset, len):
584 """Reads a UTF-8 string of a given length from the packet"""
584 """Reads a UTF-8 string of a given length from the packet"""
585 return self.data[offset:offset+len].decode('utf-8')
585 return self.data[offset:offset+len].decode('utf-8')
586
586
587 def readName(self):
587 def readName(self):
588 """Reads a domain name from the packet"""
588 """Reads a domain name from the packet"""
589 result = ''
589 result = ''
590 off = self.offset
590 off = self.offset
591 next = -1
591 next = -1
592 first = off
592 first = off
593
593
594 while True:
594 while True:
595 len = ord(self.data[off])
595 len = ord(self.data[off])
596 off += 1
596 off += 1
597 if len == 0:
597 if len == 0:
598 break
598 break
599 t = len & 0xC0
599 t = len & 0xC0
600 if t == 0x00:
600 if t == 0x00:
601 result = ''.join((result, self.readUTF(off, len) + '.'))
601 result = ''.join((result, self.readUTF(off, len) + '.'))
602 off += len
602 off += len
603 elif t == 0xC0:
603 elif t == 0xC0:
604 if next < 0:
604 if next < 0:
605 next = off + 1
605 next = off + 1
606 off = ((len & 0x3F) << 8) | ord(self.data[off])
606 off = ((len & 0x3F) << 8) | ord(self.data[off])
607 if off >= first:
607 if off >= first:
608 raise BadDomainNameCircular(off)
608 raise BadDomainNameCircular(off)
609 first = off
609 first = off
610 else:
610 else:
611 raise BadDomainName(off)
611 raise BadDomainName(off)
612
612
613 if next >= 0:
613 if next >= 0:
614 self.offset = next
614 self.offset = next
615 else:
615 else:
616 self.offset = off
616 self.offset = off
617
617
618 return result
618 return result
619
619
620
620
621 class DNSOutgoing(object):
621 class DNSOutgoing(object):
622 """Object representation of an outgoing packet"""
622 """Object representation of an outgoing packet"""
623
623
624 def __init__(self, flags, multicast = 1):
624 def __init__(self, flags, multicast = 1):
625 self.finished = 0
625 self.finished = 0
626 self.id = 0
626 self.id = 0
627 self.multicast = multicast
627 self.multicast = multicast
628 self.flags = flags
628 self.flags = flags
629 self.names = {}
629 self.names = {}
630 self.data = []
630 self.data = []
631 self.size = 12
631 self.size = 12
632
632
633 self.questions = []
633 self.questions = []
634 self.answers = []
634 self.answers = []
635 self.authorities = []
635 self.authorities = []
636 self.additionals = []
636 self.additionals = []
637
637
638 def addQuestion(self, record):
638 def addQuestion(self, record):
639 """Adds a question"""
639 """Adds a question"""
640 self.questions.append(record)
640 self.questions.append(record)
641
641
642 def addAnswer(self, inp, record):
642 def addAnswer(self, inp, record):
643 """Adds an answer"""
643 """Adds an answer"""
644 if not record.suppressedBy(inp):
644 if not record.suppressedBy(inp):
645 self.addAnswerAtTime(record, 0)
645 self.addAnswerAtTime(record, 0)
646
646
647 def addAnswerAtTime(self, record, now):
647 def addAnswerAtTime(self, record, now):
648 """Adds an answer if if does not expire by a certain time"""
648 """Adds an answer if if does not expire by a certain time"""
649 if record is not None:
649 if record is not None:
650 if now == 0 or not record.isExpired(now):
650 if now == 0 or not record.isExpired(now):
651 self.answers.append((record, now))
651 self.answers.append((record, now))
652
652
653 def addAuthorativeAnswer(self, record):
653 def addAuthoritativeAnswer(self, record):
654 """Adds an authoritative answer"""
654 """Adds an authoritative answer"""
655 self.authorities.append(record)
655 self.authorities.append(record)
656
656
657 def addAdditionalAnswer(self, record):
657 def addAdditionalAnswer(self, record):
658 """Adds an additional answer"""
658 """Adds an additional answer"""
659 self.additionals.append(record)
659 self.additionals.append(record)
660
660
661 def writeByte(self, value):
661 def writeByte(self, value):
662 """Writes a single byte to the packet"""
662 """Writes a single byte to the packet"""
663 format = '!c'
663 format = '!c'
664 self.data.append(struct.pack(format, chr(value)))
664 self.data.append(struct.pack(format, chr(value)))
665 self.size += 1
665 self.size += 1
666
666
667 def insertShort(self, index, value):
667 def insertShort(self, index, value):
668 """Inserts an unsigned short in a certain position in the packet"""
668 """Inserts an unsigned short in a certain position in the packet"""
669 format = '!H'
669 format = '!H'
670 self.data.insert(index, struct.pack(format, value))
670 self.data.insert(index, struct.pack(format, value))
671 self.size += 2
671 self.size += 2
672
672
673 def writeShort(self, value):
673 def writeShort(self, value):
674 """Writes an unsigned short to the packet"""
674 """Writes an unsigned short to the packet"""
675 format = '!H'
675 format = '!H'
676 self.data.append(struct.pack(format, value))
676 self.data.append(struct.pack(format, value))
677 self.size += 2
677 self.size += 2
678
678
679 def writeInt(self, value):
679 def writeInt(self, value):
680 """Writes an unsigned integer to the packet"""
680 """Writes an unsigned integer to the packet"""
681 format = '!I'
681 format = '!I'
682 self.data.append(struct.pack(format, int(value)))
682 self.data.append(struct.pack(format, int(value)))
683 self.size += 4
683 self.size += 4
684
684
685 def writeString(self, value, length):
685 def writeString(self, value, length):
686 """Writes a string to the packet"""
686 """Writes a string to the packet"""
687 format = '!' + str(length) + 's'
687 format = '!' + str(length) + 's'
688 self.data.append(struct.pack(format, value))
688 self.data.append(struct.pack(format, value))
689 self.size += length
689 self.size += length
690
690
691 def writeUTF(self, s):
691 def writeUTF(self, s):
692 """Writes a UTF-8 string of a given length to the packet"""
692 """Writes a UTF-8 string of a given length to the packet"""
693 utfstr = s.encode('utf-8')
693 utfstr = s.encode('utf-8')
694 length = len(utfstr)
694 length = len(utfstr)
695 if length > 64:
695 if length > 64:
696 raise NamePartTooLongException
696 raise NamePartTooLongException
697 self.writeByte(length)
697 self.writeByte(length)
698 self.writeString(utfstr, length)
698 self.writeString(utfstr, length)
699
699
700 def writeName(self, name):
700 def writeName(self, name):
701 """Writes a domain name to the packet"""
701 """Writes a domain name to the packet"""
702
702
703 try:
703 try:
704 # Find existing instance of this name in packet
704 # Find existing instance of this name in packet
705 #
705 #
706 index = self.names[name]
706 index = self.names[name]
707 except KeyError:
707 except KeyError:
708 # No record of this name already, so write it
708 # No record of this name already, so write it
709 # out as normal, recording the location of the name
709 # out as normal, recording the location of the name
710 # for future pointers to it.
710 # for future pointers to it.
711 #
711 #
712 self.names[name] = self.size
712 self.names[name] = self.size
713 parts = name.split('.')
713 parts = name.split('.')
714 if parts[-1] == '':
714 if parts[-1] == '':
715 parts = parts[:-1]
715 parts = parts[:-1]
716 for part in parts:
716 for part in parts:
717 self.writeUTF(part)
717 self.writeUTF(part)
718 self.writeByte(0)
718 self.writeByte(0)
719 return
719 return
720
720
721 # An index was found, so write a pointer to it
721 # An index was found, so write a pointer to it
722 #
722 #
723 self.writeByte((index >> 8) | 0xC0)
723 self.writeByte((index >> 8) | 0xC0)
724 self.writeByte(index)
724 self.writeByte(index)
725
725
726 def writeQuestion(self, question):
726 def writeQuestion(self, question):
727 """Writes a question to the packet"""
727 """Writes a question to the packet"""
728 self.writeName(question.name)
728 self.writeName(question.name)
729 self.writeShort(question.type)
729 self.writeShort(question.type)
730 self.writeShort(question.clazz)
730 self.writeShort(question.clazz)
731
731
732 def writeRecord(self, record, now):
732 def writeRecord(self, record, now):
733 """Writes a record (answer, authoritative answer, additional) to
733 """Writes a record (answer, authoritative answer, additional) to
734 the packet"""
734 the packet"""
735 self.writeName(record.name)
735 self.writeName(record.name)
736 self.writeShort(record.type)
736 self.writeShort(record.type)
737 if record.unique and self.multicast:
737 if record.unique and self.multicast:
738 self.writeShort(record.clazz | _CLASS_UNIQUE)
738 self.writeShort(record.clazz | _CLASS_UNIQUE)
739 else:
739 else:
740 self.writeShort(record.clazz)
740 self.writeShort(record.clazz)
741 if now == 0:
741 if now == 0:
742 self.writeInt(record.ttl)
742 self.writeInt(record.ttl)
743 else:
743 else:
744 self.writeInt(record.getRemainingTTL(now))
744 self.writeInt(record.getRemainingTTL(now))
745 index = len(self.data)
745 index = len(self.data)
746 # Adjust size for the short we will write before this record
746 # Adjust size for the short we will write before this record
747 #
747 #
748 self.size += 2
748 self.size += 2
749 record.write(self)
749 record.write(self)
750 self.size -= 2
750 self.size -= 2
751
751
752 length = len(''.join(self.data[index:]))
752 length = len(''.join(self.data[index:]))
753 self.insertShort(index, length) # Here is the short we adjusted for
753 self.insertShort(index, length) # Here is the short we adjusted for
754
754
755 def packet(self):
755 def packet(self):
756 """Returns a string containing the packet's bytes
756 """Returns a string containing the packet's bytes
757
757
758 No further parts should be added to the packet once this
758 No further parts should be added to the packet once this
759 is done."""
759 is done."""
760 if not self.finished:
760 if not self.finished:
761 self.finished = 1
761 self.finished = 1
762 for question in self.questions:
762 for question in self.questions:
763 self.writeQuestion(question)
763 self.writeQuestion(question)
764 for answer, time in self.answers:
764 for answer, time in self.answers:
765 self.writeRecord(answer, time)
765 self.writeRecord(answer, time)
766 for authority in self.authorities:
766 for authority in self.authorities:
767 self.writeRecord(authority, 0)
767 self.writeRecord(authority, 0)
768 for additional in self.additionals:
768 for additional in self.additionals:
769 self.writeRecord(additional, 0)
769 self.writeRecord(additional, 0)
770
770
771 self.insertShort(0, len(self.additionals))
771 self.insertShort(0, len(self.additionals))
772 self.insertShort(0, len(self.authorities))
772 self.insertShort(0, len(self.authorities))
773 self.insertShort(0, len(self.answers))
773 self.insertShort(0, len(self.answers))
774 self.insertShort(0, len(self.questions))
774 self.insertShort(0, len(self.questions))
775 self.insertShort(0, self.flags)
775 self.insertShort(0, self.flags)
776 if self.multicast:
776 if self.multicast:
777 self.insertShort(0, 0)
777 self.insertShort(0, 0)
778 else:
778 else:
779 self.insertShort(0, self.id)
779 self.insertShort(0, self.id)
780 return ''.join(self.data)
780 return ''.join(self.data)
781
781
782
782
783 class DNSCache(object):
783 class DNSCache(object):
784 """A cache of DNS entries"""
784 """A cache of DNS entries"""
785
785
786 def __init__(self):
786 def __init__(self):
787 self.cache = {}
787 self.cache = {}
788
788
789 def add(self, entry):
789 def add(self, entry):
790 """Adds an entry"""
790 """Adds an entry"""
791 try:
791 try:
792 list = self.cache[entry.key]
792 list = self.cache[entry.key]
793 except KeyError:
793 except KeyError:
794 list = self.cache[entry.key] = []
794 list = self.cache[entry.key] = []
795 list.append(entry)
795 list.append(entry)
796
796
797 def remove(self, entry):
797 def remove(self, entry):
798 """Removes an entry"""
798 """Removes an entry"""
799 try:
799 try:
800 list = self.cache[entry.key]
800 list = self.cache[entry.key]
801 list.remove(entry)
801 list.remove(entry)
802 except KeyError:
802 except KeyError:
803 pass
803 pass
804
804
805 def get(self, entry):
805 def get(self, entry):
806 """Gets an entry by key. Will return None if there is no
806 """Gets an entry by key. Will return None if there is no
807 matching entry."""
807 matching entry."""
808 try:
808 try:
809 list = self.cache[entry.key]
809 list = self.cache[entry.key]
810 return list[list.index(entry)]
810 return list[list.index(entry)]
811 except (KeyError, ValueError):
811 except (KeyError, ValueError):
812 return None
812 return None
813
813
814 def getByDetails(self, name, type, clazz):
814 def getByDetails(self, name, type, clazz):
815 """Gets an entry by details. Will return None if there is
815 """Gets an entry by details. Will return None if there is
816 no matching entry."""
816 no matching entry."""
817 entry = DNSEntry(name, type, clazz)
817 entry = DNSEntry(name, type, clazz)
818 return self.get(entry)
818 return self.get(entry)
819
819
820 def entriesWithName(self, name):
820 def entriesWithName(self, name):
821 """Returns a list of entries whose key matches the name."""
821 """Returns a list of entries whose key matches the name."""
822 try:
822 try:
823 return self.cache[name]
823 return self.cache[name]
824 except KeyError:
824 except KeyError:
825 return []
825 return []
826
826
827 def entries(self):
827 def entries(self):
828 """Returns a list of all entries"""
828 """Returns a list of all entries"""
829 def add(x, y): return x+y
829 def add(x, y): return x+y
830 try:
830 try:
831 return reduce(add, self.cache.values())
831 return reduce(add, self.cache.values())
832 except Exception:
832 except Exception:
833 return []
833 return []
834
834
835
835
836 class Engine(threading.Thread):
836 class Engine(threading.Thread):
837 """An engine wraps read access to sockets, allowing objects that
837 """An engine wraps read access to sockets, allowing objects that
838 need to receive data from sockets to be called back when the
838 need to receive data from sockets to be called back when the
839 sockets are ready.
839 sockets are ready.
840
840
841 A reader needs a handle_read() method, which is called when the socket
841 A reader needs a handle_read() method, which is called when the socket
842 it is interested in is ready for reading.
842 it is interested in is ready for reading.
843
843
844 Writers are not implemented here, because we only send short
844 Writers are not implemented here, because we only send short
845 packets.
845 packets.
846 """
846 """
847
847
848 def __init__(self, zeroconf):
848 def __init__(self, zeroconf):
849 threading.Thread.__init__(self)
849 threading.Thread.__init__(self)
850 self.zeroconf = zeroconf
850 self.zeroconf = zeroconf
851 self.readers = {} # maps socket to reader
851 self.readers = {} # maps socket to reader
852 self.timeout = 5
852 self.timeout = 5
853 self.condition = threading.Condition()
853 self.condition = threading.Condition()
854 self.start()
854 self.start()
855
855
856 def run(self):
856 def run(self):
857 while not globals()['_GLOBAL_DONE']:
857 while not globals()['_GLOBAL_DONE']:
858 rs = self.getReaders()
858 rs = self.getReaders()
859 if len(rs) == 0:
859 if len(rs) == 0:
860 # No sockets to manage, but we wait for the timeout
860 # No sockets to manage, but we wait for the timeout
861 # or addition of a socket
861 # or addition of a socket
862 #
862 #
863 self.condition.acquire()
863 self.condition.acquire()
864 self.condition.wait(self.timeout)
864 self.condition.wait(self.timeout)
865 self.condition.release()
865 self.condition.release()
866 else:
866 else:
867 try:
867 try:
868 rr, wr, er = select.select(rs, [], [], self.timeout)
868 rr, wr, er = select.select(rs, [], [], self.timeout)
869 for socket in rr:
869 for socket in rr:
870 try:
870 try:
871 self.readers[socket].handle_read()
871 self.readers[socket].handle_read()
872 except Exception:
872 except Exception:
873 if not globals()['_GLOBAL_DONE']:
873 if not globals()['_GLOBAL_DONE']:
874 traceback.print_exc()
874 traceback.print_exc()
875 except Exception:
875 except Exception:
876 pass
876 pass
877
877
878 def getReaders(self):
878 def getReaders(self):
879 self.condition.acquire()
879 self.condition.acquire()
880 result = self.readers.keys()
880 result = self.readers.keys()
881 self.condition.release()
881 self.condition.release()
882 return result
882 return result
883
883
884 def addReader(self, reader, socket):
884 def addReader(self, reader, socket):
885 self.condition.acquire()
885 self.condition.acquire()
886 self.readers[socket] = reader
886 self.readers[socket] = reader
887 self.condition.notify()
887 self.condition.notify()
888 self.condition.release()
888 self.condition.release()
889
889
890 def delReader(self, socket):
890 def delReader(self, socket):
891 self.condition.acquire()
891 self.condition.acquire()
892 del(self.readers[socket])
892 del(self.readers[socket])
893 self.condition.notify()
893 self.condition.notify()
894 self.condition.release()
894 self.condition.release()
895
895
896 def notify(self):
896 def notify(self):
897 self.condition.acquire()
897 self.condition.acquire()
898 self.condition.notify()
898 self.condition.notify()
899 self.condition.release()
899 self.condition.release()
900
900
901 class Listener(object):
901 class Listener(object):
902 """A Listener is used by this module to listen on the multicast
902 """A Listener is used by this module to listen on the multicast
903 group to which DNS messages are sent, allowing the implementation
903 group to which DNS messages are sent, allowing the implementation
904 to cache information as it arrives.
904 to cache information as it arrives.
905
905
906 It requires registration with an Engine object in order to have
906 It requires registration with an Engine object in order to have
907 the read() method called when a socket is available for reading."""
907 the read() method called when a socket is available for reading."""
908
908
909 def __init__(self, zeroconf):
909 def __init__(self, zeroconf):
910 self.zeroconf = zeroconf
910 self.zeroconf = zeroconf
911 self.zeroconf.engine.addReader(self, self.zeroconf.socket)
911 self.zeroconf.engine.addReader(self, self.zeroconf.socket)
912
912
913 def handle_read(self):
913 def handle_read(self):
914 data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE)
914 data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE)
915 self.data = data
915 self.data = data
916 msg = DNSIncoming(data)
916 msg = DNSIncoming(data)
917 if msg.isQuery():
917 if msg.isQuery():
918 # Always multicast responses
918 # Always multicast responses
919 #
919 #
920 if port == _MDNS_PORT:
920 if port == _MDNS_PORT:
921 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
921 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
922 # If it's not a multicast query, reply via unicast
922 # If it's not a multicast query, reply via unicast
923 # and multicast
923 # and multicast
924 #
924 #
925 elif port == _DNS_PORT:
925 elif port == _DNS_PORT:
926 self.zeroconf.handleQuery(msg, addr, port)
926 self.zeroconf.handleQuery(msg, addr, port)
927 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
927 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
928 else:
928 else:
929 self.zeroconf.handleResponse(msg)
929 self.zeroconf.handleResponse(msg)
930
930
931
931
932 class Reaper(threading.Thread):
932 class Reaper(threading.Thread):
933 """A Reaper is used by this module to remove cache entries that
933 """A Reaper is used by this module to remove cache entries that
934 have expired."""
934 have expired."""
935
935
936 def __init__(self, zeroconf):
936 def __init__(self, zeroconf):
937 threading.Thread.__init__(self)
937 threading.Thread.__init__(self)
938 self.zeroconf = zeroconf
938 self.zeroconf = zeroconf
939 self.start()
939 self.start()
940
940
941 def run(self):
941 def run(self):
942 while True:
942 while True:
943 self.zeroconf.wait(10 * 1000)
943 self.zeroconf.wait(10 * 1000)
944 if globals()['_GLOBAL_DONE']:
944 if globals()['_GLOBAL_DONE']:
945 return
945 return
946 now = currentTimeMillis()
946 now = currentTimeMillis()
947 for record in self.zeroconf.cache.entries():
947 for record in self.zeroconf.cache.entries():
948 if record.isExpired(now):
948 if record.isExpired(now):
949 self.zeroconf.updateRecord(now, record)
949 self.zeroconf.updateRecord(now, record)
950 self.zeroconf.cache.remove(record)
950 self.zeroconf.cache.remove(record)
951
951
952
952
953 class ServiceBrowser(threading.Thread):
953 class ServiceBrowser(threading.Thread):
954 """Used to browse for a service of a specific type.
954 """Used to browse for a service of a specific type.
955
955
956 The listener object will have its addService() and
956 The listener object will have its addService() and
957 removeService() methods called when this browser
957 removeService() methods called when this browser
958 discovers changes in the services availability."""
958 discovers changes in the services availability."""
959
959
960 def __init__(self, zeroconf, type, listener):
960 def __init__(self, zeroconf, type, listener):
961 """Creates a browser for a specific type"""
961 """Creates a browser for a specific type"""
962 threading.Thread.__init__(self)
962 threading.Thread.__init__(self)
963 self.zeroconf = zeroconf
963 self.zeroconf = zeroconf
964 self.type = type
964 self.type = type
965 self.listener = listener
965 self.listener = listener
966 self.services = {}
966 self.services = {}
967 self.nextTime = currentTimeMillis()
967 self.nextTime = currentTimeMillis()
968 self.delay = _BROWSER_TIME
968 self.delay = _BROWSER_TIME
969 self.list = []
969 self.list = []
970
970
971 self.done = 0
971 self.done = 0
972
972
973 self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
973 self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
974 self.start()
974 self.start()
975
975
976 def updateRecord(self, zeroconf, now, record):
976 def updateRecord(self, zeroconf, now, record):
977 """Callback invoked by Zeroconf when new information arrives.
977 """Callback invoked by Zeroconf when new information arrives.
978
978
979 Updates information required by browser in the Zeroconf cache."""
979 Updates information required by browser in the Zeroconf cache."""
980 if record.type == _TYPE_PTR and record.name == self.type:
980 if record.type == _TYPE_PTR and record.name == self.type:
981 expired = record.isExpired(now)
981 expired = record.isExpired(now)
982 try:
982 try:
983 oldrecord = self.services[record.alias.lower()]
983 oldrecord = self.services[record.alias.lower()]
984 if not expired:
984 if not expired:
985 oldrecord.resetTTL(record)
985 oldrecord.resetTTL(record)
986 else:
986 else:
987 del(self.services[record.alias.lower()])
987 del(self.services[record.alias.lower()])
988 callback = lambda x: self.listener.removeService(x, self.type, record.alias)
988 callback = lambda x: self.listener.removeService(x, self.type, record.alias)
989 self.list.append(callback)
989 self.list.append(callback)
990 return
990 return
991 except Exception:
991 except Exception:
992 if not expired:
992 if not expired:
993 self.services[record.alias.lower()] = record
993 self.services[record.alias.lower()] = record
994 callback = lambda x: self.listener.addService(x, self.type, record.alias)
994 callback = lambda x: self.listener.addService(x, self.type, record.alias)
995 self.list.append(callback)
995 self.list.append(callback)
996
996
997 expires = record.getExpirationTime(75)
997 expires = record.getExpirationTime(75)
998 if expires < self.nextTime:
998 if expires < self.nextTime:
999 self.nextTime = expires
999 self.nextTime = expires
1000
1000
1001 def cancel(self):
1001 def cancel(self):
1002 self.done = 1
1002 self.done = 1
1003 self.zeroconf.notifyAll()
1003 self.zeroconf.notifyAll()
1004
1004
1005 def run(self):
1005 def run(self):
1006 while True:
1006 while True:
1007 event = None
1007 event = None
1008 now = currentTimeMillis()
1008 now = currentTimeMillis()
1009 if len(self.list) == 0 and self.nextTime > now:
1009 if len(self.list) == 0 and self.nextTime > now:
1010 self.zeroconf.wait(self.nextTime - now)
1010 self.zeroconf.wait(self.nextTime - now)
1011 if globals()['_GLOBAL_DONE'] or self.done:
1011 if globals()['_GLOBAL_DONE'] or self.done:
1012 return
1012 return
1013 now = currentTimeMillis()
1013 now = currentTimeMillis()
1014
1014
1015 if self.nextTime <= now:
1015 if self.nextTime <= now:
1016 out = DNSOutgoing(_FLAGS_QR_QUERY)
1016 out = DNSOutgoing(_FLAGS_QR_QUERY)
1017 out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
1017 out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
1018 for record in self.services.values():
1018 for record in self.services.values():
1019 if not record.isExpired(now):
1019 if not record.isExpired(now):
1020 out.addAnswerAtTime(record, now)
1020 out.addAnswerAtTime(record, now)
1021 self.zeroconf.send(out)
1021 self.zeroconf.send(out)
1022 self.nextTime = now + self.delay
1022 self.nextTime = now + self.delay
1023 self.delay = min(20 * 1000, self.delay * 2)
1023 self.delay = min(20 * 1000, self.delay * 2)
1024
1024
1025 if len(self.list) > 0:
1025 if len(self.list) > 0:
1026 event = self.list.pop(0)
1026 event = self.list.pop(0)
1027
1027
1028 if event is not None:
1028 if event is not None:
1029 event(self.zeroconf)
1029 event(self.zeroconf)
1030
1030
1031
1031
1032 class ServiceInfo(object):
1032 class ServiceInfo(object):
1033 """Service information"""
1033 """Service information"""
1034
1034
1035 def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None):
1035 def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None):
1036 """Create a service description.
1036 """Create a service description.
1037
1037
1038 type: fully qualified service type name
1038 type: fully qualified service type name
1039 name: fully qualified service name
1039 name: fully qualified service name
1040 address: IP address as unsigned short, network byte order
1040 address: IP address as unsigned short, network byte order
1041 port: port that the service runs on
1041 port: port that the service runs on
1042 weight: weight of the service
1042 weight: weight of the service
1043 priority: priority of the service
1043 priority: priority of the service
1044 properties: dictionary of properties (or a string holding the bytes for the text field)
1044 properties: dictionary of properties (or a string holding the bytes for the text field)
1045 server: fully qualified name for service host (defaults to name)"""
1045 server: fully qualified name for service host (defaults to name)"""
1046
1046
1047 if not name.endswith(type):
1047 if not name.endswith(type):
1048 raise BadTypeInNameException
1048 raise BadTypeInNameException
1049 self.type = type
1049 self.type = type
1050 self.name = name
1050 self.name = name
1051 self.address = address
1051 self.address = address
1052 self.port = port
1052 self.port = port
1053 self.weight = weight
1053 self.weight = weight
1054 self.priority = priority
1054 self.priority = priority
1055 if server:
1055 if server:
1056 self.server = server
1056 self.server = server
1057 else:
1057 else:
1058 self.server = name
1058 self.server = name
1059 self.setProperties(properties)
1059 self.setProperties(properties)
1060
1060
1061 def setProperties(self, properties):
1061 def setProperties(self, properties):
1062 """Sets properties and text of this info from a dictionary"""
1062 """Sets properties and text of this info from a dictionary"""
1063 if isinstance(properties, dict):
1063 if isinstance(properties, dict):
1064 self.properties = properties
1064 self.properties = properties
1065 list = []
1065 list = []
1066 result = ''
1066 result = ''
1067 for key in properties:
1067 for key in properties:
1068 value = properties[key]
1068 value = properties[key]
1069 if value is None:
1069 if value is None:
1070 suffix = ''
1070 suffix = ''
1071 elif isinstance(value, str):
1071 elif isinstance(value, str):
1072 suffix = value
1072 suffix = value
1073 elif isinstance(value, int):
1073 elif isinstance(value, int):
1074 if value:
1074 if value:
1075 suffix = 'true'
1075 suffix = 'true'
1076 else:
1076 else:
1077 suffix = 'false'
1077 suffix = 'false'
1078 else:
1078 else:
1079 suffix = ''
1079 suffix = ''
1080 list.append('='.join((key, suffix)))
1080 list.append('='.join((key, suffix)))
1081 for item in list:
1081 for item in list:
1082 result = ''.join((result, struct.pack('!c', chr(len(item))), item))
1082 result = ''.join((result, struct.pack('!c', chr(len(item))), item))
1083 self.text = result
1083 self.text = result
1084 else:
1084 else:
1085 self.text = properties
1085 self.text = properties
1086
1086
1087 def setText(self, text):
1087 def setText(self, text):
1088 """Sets properties and text given a text field"""
1088 """Sets properties and text given a text field"""
1089 self.text = text
1089 self.text = text
1090 try:
1090 try:
1091 result = {}
1091 result = {}
1092 end = len(text)
1092 end = len(text)
1093 index = 0
1093 index = 0
1094 strs = []
1094 strs = []
1095 while index < end:
1095 while index < end:
1096 length = ord(text[index])
1096 length = ord(text[index])
1097 index += 1
1097 index += 1
1098 strs.append(text[index:index+length])
1098 strs.append(text[index:index+length])
1099 index += length
1099 index += length
1100
1100
1101 for s in strs:
1101 for s in strs:
1102 eindex = s.find('=')
1102 eindex = s.find('=')
1103 if eindex == -1:
1103 if eindex == -1:
1104 # No equals sign at all
1104 # No equals sign at all
1105 key = s
1105 key = s
1106 value = 0
1106 value = 0
1107 else:
1107 else:
1108 key = s[:eindex]
1108 key = s[:eindex]
1109 value = s[eindex+1:]
1109 value = s[eindex+1:]
1110 if value == 'true':
1110 if value == 'true':
1111 value = 1
1111 value = 1
1112 elif value == 'false' or not value:
1112 elif value == 'false' or not value:
1113 value = 0
1113 value = 0
1114
1114
1115 # Only update non-existent properties
1115 # Only update non-existent properties
1116 if key and result.get(key) == None:
1116 if key and result.get(key) == None:
1117 result[key] = value
1117 result[key] = value
1118
1118
1119 self.properties = result
1119 self.properties = result
1120 except Exception:
1120 except Exception:
1121 traceback.print_exc()
1121 traceback.print_exc()
1122 self.properties = None
1122 self.properties = None
1123
1123
1124 def getType(self):
1124 def getType(self):
1125 """Type accessor"""
1125 """Type accessor"""
1126 return self.type
1126 return self.type
1127
1127
1128 def getName(self):
1128 def getName(self):
1129 """Name accessor"""
1129 """Name accessor"""
1130 if self.type is not None and self.name.endswith("." + self.type):
1130 if self.type is not None and self.name.endswith("." + self.type):
1131 return self.name[:len(self.name) - len(self.type) - 1]
1131 return self.name[:len(self.name) - len(self.type) - 1]
1132 return self.name
1132 return self.name
1133
1133
1134 def getAddress(self):
1134 def getAddress(self):
1135 """Address accessor"""
1135 """Address accessor"""
1136 return self.address
1136 return self.address
1137
1137
1138 def getPort(self):
1138 def getPort(self):
1139 """Port accessor"""
1139 """Port accessor"""
1140 return self.port
1140 return self.port
1141
1141
1142 def getPriority(self):
1142 def getPriority(self):
1143 """Priority accessor"""
1143 """Priority accessor"""
1144 return self.priority
1144 return self.priority
1145
1145
1146 def getWeight(self):
1146 def getWeight(self):
1147 """Weight accessor"""
1147 """Weight accessor"""
1148 return self.weight
1148 return self.weight
1149
1149
1150 def getProperties(self):
1150 def getProperties(self):
1151 """Properties accessor"""
1151 """Properties accessor"""
1152 return self.properties
1152 return self.properties
1153
1153
1154 def getText(self):
1154 def getText(self):
1155 """Text accessor"""
1155 """Text accessor"""
1156 return self.text
1156 return self.text
1157
1157
1158 def getServer(self):
1158 def getServer(self):
1159 """Server accessor"""
1159 """Server accessor"""
1160 return self.server
1160 return self.server
1161
1161
1162 def updateRecord(self, zeroconf, now, record):
1162 def updateRecord(self, zeroconf, now, record):
1163 """Updates service information from a DNS record"""
1163 """Updates service information from a DNS record"""
1164 if record is not None and not record.isExpired(now):
1164 if record is not None and not record.isExpired(now):
1165 if record.type == _TYPE_A:
1165 if record.type == _TYPE_A:
1166 #if record.name == self.name:
1166 #if record.name == self.name:
1167 if record.name == self.server:
1167 if record.name == self.server:
1168 self.address = record.address
1168 self.address = record.address
1169 elif record.type == _TYPE_SRV:
1169 elif record.type == _TYPE_SRV:
1170 if record.name == self.name:
1170 if record.name == self.name:
1171 self.server = record.server
1171 self.server = record.server
1172 self.port = record.port
1172 self.port = record.port
1173 self.weight = record.weight
1173 self.weight = record.weight
1174 self.priority = record.priority
1174 self.priority = record.priority
1175 #self.address = None
1175 #self.address = None
1176 self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN))
1176 self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN))
1177 elif record.type == _TYPE_TXT:
1177 elif record.type == _TYPE_TXT:
1178 if record.name == self.name:
1178 if record.name == self.name:
1179 self.setText(record.text)
1179 self.setText(record.text)
1180
1180
1181 def request(self, zeroconf, timeout):
1181 def request(self, zeroconf, timeout):
1182 """Returns true if the service could be discovered on the
1182 """Returns true if the service could be discovered on the
1183 network, and updates this object with details discovered.
1183 network, and updates this object with details discovered.
1184 """
1184 """
1185 now = currentTimeMillis()
1185 now = currentTimeMillis()
1186 delay = _LISTENER_TIME
1186 delay = _LISTENER_TIME
1187 next = now + delay
1187 next = now + delay
1188 last = now + timeout
1188 last = now + timeout
1189 result = 0
1189 result = 0
1190 try:
1190 try:
1191 zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN))
1191 zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN))
1192 while self.server is None or self.address is None or self.text is None:
1192 while self.server is None or self.address is None or self.text is None:
1193 if last <= now:
1193 if last <= now:
1194 return 0
1194 return 0
1195 if next <= now:
1195 if next <= now:
1196 out = DNSOutgoing(_FLAGS_QR_QUERY)
1196 out = DNSOutgoing(_FLAGS_QR_QUERY)
1197 out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN))
1197 out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN))
1198 out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now)
1198 out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now)
1199 out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN))
1199 out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN))
1200 out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now)
1200 out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now)
1201 if self.server is not None:
1201 if self.server is not None:
1202 out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN))
1202 out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN))
1203 out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now)
1203 out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now)
1204 zeroconf.send(out)
1204 zeroconf.send(out)
1205 next = now + delay
1205 next = now + delay
1206 delay = delay * 2
1206 delay = delay * 2
1207
1207
1208 zeroconf.wait(min(next, last) - now)
1208 zeroconf.wait(min(next, last) - now)
1209 now = currentTimeMillis()
1209 now = currentTimeMillis()
1210 result = 1
1210 result = 1
1211 finally:
1211 finally:
1212 zeroconf.removeListener(self)
1212 zeroconf.removeListener(self)
1213
1213
1214 return result
1214 return result
1215
1215
1216 def __eq__(self, other):
1216 def __eq__(self, other):
1217 """Tests equality of service name"""
1217 """Tests equality of service name"""
1218 if isinstance(other, ServiceInfo):
1218 if isinstance(other, ServiceInfo):
1219 return other.name == self.name
1219 return other.name == self.name
1220 return 0
1220 return 0
1221
1221
1222 def __ne__(self, other):
1222 def __ne__(self, other):
1223 """Non-equality test"""
1223 """Non-equality test"""
1224 return not self.__eq__(other)
1224 return not self.__eq__(other)
1225
1225
1226 def __repr__(self):
1226 def __repr__(self):
1227 """String representation"""
1227 """String representation"""
1228 result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port)
1228 result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port)
1229 if self.text is None:
1229 if self.text is None:
1230 result += "None"
1230 result += "None"
1231 else:
1231 else:
1232 if len(self.text) < 20:
1232 if len(self.text) < 20:
1233 result += self.text
1233 result += self.text
1234 else:
1234 else:
1235 result += self.text[:17] + "..."
1235 result += self.text[:17] + "..."
1236 result += "]"
1236 result += "]"
1237 return result
1237 return result
1238
1238
1239
1239
1240 class Zeroconf(object):
1240 class Zeroconf(object):
1241 """Implementation of Zeroconf Multicast DNS Service Discovery
1241 """Implementation of Zeroconf Multicast DNS Service Discovery
1242
1242
1243 Supports registration, unregistration, queries and browsing.
1243 Supports registration, unregistration, queries and browsing.
1244 """
1244 """
1245 def __init__(self, bindaddress=None):
1245 def __init__(self, bindaddress=None):
1246 """Creates an instance of the Zeroconf class, establishing
1246 """Creates an instance of the Zeroconf class, establishing
1247 multicast communications, listening and reaping threads."""
1247 multicast communications, listening and reaping threads."""
1248 globals()['_GLOBAL_DONE'] = 0
1248 globals()['_GLOBAL_DONE'] = 0
1249 if bindaddress is None:
1249 if bindaddress is None:
1250 self.intf = socket.gethostbyname(socket.gethostname())
1250 self.intf = socket.gethostbyname(socket.gethostname())
1251 else:
1251 else:
1252 self.intf = bindaddress
1252 self.intf = bindaddress
1253 self.group = ('', _MDNS_PORT)
1253 self.group = ('', _MDNS_PORT)
1254 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1254 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1255 try:
1255 try:
1256 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1256 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1257 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
1257 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
1258 except Exception:
1258 except Exception:
1259 # SO_REUSEADDR should be equivalent to SO_REUSEPORT for
1259 # SO_REUSEADDR should be equivalent to SO_REUSEPORT for
1260 # multicast UDP sockets (p 731, "TCP/IP Illustrated,
1260 # multicast UDP sockets (p 731, "TCP/IP Illustrated,
1261 # Volume 2"), but some BSD-derived systems require
1261 # Volume 2"), but some BSD-derived systems require
1262 # SO_REUSEPORT to be specified explicitly. Also, not all
1262 # SO_REUSEPORT to be specified explicitly. Also, not all
1263 # versions of Python have SO_REUSEPORT available. So
1263 # versions of Python have SO_REUSEPORT available. So
1264 # if you're on a BSD-based system, and haven't upgraded
1264 # if you're on a BSD-based system, and haven't upgraded
1265 # to Python 2.3 yet, you may find this library doesn't
1265 # to Python 2.3 yet, you may find this library doesn't
1266 # work as expected.
1266 # work as expected.
1267 #
1267 #
1268 pass
1268 pass
1269 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255)
1269 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255)
1270 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
1270 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
1271 try:
1271 try:
1272 self.socket.bind(self.group)
1272 self.socket.bind(self.group)
1273 except Exception:
1273 except Exception:
1274 # Some versions of linux raise an exception even though
1274 # Some versions of linux raise an exception even though
1275 # the SO_REUSE* options have been set, so ignore it
1275 # the SO_REUSE* options have been set, so ignore it
1276 #
1276 #
1277 pass
1277 pass
1278 #self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.intf) + socket.inet_aton('0.0.0.0'))
1278 #self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.intf) + socket.inet_aton('0.0.0.0'))
1279 self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
1279 self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
1280
1280
1281 self.listeners = []
1281 self.listeners = []
1282 self.browsers = []
1282 self.browsers = []
1283 self.services = {}
1283 self.services = {}
1284 self.servicetypes = {}
1284 self.servicetypes = {}
1285
1285
1286 self.cache = DNSCache()
1286 self.cache = DNSCache()
1287
1287
1288 self.condition = threading.Condition()
1288 self.condition = threading.Condition()
1289
1289
1290 self.engine = Engine(self)
1290 self.engine = Engine(self)
1291 self.listener = Listener(self)
1291 self.listener = Listener(self)
1292 self.reaper = Reaper(self)
1292 self.reaper = Reaper(self)
1293
1293
1294 def isLoopback(self):
1294 def isLoopback(self):
1295 return self.intf.startswith("127.0.0.1")
1295 return self.intf.startswith("127.0.0.1")
1296
1296
1297 def isLinklocal(self):
1297 def isLinklocal(self):
1298 return self.intf.startswith("169.254.")
1298 return self.intf.startswith("169.254.")
1299
1299
1300 def wait(self, timeout):
1300 def wait(self, timeout):
1301 """Calling thread waits for a given number of milliseconds or
1301 """Calling thread waits for a given number of milliseconds or
1302 until notified."""
1302 until notified."""
1303 self.condition.acquire()
1303 self.condition.acquire()
1304 self.condition.wait(timeout/1000)
1304 self.condition.wait(timeout/1000)
1305 self.condition.release()
1305 self.condition.release()
1306
1306
1307 def notifyAll(self):
1307 def notifyAll(self):
1308 """Notifies all waiting threads"""
1308 """Notifies all waiting threads"""
1309 self.condition.acquire()
1309 self.condition.acquire()
1310 self.condition.notifyAll()
1310 self.condition.notifyAll()
1311 self.condition.release()
1311 self.condition.release()
1312
1312
1313 def getServiceInfo(self, type, name, timeout=3000):
1313 def getServiceInfo(self, type, name, timeout=3000):
1314 """Returns network's service information for a particular
1314 """Returns network's service information for a particular
1315 name and type, or None if no service matches by the timeout,
1315 name and type, or None if no service matches by the timeout,
1316 which defaults to 3 seconds."""
1316 which defaults to 3 seconds."""
1317 info = ServiceInfo(type, name)
1317 info = ServiceInfo(type, name)
1318 if info.request(self, timeout):
1318 if info.request(self, timeout):
1319 return info
1319 return info
1320 return None
1320 return None
1321
1321
1322 def addServiceListener(self, type, listener):
1322 def addServiceListener(self, type, listener):
1323 """Adds a listener for a particular service type. This object
1323 """Adds a listener for a particular service type. This object
1324 will then have its updateRecord method called when information
1324 will then have its updateRecord method called when information
1325 arrives for that type."""
1325 arrives for that type."""
1326 self.removeServiceListener(listener)
1326 self.removeServiceListener(listener)
1327 self.browsers.append(ServiceBrowser(self, type, listener))
1327 self.browsers.append(ServiceBrowser(self, type, listener))
1328
1328
1329 def removeServiceListener(self, listener):
1329 def removeServiceListener(self, listener):
1330 """Removes a listener from the set that is currently listening."""
1330 """Removes a listener from the set that is currently listening."""
1331 for browser in self.browsers:
1331 for browser in self.browsers:
1332 if browser.listener == listener:
1332 if browser.listener == listener:
1333 browser.cancel()
1333 browser.cancel()
1334 del(browser)
1334 del(browser)
1335
1335
1336 def registerService(self, info, ttl=_DNS_TTL):
1336 def registerService(self, info, ttl=_DNS_TTL):
1337 """Registers service information to the network with a default TTL
1337 """Registers service information to the network with a default TTL
1338 of 60 seconds. Zeroconf will then respond to requests for
1338 of 60 seconds. Zeroconf will then respond to requests for
1339 information for that service. The name of the service may be
1339 information for that service. The name of the service may be
1340 changed if needed to make it unique on the network."""
1340 changed if needed to make it unique on the network."""
1341 self.checkService(info)
1341 self.checkService(info)
1342 self.services[info.name.lower()] = info
1342 self.services[info.name.lower()] = info
1343 if self.servicetypes.has_key(info.type):
1343 if self.servicetypes.has_key(info.type):
1344 self.servicetypes[info.type]+=1
1344 self.servicetypes[info.type]+=1
1345 else:
1345 else:
1346 self.servicetypes[info.type]=1
1346 self.servicetypes[info.type]=1
1347 now = currentTimeMillis()
1347 now = currentTimeMillis()
1348 nextTime = now
1348 nextTime = now
1349 i = 0
1349 i = 0
1350 while i < 3:
1350 while i < 3:
1351 if now < nextTime:
1351 if now < nextTime:
1352 self.wait(nextTime - now)
1352 self.wait(nextTime - now)
1353 now = currentTimeMillis()
1353 now = currentTimeMillis()
1354 continue
1354 continue
1355 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1355 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1356 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0)
1356 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0)
1357 out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0)
1357 out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0)
1358 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0)
1358 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0)
1359 if info.address:
1359 if info.address:
1360 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0)
1360 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0)
1361 self.send(out)
1361 self.send(out)
1362 i += 1
1362 i += 1
1363 nextTime += _REGISTER_TIME
1363 nextTime += _REGISTER_TIME
1364
1364
1365 def unregisterService(self, info):
1365 def unregisterService(self, info):
1366 """Unregister a service."""
1366 """Unregister a service."""
1367 try:
1367 try:
1368 del(self.services[info.name.lower()])
1368 del(self.services[info.name.lower()])
1369 if self.servicetypes[info.type]>1:
1369 if self.servicetypes[info.type]>1:
1370 self.servicetypes[info.type]-=1
1370 self.servicetypes[info.type]-=1
1371 else:
1371 else:
1372 del self.servicetypes[info.type]
1372 del self.servicetypes[info.type]
1373 except KeyError:
1373 except KeyError:
1374 pass
1374 pass
1375 now = currentTimeMillis()
1375 now = currentTimeMillis()
1376 nextTime = now
1376 nextTime = now
1377 i = 0
1377 i = 0
1378 while i < 3:
1378 while i < 3:
1379 if now < nextTime:
1379 if now < nextTime:
1380 self.wait(nextTime - now)
1380 self.wait(nextTime - now)
1381 now = currentTimeMillis()
1381 now = currentTimeMillis()
1382 continue
1382 continue
1383 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1383 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1384 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
1384 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
1385 out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0)
1385 out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0)
1386 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
1386 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
1387 if info.address:
1387 if info.address:
1388 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
1388 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
1389 self.send(out)
1389 self.send(out)
1390 i += 1
1390 i += 1
1391 nextTime += _UNREGISTER_TIME
1391 nextTime += _UNREGISTER_TIME
1392
1392
1393 def unregisterAllServices(self):
1393 def unregisterAllServices(self):
1394 """Unregister all registered services."""
1394 """Unregister all registered services."""
1395 if len(self.services) > 0:
1395 if len(self.services) > 0:
1396 now = currentTimeMillis()
1396 now = currentTimeMillis()
1397 nextTime = now
1397 nextTime = now
1398 i = 0
1398 i = 0
1399 while i < 3:
1399 while i < 3:
1400 if now < nextTime:
1400 if now < nextTime:
1401 self.wait(nextTime - now)
1401 self.wait(nextTime - now)
1402 now = currentTimeMillis()
1402 now = currentTimeMillis()
1403 continue
1403 continue
1404 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1404 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1405 for info in self.services.values():
1405 for info in self.services.values():
1406 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
1406 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
1407 out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0)
1407 out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0)
1408 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
1408 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
1409 if info.address:
1409 if info.address:
1410 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
1410 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
1411 self.send(out)
1411 self.send(out)
1412 i += 1
1412 i += 1
1413 nextTime += _UNREGISTER_TIME
1413 nextTime += _UNREGISTER_TIME
1414
1414
1415 def checkService(self, info):
1415 def checkService(self, info):
1416 """Checks the network for a unique service name, modifying the
1416 """Checks the network for a unique service name, modifying the
1417 ServiceInfo passed in if it is not unique."""
1417 ServiceInfo passed in if it is not unique."""
1418 now = currentTimeMillis()
1418 now = currentTimeMillis()
1419 nextTime = now
1419 nextTime = now
1420 i = 0
1420 i = 0
1421 while i < 3:
1421 while i < 3:
1422 for record in self.cache.entriesWithName(info.type):
1422 for record in self.cache.entriesWithName(info.type):
1423 if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name:
1423 if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name:
1424 if (info.name.find('.') < 0):
1424 if (info.name.find('.') < 0):
1425 info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type
1425 info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type
1426 self.checkService(info)
1426 self.checkService(info)
1427 return
1427 return
1428 raise NonUniqueNameException
1428 raise NonUniqueNameException
1429 if now < nextTime:
1429 if now < nextTime:
1430 self.wait(nextTime - now)
1430 self.wait(nextTime - now)
1431 now = currentTimeMillis()
1431 now = currentTimeMillis()
1432 continue
1432 continue
1433 out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA)
1433 out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA)
1434 self.debug = out
1434 self.debug = out
1435 out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN))
1435 out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN))
1436 out.addAuthorativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name))
1436 out.addAuthoritativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name))
1437 self.send(out)
1437 self.send(out)
1438 i += 1
1438 i += 1
1439 nextTime += _CHECK_TIME
1439 nextTime += _CHECK_TIME
1440
1440
1441 def addListener(self, listener, question):
1441 def addListener(self, listener, question):
1442 """Adds a listener for a given question. The listener will have
1442 """Adds a listener for a given question. The listener will have
1443 its updateRecord method called when information is available to
1443 its updateRecord method called when information is available to
1444 answer the question."""
1444 answer the question."""
1445 now = currentTimeMillis()
1445 now = currentTimeMillis()
1446 self.listeners.append(listener)
1446 self.listeners.append(listener)
1447 if question is not None:
1447 if question is not None:
1448 for record in self.cache.entriesWithName(question.name):
1448 for record in self.cache.entriesWithName(question.name):
1449 if question.answeredBy(record) and not record.isExpired(now):
1449 if question.answeredBy(record) and not record.isExpired(now):
1450 listener.updateRecord(self, now, record)
1450 listener.updateRecord(self, now, record)
1451 self.notifyAll()
1451 self.notifyAll()
1452
1452
1453 def removeListener(self, listener):
1453 def removeListener(self, listener):
1454 """Removes a listener."""
1454 """Removes a listener."""
1455 try:
1455 try:
1456 self.listeners.remove(listener)
1456 self.listeners.remove(listener)
1457 self.notifyAll()
1457 self.notifyAll()
1458 except Exception:
1458 except Exception:
1459 pass
1459 pass
1460
1460
1461 def updateRecord(self, now, rec):
1461 def updateRecord(self, now, rec):
1462 """Used to notify listeners of new information that has updated
1462 """Used to notify listeners of new information that has updated
1463 a record."""
1463 a record."""
1464 for listener in self.listeners:
1464 for listener in self.listeners:
1465 listener.updateRecord(self, now, rec)
1465 listener.updateRecord(self, now, rec)
1466 self.notifyAll()
1466 self.notifyAll()
1467
1467
1468 def handleResponse(self, msg):
1468 def handleResponse(self, msg):
1469 """Deal with incoming response packets. All answers
1469 """Deal with incoming response packets. All answers
1470 are held in the cache, and listeners are notified."""
1470 are held in the cache, and listeners are notified."""
1471 now = currentTimeMillis()
1471 now = currentTimeMillis()
1472 for record in msg.answers:
1472 for record in msg.answers:
1473 expired = record.isExpired(now)
1473 expired = record.isExpired(now)
1474 if record in self.cache.entries():
1474 if record in self.cache.entries():
1475 if expired:
1475 if expired:
1476 self.cache.remove(record)
1476 self.cache.remove(record)
1477 else:
1477 else:
1478 entry = self.cache.get(record)
1478 entry = self.cache.get(record)
1479 if entry is not None:
1479 if entry is not None:
1480 entry.resetTTL(record)
1480 entry.resetTTL(record)
1481 record = entry
1481 record = entry
1482 else:
1482 else:
1483 self.cache.add(record)
1483 self.cache.add(record)
1484
1484
1485 self.updateRecord(now, record)
1485 self.updateRecord(now, record)
1486
1486
1487 def handleQuery(self, msg, addr, port):
1487 def handleQuery(self, msg, addr, port):
1488 """Deal with incoming query packets. Provides a response if
1488 """Deal with incoming query packets. Provides a response if
1489 possible."""
1489 possible."""
1490 out = None
1490 out = None
1491
1491
1492 # Support unicast client responses
1492 # Support unicast client responses
1493 #
1493 #
1494 if port != _MDNS_PORT:
1494 if port != _MDNS_PORT:
1495 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
1495 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
1496 for question in msg.questions:
1496 for question in msg.questions:
1497 out.addQuestion(question)
1497 out.addQuestion(question)
1498
1498
1499 for question in msg.questions:
1499 for question in msg.questions:
1500 if question.type == _TYPE_PTR:
1500 if question.type == _TYPE_PTR:
1501 if question.name == "_services._dns-sd._udp.local.":
1501 if question.name == "_services._dns-sd._udp.local.":
1502 for stype in self.servicetypes.keys():
1502 for stype in self.servicetypes.keys():
1503 if out is None:
1503 if out is None:
1504 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1504 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1505 out.addAnswer(msg, DNSPointer("_services._dns-sd._udp.local.", _TYPE_PTR, _CLASS_IN, _DNS_TTL, stype))
1505 out.addAnswer(msg, DNSPointer("_services._dns-sd._udp.local.", _TYPE_PTR, _CLASS_IN, _DNS_TTL, stype))
1506 for service in self.services.values():
1506 for service in self.services.values():
1507 if question.name == service.type:
1507 if question.name == service.type:
1508 if out is None:
1508 if out is None:
1509 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1509 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1510 out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name))
1510 out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name))
1511 else:
1511 else:
1512 try:
1512 try:
1513 if out is None:
1513 if out is None:
1514 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1514 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1515
1515
1516 # Answer A record queries for any service addresses we know
1516 # Answer A record queries for any service addresses we know
1517 if question.type == _TYPE_A or question.type == _TYPE_ANY:
1517 if question.type == _TYPE_A or question.type == _TYPE_ANY:
1518 for service in self.services.values():
1518 for service in self.services.values():
1519 if service.server == question.name.lower():
1519 if service.server == question.name.lower():
1520 out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
1520 out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
1521
1521
1522 service = self.services.get(question.name.lower(), None)
1522 service = self.services.get(question.name.lower(), None)
1523 if not service: continue
1523 if not service: continue
1524
1524
1525 if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
1525 if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
1526 out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server))
1526 out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server))
1527 if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
1527 if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
1528 out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text))
1528 out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text))
1529 if question.type == _TYPE_SRV:
1529 if question.type == _TYPE_SRV:
1530 out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
1530 out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
1531 except Exception:
1531 except Exception:
1532 traceback.print_exc()
1532 traceback.print_exc()
1533
1533
1534 if out is not None and out.answers:
1534 if out is not None and out.answers:
1535 out.id = msg.id
1535 out.id = msg.id
1536 self.send(out, addr, port)
1536 self.send(out, addr, port)
1537
1537
1538 def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT):
1538 def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT):
1539 """Sends an outgoing packet."""
1539 """Sends an outgoing packet."""
1540 # This is a quick test to see if we can parse the packets we generate
1540 # This is a quick test to see if we can parse the packets we generate
1541 #temp = DNSIncoming(out.packet())
1541 #temp = DNSIncoming(out.packet())
1542 try:
1542 try:
1543 self.socket.sendto(out.packet(), 0, (addr, port))
1543 self.socket.sendto(out.packet(), 0, (addr, port))
1544 except Exception:
1544 except Exception:
1545 # Ignore this, it may be a temporary loss of network connection
1545 # Ignore this, it may be a temporary loss of network connection
1546 pass
1546 pass
1547
1547
1548 def close(self):
1548 def close(self):
1549 """Ends the background threads, and prevent this instance from
1549 """Ends the background threads, and prevent this instance from
1550 servicing further queries."""
1550 servicing further queries."""
1551 if globals()['_GLOBAL_DONE'] == 0:
1551 if globals()['_GLOBAL_DONE'] == 0:
1552 globals()['_GLOBAL_DONE'] = 1
1552 globals()['_GLOBAL_DONE'] = 1
1553 self.notifyAll()
1553 self.notifyAll()
1554 self.engine.notify()
1554 self.engine.notify()
1555 self.unregisterAllServices()
1555 self.unregisterAllServices()
1556 self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
1556 self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
1557 self.socket.close()
1557 self.socket.close()
1558
1558
1559 # Test a few module features, including service registration, service
1559 # Test a few module features, including service registration, service
1560 # query (for Zoe), and service unregistration.
1560 # query (for Zoe), and service unregistration.
1561
1561
1562 if __name__ == '__main__':
1562 if __name__ == '__main__':
1563 print "Multicast DNS Service Discovery for Python, version", __version__
1563 print "Multicast DNS Service Discovery for Python, version", __version__
1564 r = Zeroconf()
1564 r = Zeroconf()
1565 print "1. Testing registration of a service..."
1565 print "1. Testing registration of a service..."
1566 desc = {'version':'0.10','a':'test value', 'b':'another value'}
1566 desc = {'version':'0.10','a':'test value', 'b':'another value'}
1567 info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc)
1567 info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc)
1568 print " Registering service..."
1568 print " Registering service..."
1569 r.registerService(info)
1569 r.registerService(info)
1570 print " Registration done."
1570 print " Registration done."
1571 print "2. Testing query of service information..."
1571 print "2. Testing query of service information..."
1572 print " Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local."))
1572 print " Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local."))
1573 print " Query done."
1573 print " Query done."
1574 print "3. Testing query of own service..."
1574 print "3. Testing query of own service..."
1575 print " Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local."))
1575 print " Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local."))
1576 print " Query done."
1576 print " Query done."
1577 print "4. Testing unregister of service information..."
1577 print "4. Testing unregister of service information..."
1578 r.unregisterService(info)
1578 r.unregisterService(info)
1579 print " Unregister done."
1579 print " Unregister done."
1580 r.close()
1580 r.close()
1581
1581
1582 # no-check-code
1582 # no-check-code
@@ -1,254 +1,254 b''
1 # Mercurial bookmark support code
1 # Mercurial bookmark support code
2 #
2 #
3 # Copyright 2008 David Soria Parra <dsp@php.net>
3 # Copyright 2008 David Soria Parra <dsp@php.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from mercurial.i18n import _
8 from mercurial.i18n import _
9 from mercurial.node import hex
9 from mercurial.node import hex
10 from mercurial import encoding, error, util
10 from mercurial import encoding, error, util
11 import errno, os
11 import errno, os
12
12
13 def valid(mark):
13 def valid(mark):
14 for c in (':', '\0', '\n', '\r'):
14 for c in (':', '\0', '\n', '\r'):
15 if c in mark:
15 if c in mark:
16 return False
16 return False
17 return True
17 return True
18
18
19 def read(repo):
19 def read(repo):
20 '''Parse .hg/bookmarks file and return a dictionary
20 '''Parse .hg/bookmarks file and return a dictionary
21
21
22 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
22 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
23 in the .hg/bookmarks file.
23 in the .hg/bookmarks file.
24 Read the file and return a (name=>nodeid) dictionary
24 Read the file and return a (name=>nodeid) dictionary
25 '''
25 '''
26 bookmarks = {}
26 bookmarks = {}
27 try:
27 try:
28 for line in repo.opener('bookmarks'):
28 for line in repo.opener('bookmarks'):
29 line = line.strip()
29 line = line.strip()
30 if not line:
30 if not line:
31 continue
31 continue
32 if ' ' not in line:
32 if ' ' not in line:
33 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n') % line)
33 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n') % line)
34 continue
34 continue
35 sha, refspec = line.split(' ', 1)
35 sha, refspec = line.split(' ', 1)
36 refspec = encoding.tolocal(refspec)
36 refspec = encoding.tolocal(refspec)
37 try:
37 try:
38 bookmarks[refspec] = repo.changelog.lookup(sha)
38 bookmarks[refspec] = repo.changelog.lookup(sha)
39 except LookupError:
39 except LookupError:
40 pass
40 pass
41 except IOError, inst:
41 except IOError, inst:
42 if inst.errno != errno.ENOENT:
42 if inst.errno != errno.ENOENT:
43 raise
43 raise
44 return bookmarks
44 return bookmarks
45
45
46 def readcurrent(repo):
46 def readcurrent(repo):
47 '''Get the current bookmark
47 '''Get the current bookmark
48
48
49 If we use gittishsh branches we have a current bookmark that
49 If we use gittishsh branches we have a current bookmark that
50 we are on. This function returns the name of the bookmark. It
50 we are on. This function returns the name of the bookmark. It
51 is stored in .hg/bookmarks.current
51 is stored in .hg/bookmarks.current
52 '''
52 '''
53 mark = None
53 mark = None
54 try:
54 try:
55 file = repo.opener('bookmarks.current')
55 file = repo.opener('bookmarks.current')
56 except IOError, inst:
56 except IOError, inst:
57 if inst.errno != errno.ENOENT:
57 if inst.errno != errno.ENOENT:
58 raise
58 raise
59 return None
59 return None
60 try:
60 try:
61 # No readline() in posixfile_nt, reading everything is cheap
61 # No readline() in osutil.posixfile, reading everything is cheap
62 mark = encoding.tolocal((file.readlines() or [''])[0])
62 mark = encoding.tolocal((file.readlines() or [''])[0])
63 if mark == '' or mark not in repo._bookmarks:
63 if mark == '' or mark not in repo._bookmarks:
64 mark = None
64 mark = None
65 finally:
65 finally:
66 file.close()
66 file.close()
67 return mark
67 return mark
68
68
69 def write(repo):
69 def write(repo):
70 '''Write bookmarks
70 '''Write bookmarks
71
71
72 Write the given bookmark => hash dictionary to the .hg/bookmarks file
72 Write the given bookmark => hash dictionary to the .hg/bookmarks file
73 in a format equal to those of localtags.
73 in a format equal to those of localtags.
74
74
75 We also store a backup of the previous state in undo.bookmarks that
75 We also store a backup of the previous state in undo.bookmarks that
76 can be copied back on rollback.
76 can be copied back on rollback.
77 '''
77 '''
78 refs = repo._bookmarks
78 refs = repo._bookmarks
79
79
80 if repo._bookmarkcurrent not in refs:
80 if repo._bookmarkcurrent not in refs:
81 setcurrent(repo, None)
81 setcurrent(repo, None)
82 for mark in refs.keys():
82 for mark in refs.keys():
83 if not valid(mark):
83 if not valid(mark):
84 raise util.Abort(_("bookmark '%s' contains illegal "
84 raise util.Abort(_("bookmark '%s' contains illegal "
85 "character" % mark))
85 "character" % mark))
86
86
87 wlock = repo.wlock()
87 wlock = repo.wlock()
88 try:
88 try:
89
89
90 file = repo.opener('bookmarks', 'w', atomictemp=True)
90 file = repo.opener('bookmarks', 'w', atomictemp=True)
91 for refspec, node in refs.iteritems():
91 for refspec, node in refs.iteritems():
92 file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
92 file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
93 file.close()
93 file.close()
94
94
95 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
95 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
96 try:
96 try:
97 os.utime(repo.sjoin('00changelog.i'), None)
97 os.utime(repo.sjoin('00changelog.i'), None)
98 except OSError:
98 except OSError:
99 pass
99 pass
100
100
101 finally:
101 finally:
102 wlock.release()
102 wlock.release()
103
103
104 def setcurrent(repo, mark):
104 def setcurrent(repo, mark):
105 '''Set the name of the bookmark that we are currently on
105 '''Set the name of the bookmark that we are currently on
106
106
107 Set the name of the bookmark that we are on (hg update <bookmark>).
107 Set the name of the bookmark that we are on (hg update <bookmark>).
108 The name is recorded in .hg/bookmarks.current
108 The name is recorded in .hg/bookmarks.current
109 '''
109 '''
110 current = repo._bookmarkcurrent
110 current = repo._bookmarkcurrent
111 if current == mark:
111 if current == mark:
112 return
112 return
113
113
114 if mark not in repo._bookmarks:
114 if mark not in repo._bookmarks:
115 mark = ''
115 mark = ''
116 if not valid(mark):
116 if not valid(mark):
117 raise util.Abort(_("bookmark '%s' contains illegal "
117 raise util.Abort(_("bookmark '%s' contains illegal "
118 "character" % mark))
118 "character" % mark))
119
119
120 wlock = repo.wlock()
120 wlock = repo.wlock()
121 try:
121 try:
122 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
122 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
123 file.write(encoding.fromlocal(mark))
123 file.write(encoding.fromlocal(mark))
124 file.close()
124 file.close()
125 finally:
125 finally:
126 wlock.release()
126 wlock.release()
127 repo._bookmarkcurrent = mark
127 repo._bookmarkcurrent = mark
128
128
129 def unsetcurrent(repo):
129 def unsetcurrent(repo):
130 wlock = repo.wlock()
130 wlock = repo.wlock()
131 try:
131 try:
132 try:
132 try:
133 util.unlink(repo.join('bookmarks.current'))
133 util.unlink(repo.join('bookmarks.current'))
134 repo._bookmarkcurrent = None
134 repo._bookmarkcurrent = None
135 except OSError, inst:
135 except OSError, inst:
136 if inst.errno != errno.ENOENT:
136 if inst.errno != errno.ENOENT:
137 raise
137 raise
138 finally:
138 finally:
139 wlock.release()
139 wlock.release()
140
140
141 def updatecurrentbookmark(repo, oldnode, curbranch):
141 def updatecurrentbookmark(repo, oldnode, curbranch):
142 try:
142 try:
143 return update(repo, oldnode, repo.branchtip(curbranch))
143 return update(repo, oldnode, repo.branchtip(curbranch))
144 except error.RepoLookupError:
144 except error.RepoLookupError:
145 if curbranch == "default": # no default branch!
145 if curbranch == "default": # no default branch!
146 return update(repo, oldnode, repo.lookup("tip"))
146 return update(repo, oldnode, repo.lookup("tip"))
147 else:
147 else:
148 raise util.Abort(_("branch %s not found") % curbranch)
148 raise util.Abort(_("branch %s not found") % curbranch)
149
149
150 def update(repo, parents, node):
150 def update(repo, parents, node):
151 marks = repo._bookmarks
151 marks = repo._bookmarks
152 update = False
152 update = False
153 cur = repo._bookmarkcurrent
153 cur = repo._bookmarkcurrent
154 if not cur:
154 if not cur:
155 return False
155 return False
156
156
157 toupdate = [b for b in marks if b.split('@', 1)[0] == cur.split('@', 1)[0]]
157 toupdate = [b for b in marks if b.split('@', 1)[0] == cur.split('@', 1)[0]]
158 for mark in toupdate:
158 for mark in toupdate:
159 if mark and marks[mark] in parents:
159 if mark and marks[mark] in parents:
160 old = repo[marks[mark]]
160 old = repo[marks[mark]]
161 new = repo[node]
161 new = repo[node]
162 if new in old.descendants() and mark == cur:
162 if new in old.descendants() and mark == cur:
163 marks[cur] = new.node()
163 marks[cur] = new.node()
164 update = True
164 update = True
165 if mark != cur:
165 if mark != cur:
166 del marks[mark]
166 del marks[mark]
167 if update:
167 if update:
168 repo._writebookmarks(marks)
168 repo._writebookmarks(marks)
169 return update
169 return update
170
170
171 def listbookmarks(repo):
171 def listbookmarks(repo):
172 # We may try to list bookmarks on a repo type that does not
172 # We may try to list bookmarks on a repo type that does not
173 # support it (e.g., statichttprepository).
173 # support it (e.g., statichttprepository).
174 marks = getattr(repo, '_bookmarks', {})
174 marks = getattr(repo, '_bookmarks', {})
175
175
176 d = {}
176 d = {}
177 for k, v in marks.iteritems():
177 for k, v in marks.iteritems():
178 # don't expose local divergent bookmarks
178 # don't expose local divergent bookmarks
179 if '@' not in k or k.endswith('@'):
179 if '@' not in k or k.endswith('@'):
180 d[k] = hex(v)
180 d[k] = hex(v)
181 return d
181 return d
182
182
183 def pushbookmark(repo, key, old, new):
183 def pushbookmark(repo, key, old, new):
184 w = repo.wlock()
184 w = repo.wlock()
185 try:
185 try:
186 marks = repo._bookmarks
186 marks = repo._bookmarks
187 if hex(marks.get(key, '')) != old:
187 if hex(marks.get(key, '')) != old:
188 return False
188 return False
189 if new == '':
189 if new == '':
190 del marks[key]
190 del marks[key]
191 else:
191 else:
192 if new not in repo:
192 if new not in repo:
193 return False
193 return False
194 marks[key] = repo[new].node()
194 marks[key] = repo[new].node()
195 write(repo)
195 write(repo)
196 return True
196 return True
197 finally:
197 finally:
198 w.release()
198 w.release()
199
199
200 def updatefromremote(ui, repo, remote, path):
200 def updatefromremote(ui, repo, remote, path):
201 ui.debug("checking for updated bookmarks\n")
201 ui.debug("checking for updated bookmarks\n")
202 rb = remote.listkeys('bookmarks')
202 rb = remote.listkeys('bookmarks')
203 changed = False
203 changed = False
204 for k in rb.keys():
204 for k in rb.keys():
205 if k in repo._bookmarks:
205 if k in repo._bookmarks:
206 nr, nl = rb[k], repo._bookmarks[k]
206 nr, nl = rb[k], repo._bookmarks[k]
207 if nr in repo:
207 if nr in repo:
208 cr = repo[nr]
208 cr = repo[nr]
209 cl = repo[nl]
209 cl = repo[nl]
210 if cl.rev() >= cr.rev():
210 if cl.rev() >= cr.rev():
211 continue
211 continue
212 if cr in cl.descendants():
212 if cr in cl.descendants():
213 repo._bookmarks[k] = cr.node()
213 repo._bookmarks[k] = cr.node()
214 changed = True
214 changed = True
215 ui.status(_("updating bookmark %s\n") % k)
215 ui.status(_("updating bookmark %s\n") % k)
216 else:
216 else:
217 # find a unique @ suffix
217 # find a unique @ suffix
218 for x in range(1, 100):
218 for x in range(1, 100):
219 n = '%s@%d' % (k, x)
219 n = '%s@%d' % (k, x)
220 if n not in repo._bookmarks:
220 if n not in repo._bookmarks:
221 break
221 break
222 # try to use an @pathalias suffix
222 # try to use an @pathalias suffix
223 # if an @pathalias already exists, we overwrite (update) it
223 # if an @pathalias already exists, we overwrite (update) it
224 for p, u in ui.configitems("paths"):
224 for p, u in ui.configitems("paths"):
225 if path == u:
225 if path == u:
226 n = '%s@%s' % (k, p)
226 n = '%s@%s' % (k, p)
227
227
228 repo._bookmarks[n] = cr.node()
228 repo._bookmarks[n] = cr.node()
229 changed = True
229 changed = True
230 ui.warn(_("divergent bookmark %s stored as %s\n") % (k, n))
230 ui.warn(_("divergent bookmark %s stored as %s\n") % (k, n))
231 elif rb[k] in repo:
231 elif rb[k] in repo:
232 # add remote bookmarks for changes we already have
232 # add remote bookmarks for changes we already have
233 repo._bookmarks[k] = repo[rb[k]].node()
233 repo._bookmarks[k] = repo[rb[k]].node()
234 changed = True
234 changed = True
235 ui.status(_("adding remote bookmark %s\n") % k)
235 ui.status(_("adding remote bookmark %s\n") % k)
236
236
237 if changed:
237 if changed:
238 write(repo)
238 write(repo)
239
239
240 def diff(ui, repo, remote):
240 def diff(ui, repo, remote):
241 ui.status(_("searching for changed bookmarks\n"))
241 ui.status(_("searching for changed bookmarks\n"))
242
242
243 lmarks = repo.listkeys('bookmarks')
243 lmarks = repo.listkeys('bookmarks')
244 rmarks = remote.listkeys('bookmarks')
244 rmarks = remote.listkeys('bookmarks')
245
245
246 diff = sorted(set(rmarks) - set(lmarks))
246 diff = sorted(set(rmarks) - set(lmarks))
247 for k in diff:
247 for k in diff:
248 mark = ui.debugflag and rmarks[k] or rmarks[k][:12]
248 mark = ui.debugflag and rmarks[k] or rmarks[k][:12]
249 ui.write(" %-25s %s\n" % (k, mark))
249 ui.write(" %-25s %s\n" % (k, mark))
250
250
251 if len(diff) <= 0:
251 if len(diff) <= 0:
252 ui.status(_("no changed bookmarks found\n"))
252 ui.status(_("no changed bookmarks found\n"))
253 return 1
253 return 1
254 return 0
254 return 0
@@ -1,5902 +1,5902 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, bin, nullid, nullrev, short
8 from node import hex, bin, nullid, nullrev, short
9 from lock import release
9 from lock import release
10 from i18n import _, gettext
10 from i18n import _, gettext
11 import os, re, difflib, time, tempfile, errno
11 import os, re, difflib, time, tempfile, errno
12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
13 import patch, help, url, encoding, templatekw, discovery
13 import patch, help, url, encoding, templatekw, discovery
14 import archival, changegroup, cmdutil, hbisect
14 import archival, changegroup, cmdutil, hbisect
15 import sshserver, hgweb, hgweb.server, commandserver
15 import sshserver, hgweb, hgweb.server, commandserver
16 import merge as mergemod
16 import merge as mergemod
17 import minirst, revset, fileset
17 import minirst, revset, fileset
18 import dagparser, context, simplemerge, graphmod
18 import dagparser, context, simplemerge, graphmod
19 import random, setdiscovery, treediscovery, dagutil, pvec, localrepo
19 import random, setdiscovery, treediscovery, dagutil, pvec, localrepo
20 import phases, obsolete
20 import phases, obsolete
21
21
22 table = {}
22 table = {}
23
23
24 command = cmdutil.command(table)
24 command = cmdutil.command(table)
25
25
26 # common command options
26 # common command options
27
27
28 globalopts = [
28 globalopts = [
29 ('R', 'repository', '',
29 ('R', 'repository', '',
30 _('repository root directory or name of overlay bundle file'),
30 _('repository root directory or name of overlay bundle file'),
31 _('REPO')),
31 _('REPO')),
32 ('', 'cwd', '',
32 ('', 'cwd', '',
33 _('change working directory'), _('DIR')),
33 _('change working directory'), _('DIR')),
34 ('y', 'noninteractive', None,
34 ('y', 'noninteractive', None,
35 _('do not prompt, automatically pick the first choice for all prompts')),
35 _('do not prompt, automatically pick the first choice for all prompts')),
36 ('q', 'quiet', None, _('suppress output')),
36 ('q', 'quiet', None, _('suppress output')),
37 ('v', 'verbose', None, _('enable additional output')),
37 ('v', 'verbose', None, _('enable additional output')),
38 ('', 'config', [],
38 ('', 'config', [],
39 _('set/override config option (use \'section.name=value\')'),
39 _('set/override config option (use \'section.name=value\')'),
40 _('CONFIG')),
40 _('CONFIG')),
41 ('', 'debug', None, _('enable debugging output')),
41 ('', 'debug', None, _('enable debugging output')),
42 ('', 'debugger', None, _('start debugger')),
42 ('', 'debugger', None, _('start debugger')),
43 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
43 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
44 _('ENCODE')),
44 _('ENCODE')),
45 ('', 'encodingmode', encoding.encodingmode,
45 ('', 'encodingmode', encoding.encodingmode,
46 _('set the charset encoding mode'), _('MODE')),
46 _('set the charset encoding mode'), _('MODE')),
47 ('', 'traceback', None, _('always print a traceback on exception')),
47 ('', 'traceback', None, _('always print a traceback on exception')),
48 ('', 'time', None, _('time how long the command takes')),
48 ('', 'time', None, _('time how long the command takes')),
49 ('', 'profile', None, _('print command execution profile')),
49 ('', 'profile', None, _('print command execution profile')),
50 ('', 'version', None, _('output version information and exit')),
50 ('', 'version', None, _('output version information and exit')),
51 ('h', 'help', None, _('display help and exit')),
51 ('h', 'help', None, _('display help and exit')),
52 ]
52 ]
53
53
54 dryrunopts = [('n', 'dry-run', None,
54 dryrunopts = [('n', 'dry-run', None,
55 _('do not perform actions, just print output'))]
55 _('do not perform actions, just print output'))]
56
56
57 remoteopts = [
57 remoteopts = [
58 ('e', 'ssh', '',
58 ('e', 'ssh', '',
59 _('specify ssh command to use'), _('CMD')),
59 _('specify ssh command to use'), _('CMD')),
60 ('', 'remotecmd', '',
60 ('', 'remotecmd', '',
61 _('specify hg command to run on the remote side'), _('CMD')),
61 _('specify hg command to run on the remote side'), _('CMD')),
62 ('', 'insecure', None,
62 ('', 'insecure', None,
63 _('do not verify server certificate (ignoring web.cacerts config)')),
63 _('do not verify server certificate (ignoring web.cacerts config)')),
64 ]
64 ]
65
65
66 walkopts = [
66 walkopts = [
67 ('I', 'include', [],
67 ('I', 'include', [],
68 _('include names matching the given patterns'), _('PATTERN')),
68 _('include names matching the given patterns'), _('PATTERN')),
69 ('X', 'exclude', [],
69 ('X', 'exclude', [],
70 _('exclude names matching the given patterns'), _('PATTERN')),
70 _('exclude names matching the given patterns'), _('PATTERN')),
71 ]
71 ]
72
72
73 commitopts = [
73 commitopts = [
74 ('m', 'message', '',
74 ('m', 'message', '',
75 _('use text as commit message'), _('TEXT')),
75 _('use text as commit message'), _('TEXT')),
76 ('l', 'logfile', '',
76 ('l', 'logfile', '',
77 _('read commit message from file'), _('FILE')),
77 _('read commit message from file'), _('FILE')),
78 ]
78 ]
79
79
80 commitopts2 = [
80 commitopts2 = [
81 ('d', 'date', '',
81 ('d', 'date', '',
82 _('record the specified date as commit date'), _('DATE')),
82 _('record the specified date as commit date'), _('DATE')),
83 ('u', 'user', '',
83 ('u', 'user', '',
84 _('record the specified user as committer'), _('USER')),
84 _('record the specified user as committer'), _('USER')),
85 ]
85 ]
86
86
87 templateopts = [
87 templateopts = [
88 ('', 'style', '',
88 ('', 'style', '',
89 _('display using template map file'), _('STYLE')),
89 _('display using template map file'), _('STYLE')),
90 ('', 'template', '',
90 ('', 'template', '',
91 _('display with template'), _('TEMPLATE')),
91 _('display with template'), _('TEMPLATE')),
92 ]
92 ]
93
93
94 logopts = [
94 logopts = [
95 ('p', 'patch', None, _('show patch')),
95 ('p', 'patch', None, _('show patch')),
96 ('g', 'git', None, _('use git extended diff format')),
96 ('g', 'git', None, _('use git extended diff format')),
97 ('l', 'limit', '',
97 ('l', 'limit', '',
98 _('limit number of changes displayed'), _('NUM')),
98 _('limit number of changes displayed'), _('NUM')),
99 ('M', 'no-merges', None, _('do not show merges')),
99 ('M', 'no-merges', None, _('do not show merges')),
100 ('', 'stat', None, _('output diffstat-style summary of changes')),
100 ('', 'stat', None, _('output diffstat-style summary of changes')),
101 ('G', 'graph', None, _("show the revision DAG")),
101 ('G', 'graph', None, _("show the revision DAG")),
102 ] + templateopts
102 ] + templateopts
103
103
104 diffopts = [
104 diffopts = [
105 ('a', 'text', None, _('treat all files as text')),
105 ('a', 'text', None, _('treat all files as text')),
106 ('g', 'git', None, _('use git extended diff format')),
106 ('g', 'git', None, _('use git extended diff format')),
107 ('', 'nodates', None, _('omit dates from diff headers'))
107 ('', 'nodates', None, _('omit dates from diff headers'))
108 ]
108 ]
109
109
110 diffwsopts = [
110 diffwsopts = [
111 ('w', 'ignore-all-space', None,
111 ('w', 'ignore-all-space', None,
112 _('ignore white space when comparing lines')),
112 _('ignore white space when comparing lines')),
113 ('b', 'ignore-space-change', None,
113 ('b', 'ignore-space-change', None,
114 _('ignore changes in the amount of white space')),
114 _('ignore changes in the amount of white space')),
115 ('B', 'ignore-blank-lines', None,
115 ('B', 'ignore-blank-lines', None,
116 _('ignore changes whose lines are all blank')),
116 _('ignore changes whose lines are all blank')),
117 ]
117 ]
118
118
119 diffopts2 = [
119 diffopts2 = [
120 ('p', 'show-function', None, _('show which function each change is in')),
120 ('p', 'show-function', None, _('show which function each change is in')),
121 ('', 'reverse', None, _('produce a diff that undoes the changes')),
121 ('', 'reverse', None, _('produce a diff that undoes the changes')),
122 ] + diffwsopts + [
122 ] + diffwsopts + [
123 ('U', 'unified', '',
123 ('U', 'unified', '',
124 _('number of lines of context to show'), _('NUM')),
124 _('number of lines of context to show'), _('NUM')),
125 ('', 'stat', None, _('output diffstat-style summary of changes')),
125 ('', 'stat', None, _('output diffstat-style summary of changes')),
126 ]
126 ]
127
127
128 mergetoolopts = [
128 mergetoolopts = [
129 ('t', 'tool', '', _('specify merge tool')),
129 ('t', 'tool', '', _('specify merge tool')),
130 ]
130 ]
131
131
132 similarityopts = [
132 similarityopts = [
133 ('s', 'similarity', '',
133 ('s', 'similarity', '',
134 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
134 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
135 ]
135 ]
136
136
137 subrepoopts = [
137 subrepoopts = [
138 ('S', 'subrepos', None,
138 ('S', 'subrepos', None,
139 _('recurse into subrepositories'))
139 _('recurse into subrepositories'))
140 ]
140 ]
141
141
142 # Commands start here, listed alphabetically
142 # Commands start here, listed alphabetically
143
143
144 @command('^add',
144 @command('^add',
145 walkopts + subrepoopts + dryrunopts,
145 walkopts + subrepoopts + dryrunopts,
146 _('[OPTION]... [FILE]...'))
146 _('[OPTION]... [FILE]...'))
147 def add(ui, repo, *pats, **opts):
147 def add(ui, repo, *pats, **opts):
148 """add the specified files on the next commit
148 """add the specified files on the next commit
149
149
150 Schedule files to be version controlled and added to the
150 Schedule files to be version controlled and added to the
151 repository.
151 repository.
152
152
153 The files will be added to the repository at the next commit. To
153 The files will be added to the repository at the next commit. To
154 undo an add before that, see :hg:`forget`.
154 undo an add before that, see :hg:`forget`.
155
155
156 If no names are given, add all files to the repository.
156 If no names are given, add all files to the repository.
157
157
158 .. container:: verbose
158 .. container:: verbose
159
159
160 An example showing how new (unknown) files are added
160 An example showing how new (unknown) files are added
161 automatically by :hg:`add`::
161 automatically by :hg:`add`::
162
162
163 $ ls
163 $ ls
164 foo.c
164 foo.c
165 $ hg status
165 $ hg status
166 ? foo.c
166 ? foo.c
167 $ hg add
167 $ hg add
168 adding foo.c
168 adding foo.c
169 $ hg status
169 $ hg status
170 A foo.c
170 A foo.c
171
171
172 Returns 0 if all files are successfully added.
172 Returns 0 if all files are successfully added.
173 """
173 """
174
174
175 m = scmutil.match(repo[None], pats, opts)
175 m = scmutil.match(repo[None], pats, opts)
176 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
176 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
177 opts.get('subrepos'), prefix="", explicitonly=False)
177 opts.get('subrepos'), prefix="", explicitonly=False)
178 return rejected and 1 or 0
178 return rejected and 1 or 0
179
179
180 @command('addremove',
180 @command('addremove',
181 similarityopts + walkopts + dryrunopts,
181 similarityopts + walkopts + dryrunopts,
182 _('[OPTION]... [FILE]...'))
182 _('[OPTION]... [FILE]...'))
183 def addremove(ui, repo, *pats, **opts):
183 def addremove(ui, repo, *pats, **opts):
184 """add all new files, delete all missing files
184 """add all new files, delete all missing files
185
185
186 Add all new files and remove all missing files from the
186 Add all new files and remove all missing files from the
187 repository.
187 repository.
188
188
189 New files are ignored if they match any of the patterns in
189 New files are ignored if they match any of the patterns in
190 ``.hgignore``. As with add, these changes take effect at the next
190 ``.hgignore``. As with add, these changes take effect at the next
191 commit.
191 commit.
192
192
193 Use the -s/--similarity option to detect renamed files. This
193 Use the -s/--similarity option to detect renamed files. This
194 option takes a percentage between 0 (disabled) and 100 (files must
194 option takes a percentage between 0 (disabled) and 100 (files must
195 be identical) as its parameter. With a parameter greater than 0,
195 be identical) as its parameter. With a parameter greater than 0,
196 this compares every removed file with every added file and records
196 this compares every removed file with every added file and records
197 those similar enough as renames. Detecting renamed files this way
197 those similar enough as renames. Detecting renamed files this way
198 can be expensive. After using this option, :hg:`status -C` can be
198 can be expensive. After using this option, :hg:`status -C` can be
199 used to check which files were identified as moved or renamed. If
199 used to check which files were identified as moved or renamed. If
200 not specified, -s/--similarity defaults to 100 and only renames of
200 not specified, -s/--similarity defaults to 100 and only renames of
201 identical files are detected.
201 identical files are detected.
202
202
203 Returns 0 if all files are successfully added.
203 Returns 0 if all files are successfully added.
204 """
204 """
205 try:
205 try:
206 sim = float(opts.get('similarity') or 100)
206 sim = float(opts.get('similarity') or 100)
207 except ValueError:
207 except ValueError:
208 raise util.Abort(_('similarity must be a number'))
208 raise util.Abort(_('similarity must be a number'))
209 if sim < 0 or sim > 100:
209 if sim < 0 or sim > 100:
210 raise util.Abort(_('similarity must be between 0 and 100'))
210 raise util.Abort(_('similarity must be between 0 and 100'))
211 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
211 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
212
212
213 @command('^annotate|blame',
213 @command('^annotate|blame',
214 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
214 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
215 ('', 'follow', None,
215 ('', 'follow', None,
216 _('follow copies/renames and list the filename (DEPRECATED)')),
216 _('follow copies/renames and list the filename (DEPRECATED)')),
217 ('', 'no-follow', None, _("don't follow copies and renames")),
217 ('', 'no-follow', None, _("don't follow copies and renames")),
218 ('a', 'text', None, _('treat all files as text')),
218 ('a', 'text', None, _('treat all files as text')),
219 ('u', 'user', None, _('list the author (long with -v)')),
219 ('u', 'user', None, _('list the author (long with -v)')),
220 ('f', 'file', None, _('list the filename')),
220 ('f', 'file', None, _('list the filename')),
221 ('d', 'date', None, _('list the date (short with -q)')),
221 ('d', 'date', None, _('list the date (short with -q)')),
222 ('n', 'number', None, _('list the revision number (default)')),
222 ('n', 'number', None, _('list the revision number (default)')),
223 ('c', 'changeset', None, _('list the changeset')),
223 ('c', 'changeset', None, _('list the changeset')),
224 ('l', 'line-number', None, _('show line number at the first appearance'))
224 ('l', 'line-number', None, _('show line number at the first appearance'))
225 ] + diffwsopts + walkopts,
225 ] + diffwsopts + walkopts,
226 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
226 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
227 def annotate(ui, repo, *pats, **opts):
227 def annotate(ui, repo, *pats, **opts):
228 """show changeset information by line for each file
228 """show changeset information by line for each file
229
229
230 List changes in files, showing the revision id responsible for
230 List changes in files, showing the revision id responsible for
231 each line
231 each line
232
232
233 This command is useful for discovering when a change was made and
233 This command is useful for discovering when a change was made and
234 by whom.
234 by whom.
235
235
236 Without the -a/--text option, annotate will avoid processing files
236 Without the -a/--text option, annotate will avoid processing files
237 it detects as binary. With -a, annotate will annotate the file
237 it detects as binary. With -a, annotate will annotate the file
238 anyway, although the results will probably be neither useful
238 anyway, although the results will probably be neither useful
239 nor desirable.
239 nor desirable.
240
240
241 Returns 0 on success.
241 Returns 0 on success.
242 """
242 """
243 if opts.get('follow'):
243 if opts.get('follow'):
244 # --follow is deprecated and now just an alias for -f/--file
244 # --follow is deprecated and now just an alias for -f/--file
245 # to mimic the behavior of Mercurial before version 1.5
245 # to mimic the behavior of Mercurial before version 1.5
246 opts['file'] = True
246 opts['file'] = True
247
247
248 datefunc = ui.quiet and util.shortdate or util.datestr
248 datefunc = ui.quiet and util.shortdate or util.datestr
249 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
249 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
250
250
251 if not pats:
251 if not pats:
252 raise util.Abort(_('at least one filename or pattern is required'))
252 raise util.Abort(_('at least one filename or pattern is required'))
253
253
254 hexfn = ui.debugflag and hex or short
254 hexfn = ui.debugflag and hex or short
255
255
256 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
256 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
257 ('number', ' ', lambda x: str(x[0].rev())),
257 ('number', ' ', lambda x: str(x[0].rev())),
258 ('changeset', ' ', lambda x: hexfn(x[0].node())),
258 ('changeset', ' ', lambda x: hexfn(x[0].node())),
259 ('date', ' ', getdate),
259 ('date', ' ', getdate),
260 ('file', ' ', lambda x: x[0].path()),
260 ('file', ' ', lambda x: x[0].path()),
261 ('line_number', ':', lambda x: str(x[1])),
261 ('line_number', ':', lambda x: str(x[1])),
262 ]
262 ]
263
263
264 if (not opts.get('user') and not opts.get('changeset')
264 if (not opts.get('user') and not opts.get('changeset')
265 and not opts.get('date') and not opts.get('file')):
265 and not opts.get('date') and not opts.get('file')):
266 opts['number'] = True
266 opts['number'] = True
267
267
268 linenumber = opts.get('line_number') is not None
268 linenumber = opts.get('line_number') is not None
269 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
269 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
270 raise util.Abort(_('at least one of -n/-c is required for -l'))
270 raise util.Abort(_('at least one of -n/-c is required for -l'))
271
271
272 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
272 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
273 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
273 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
274
274
275 def bad(x, y):
275 def bad(x, y):
276 raise util.Abort("%s: %s" % (x, y))
276 raise util.Abort("%s: %s" % (x, y))
277
277
278 ctx = scmutil.revsingle(repo, opts.get('rev'))
278 ctx = scmutil.revsingle(repo, opts.get('rev'))
279 m = scmutil.match(ctx, pats, opts)
279 m = scmutil.match(ctx, pats, opts)
280 m.bad = bad
280 m.bad = bad
281 follow = not opts.get('no_follow')
281 follow = not opts.get('no_follow')
282 diffopts = patch.diffopts(ui, opts, section='annotate')
282 diffopts = patch.diffopts(ui, opts, section='annotate')
283 for abs in ctx.walk(m):
283 for abs in ctx.walk(m):
284 fctx = ctx[abs]
284 fctx = ctx[abs]
285 if not opts.get('text') and util.binary(fctx.data()):
285 if not opts.get('text') and util.binary(fctx.data()):
286 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
286 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
287 continue
287 continue
288
288
289 lines = fctx.annotate(follow=follow, linenumber=linenumber,
289 lines = fctx.annotate(follow=follow, linenumber=linenumber,
290 diffopts=diffopts)
290 diffopts=diffopts)
291 pieces = []
291 pieces = []
292
292
293 for f, sep in funcmap:
293 for f, sep in funcmap:
294 l = [f(n) for n, dummy in lines]
294 l = [f(n) for n, dummy in lines]
295 if l:
295 if l:
296 sized = [(x, encoding.colwidth(x)) for x in l]
296 sized = [(x, encoding.colwidth(x)) for x in l]
297 ml = max([w for x, w in sized])
297 ml = max([w for x, w in sized])
298 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
298 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
299 for x, w in sized])
299 for x, w in sized])
300
300
301 if pieces:
301 if pieces:
302 for p, l in zip(zip(*pieces), lines):
302 for p, l in zip(zip(*pieces), lines):
303 ui.write("%s: %s" % ("".join(p), l[1]))
303 ui.write("%s: %s" % ("".join(p), l[1]))
304
304
305 if lines and not lines[-1][1].endswith('\n'):
305 if lines and not lines[-1][1].endswith('\n'):
306 ui.write('\n')
306 ui.write('\n')
307
307
308 @command('archive',
308 @command('archive',
309 [('', 'no-decode', None, _('do not pass files through decoders')),
309 [('', 'no-decode', None, _('do not pass files through decoders')),
310 ('p', 'prefix', '', _('directory prefix for files in archive'),
310 ('p', 'prefix', '', _('directory prefix for files in archive'),
311 _('PREFIX')),
311 _('PREFIX')),
312 ('r', 'rev', '', _('revision to distribute'), _('REV')),
312 ('r', 'rev', '', _('revision to distribute'), _('REV')),
313 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
313 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
314 ] + subrepoopts + walkopts,
314 ] + subrepoopts + walkopts,
315 _('[OPTION]... DEST'))
315 _('[OPTION]... DEST'))
316 def archive(ui, repo, dest, **opts):
316 def archive(ui, repo, dest, **opts):
317 '''create an unversioned archive of a repository revision
317 '''create an unversioned archive of a repository revision
318
318
319 By default, the revision used is the parent of the working
319 By default, the revision used is the parent of the working
320 directory; use -r/--rev to specify a different revision.
320 directory; use -r/--rev to specify a different revision.
321
321
322 The archive type is automatically detected based on file
322 The archive type is automatically detected based on file
323 extension (or override using -t/--type).
323 extension (or override using -t/--type).
324
324
325 .. container:: verbose
325 .. container:: verbose
326
326
327 Examples:
327 Examples:
328
328
329 - create a zip file containing the 1.0 release::
329 - create a zip file containing the 1.0 release::
330
330
331 hg archive -r 1.0 project-1.0.zip
331 hg archive -r 1.0 project-1.0.zip
332
332
333 - create a tarball excluding .hg files::
333 - create a tarball excluding .hg files::
334
334
335 hg archive project.tar.gz -X ".hg*"
335 hg archive project.tar.gz -X ".hg*"
336
336
337 Valid types are:
337 Valid types are:
338
338
339 :``files``: a directory full of files (default)
339 :``files``: a directory full of files (default)
340 :``tar``: tar archive, uncompressed
340 :``tar``: tar archive, uncompressed
341 :``tbz2``: tar archive, compressed using bzip2
341 :``tbz2``: tar archive, compressed using bzip2
342 :``tgz``: tar archive, compressed using gzip
342 :``tgz``: tar archive, compressed using gzip
343 :``uzip``: zip archive, uncompressed
343 :``uzip``: zip archive, uncompressed
344 :``zip``: zip archive, compressed using deflate
344 :``zip``: zip archive, compressed using deflate
345
345
346 The exact name of the destination archive or directory is given
346 The exact name of the destination archive or directory is given
347 using a format string; see :hg:`help export` for details.
347 using a format string; see :hg:`help export` for details.
348
348
349 Each member added to an archive file has a directory prefix
349 Each member added to an archive file has a directory prefix
350 prepended. Use -p/--prefix to specify a format string for the
350 prepended. Use -p/--prefix to specify a format string for the
351 prefix. The default is the basename of the archive, with suffixes
351 prefix. The default is the basename of the archive, with suffixes
352 removed.
352 removed.
353
353
354 Returns 0 on success.
354 Returns 0 on success.
355 '''
355 '''
356
356
357 ctx = scmutil.revsingle(repo, opts.get('rev'))
357 ctx = scmutil.revsingle(repo, opts.get('rev'))
358 if not ctx:
358 if not ctx:
359 raise util.Abort(_('no working directory: please specify a revision'))
359 raise util.Abort(_('no working directory: please specify a revision'))
360 node = ctx.node()
360 node = ctx.node()
361 dest = cmdutil.makefilename(repo, dest, node)
361 dest = cmdutil.makefilename(repo, dest, node)
362 if os.path.realpath(dest) == repo.root:
362 if os.path.realpath(dest) == repo.root:
363 raise util.Abort(_('repository root cannot be destination'))
363 raise util.Abort(_('repository root cannot be destination'))
364
364
365 kind = opts.get('type') or archival.guesskind(dest) or 'files'
365 kind = opts.get('type') or archival.guesskind(dest) or 'files'
366 prefix = opts.get('prefix')
366 prefix = opts.get('prefix')
367
367
368 if dest == '-':
368 if dest == '-':
369 if kind == 'files':
369 if kind == 'files':
370 raise util.Abort(_('cannot archive plain files to stdout'))
370 raise util.Abort(_('cannot archive plain files to stdout'))
371 dest = cmdutil.makefileobj(repo, dest)
371 dest = cmdutil.makefileobj(repo, dest)
372 if not prefix:
372 if not prefix:
373 prefix = os.path.basename(repo.root) + '-%h'
373 prefix = os.path.basename(repo.root) + '-%h'
374
374
375 prefix = cmdutil.makefilename(repo, prefix, node)
375 prefix = cmdutil.makefilename(repo, prefix, node)
376 matchfn = scmutil.match(ctx, [], opts)
376 matchfn = scmutil.match(ctx, [], opts)
377 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
377 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
378 matchfn, prefix, subrepos=opts.get('subrepos'))
378 matchfn, prefix, subrepos=opts.get('subrepos'))
379
379
380 @command('backout',
380 @command('backout',
381 [('', 'merge', None, _('merge with old dirstate parent after backout')),
381 [('', 'merge', None, _('merge with old dirstate parent after backout')),
382 ('', 'parent', '',
382 ('', 'parent', '',
383 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
383 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
384 ('r', 'rev', '', _('revision to backout'), _('REV')),
384 ('r', 'rev', '', _('revision to backout'), _('REV')),
385 ] + mergetoolopts + walkopts + commitopts + commitopts2,
385 ] + mergetoolopts + walkopts + commitopts + commitopts2,
386 _('[OPTION]... [-r] REV'))
386 _('[OPTION]... [-r] REV'))
387 def backout(ui, repo, node=None, rev=None, **opts):
387 def backout(ui, repo, node=None, rev=None, **opts):
388 '''reverse effect of earlier changeset
388 '''reverse effect of earlier changeset
389
389
390 Prepare a new changeset with the effect of REV undone in the
390 Prepare a new changeset with the effect of REV undone in the
391 current working directory.
391 current working directory.
392
392
393 If REV is the parent of the working directory, then this new changeset
393 If REV is the parent of the working directory, then this new changeset
394 is committed automatically. Otherwise, hg needs to merge the
394 is committed automatically. Otherwise, hg needs to merge the
395 changes and the merged result is left uncommitted.
395 changes and the merged result is left uncommitted.
396
396
397 .. note::
397 .. note::
398 backout cannot be used to fix either an unwanted or
398 backout cannot be used to fix either an unwanted or
399 incorrect merge.
399 incorrect merge.
400
400
401 .. container:: verbose
401 .. container:: verbose
402
402
403 By default, the pending changeset will have one parent,
403 By default, the pending changeset will have one parent,
404 maintaining a linear history. With --merge, the pending
404 maintaining a linear history. With --merge, the pending
405 changeset will instead have two parents: the old parent of the
405 changeset will instead have two parents: the old parent of the
406 working directory and a new child of REV that simply undoes REV.
406 working directory and a new child of REV that simply undoes REV.
407
407
408 Before version 1.7, the behavior without --merge was equivalent
408 Before version 1.7, the behavior without --merge was equivalent
409 to specifying --merge followed by :hg:`update --clean .` to
409 to specifying --merge followed by :hg:`update --clean .` to
410 cancel the merge and leave the child of REV as a head to be
410 cancel the merge and leave the child of REV as a head to be
411 merged separately.
411 merged separately.
412
412
413 See :hg:`help dates` for a list of formats valid for -d/--date.
413 See :hg:`help dates` for a list of formats valid for -d/--date.
414
414
415 Returns 0 on success.
415 Returns 0 on success.
416 '''
416 '''
417 if rev and node:
417 if rev and node:
418 raise util.Abort(_("please specify just one revision"))
418 raise util.Abort(_("please specify just one revision"))
419
419
420 if not rev:
420 if not rev:
421 rev = node
421 rev = node
422
422
423 if not rev:
423 if not rev:
424 raise util.Abort(_("please specify a revision to backout"))
424 raise util.Abort(_("please specify a revision to backout"))
425
425
426 date = opts.get('date')
426 date = opts.get('date')
427 if date:
427 if date:
428 opts['date'] = util.parsedate(date)
428 opts['date'] = util.parsedate(date)
429
429
430 cmdutil.bailifchanged(repo)
430 cmdutil.bailifchanged(repo)
431 node = scmutil.revsingle(repo, rev).node()
431 node = scmutil.revsingle(repo, rev).node()
432
432
433 op1, op2 = repo.dirstate.parents()
433 op1, op2 = repo.dirstate.parents()
434 a = repo.changelog.ancestor(op1, node)
434 a = repo.changelog.ancestor(op1, node)
435 if a != node:
435 if a != node:
436 raise util.Abort(_('cannot backout change on a different branch'))
436 raise util.Abort(_('cannot backout change on a different branch'))
437
437
438 p1, p2 = repo.changelog.parents(node)
438 p1, p2 = repo.changelog.parents(node)
439 if p1 == nullid:
439 if p1 == nullid:
440 raise util.Abort(_('cannot backout a change with no parents'))
440 raise util.Abort(_('cannot backout a change with no parents'))
441 if p2 != nullid:
441 if p2 != nullid:
442 if not opts.get('parent'):
442 if not opts.get('parent'):
443 raise util.Abort(_('cannot backout a merge changeset'))
443 raise util.Abort(_('cannot backout a merge changeset'))
444 p = repo.lookup(opts['parent'])
444 p = repo.lookup(opts['parent'])
445 if p not in (p1, p2):
445 if p not in (p1, p2):
446 raise util.Abort(_('%s is not a parent of %s') %
446 raise util.Abort(_('%s is not a parent of %s') %
447 (short(p), short(node)))
447 (short(p), short(node)))
448 parent = p
448 parent = p
449 else:
449 else:
450 if opts.get('parent'):
450 if opts.get('parent'):
451 raise util.Abort(_('cannot use --parent on non-merge changeset'))
451 raise util.Abort(_('cannot use --parent on non-merge changeset'))
452 parent = p1
452 parent = p1
453
453
454 # the backout should appear on the same branch
454 # the backout should appear on the same branch
455 wlock = repo.wlock()
455 wlock = repo.wlock()
456 try:
456 try:
457 branch = repo.dirstate.branch()
457 branch = repo.dirstate.branch()
458 hg.clean(repo, node, show_stats=False)
458 hg.clean(repo, node, show_stats=False)
459 repo.dirstate.setbranch(branch)
459 repo.dirstate.setbranch(branch)
460 revert_opts = opts.copy()
460 revert_opts = opts.copy()
461 revert_opts['date'] = None
461 revert_opts['date'] = None
462 revert_opts['all'] = True
462 revert_opts['all'] = True
463 revert_opts['rev'] = hex(parent)
463 revert_opts['rev'] = hex(parent)
464 revert_opts['no_backup'] = None
464 revert_opts['no_backup'] = None
465 revert(ui, repo, **revert_opts)
465 revert(ui, repo, **revert_opts)
466 if not opts.get('merge') and op1 != node:
466 if not opts.get('merge') and op1 != node:
467 try:
467 try:
468 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
468 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
469 return hg.update(repo, op1)
469 return hg.update(repo, op1)
470 finally:
470 finally:
471 ui.setconfig('ui', 'forcemerge', '')
471 ui.setconfig('ui', 'forcemerge', '')
472
472
473 commit_opts = opts.copy()
473 commit_opts = opts.copy()
474 commit_opts['addremove'] = False
474 commit_opts['addremove'] = False
475 if not commit_opts['message'] and not commit_opts['logfile']:
475 if not commit_opts['message'] and not commit_opts['logfile']:
476 # we don't translate commit messages
476 # we don't translate commit messages
477 commit_opts['message'] = "Backed out changeset %s" % short(node)
477 commit_opts['message'] = "Backed out changeset %s" % short(node)
478 commit_opts['force_editor'] = True
478 commit_opts['force_editor'] = True
479 commit(ui, repo, **commit_opts)
479 commit(ui, repo, **commit_opts)
480 def nice(node):
480 def nice(node):
481 return '%d:%s' % (repo.changelog.rev(node), short(node))
481 return '%d:%s' % (repo.changelog.rev(node), short(node))
482 ui.status(_('changeset %s backs out changeset %s\n') %
482 ui.status(_('changeset %s backs out changeset %s\n') %
483 (nice(repo.changelog.tip()), nice(node)))
483 (nice(repo.changelog.tip()), nice(node)))
484 if opts.get('merge') and op1 != node:
484 if opts.get('merge') and op1 != node:
485 hg.clean(repo, op1, show_stats=False)
485 hg.clean(repo, op1, show_stats=False)
486 ui.status(_('merging with changeset %s\n')
486 ui.status(_('merging with changeset %s\n')
487 % nice(repo.changelog.tip()))
487 % nice(repo.changelog.tip()))
488 try:
488 try:
489 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
489 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
490 return hg.merge(repo, hex(repo.changelog.tip()))
490 return hg.merge(repo, hex(repo.changelog.tip()))
491 finally:
491 finally:
492 ui.setconfig('ui', 'forcemerge', '')
492 ui.setconfig('ui', 'forcemerge', '')
493 finally:
493 finally:
494 wlock.release()
494 wlock.release()
495 return 0
495 return 0
496
496
497 @command('bisect',
497 @command('bisect',
498 [('r', 'reset', False, _('reset bisect state')),
498 [('r', 'reset', False, _('reset bisect state')),
499 ('g', 'good', False, _('mark changeset good')),
499 ('g', 'good', False, _('mark changeset good')),
500 ('b', 'bad', False, _('mark changeset bad')),
500 ('b', 'bad', False, _('mark changeset bad')),
501 ('s', 'skip', False, _('skip testing changeset')),
501 ('s', 'skip', False, _('skip testing changeset')),
502 ('e', 'extend', False, _('extend the bisect range')),
502 ('e', 'extend', False, _('extend the bisect range')),
503 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
503 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
504 ('U', 'noupdate', False, _('do not update to target'))],
504 ('U', 'noupdate', False, _('do not update to target'))],
505 _("[-gbsr] [-U] [-c CMD] [REV]"))
505 _("[-gbsr] [-U] [-c CMD] [REV]"))
506 def bisect(ui, repo, rev=None, extra=None, command=None,
506 def bisect(ui, repo, rev=None, extra=None, command=None,
507 reset=None, good=None, bad=None, skip=None, extend=None,
507 reset=None, good=None, bad=None, skip=None, extend=None,
508 noupdate=None):
508 noupdate=None):
509 """subdivision search of changesets
509 """subdivision search of changesets
510
510
511 This command helps to find changesets which introduce problems. To
511 This command helps to find changesets which introduce problems. To
512 use, mark the earliest changeset you know exhibits the problem as
512 use, mark the earliest changeset you know exhibits the problem as
513 bad, then mark the latest changeset which is free from the problem
513 bad, then mark the latest changeset which is free from the problem
514 as good. Bisect will update your working directory to a revision
514 as good. Bisect will update your working directory to a revision
515 for testing (unless the -U/--noupdate option is specified). Once
515 for testing (unless the -U/--noupdate option is specified). Once
516 you have performed tests, mark the working directory as good or
516 you have performed tests, mark the working directory as good or
517 bad, and bisect will either update to another candidate changeset
517 bad, and bisect will either update to another candidate changeset
518 or announce that it has found the bad revision.
518 or announce that it has found the bad revision.
519
519
520 As a shortcut, you can also use the revision argument to mark a
520 As a shortcut, you can also use the revision argument to mark a
521 revision as good or bad without checking it out first.
521 revision as good or bad without checking it out first.
522
522
523 If you supply a command, it will be used for automatic bisection.
523 If you supply a command, it will be used for automatic bisection.
524 The environment variable HG_NODE will contain the ID of the
524 The environment variable HG_NODE will contain the ID of the
525 changeset being tested. The exit status of the command will be
525 changeset being tested. The exit status of the command will be
526 used to mark revisions as good or bad: status 0 means good, 125
526 used to mark revisions as good or bad: status 0 means good, 125
527 means to skip the revision, 127 (command not found) will abort the
527 means to skip the revision, 127 (command not found) will abort the
528 bisection, and any other non-zero exit status means the revision
528 bisection, and any other non-zero exit status means the revision
529 is bad.
529 is bad.
530
530
531 .. container:: verbose
531 .. container:: verbose
532
532
533 Some examples:
533 Some examples:
534
534
535 - start a bisection with known bad revision 12, and good revision 34::
535 - start a bisection with known bad revision 12, and good revision 34::
536
536
537 hg bisect --bad 34
537 hg bisect --bad 34
538 hg bisect --good 12
538 hg bisect --good 12
539
539
540 - advance the current bisection by marking current revision as good or
540 - advance the current bisection by marking current revision as good or
541 bad::
541 bad::
542
542
543 hg bisect --good
543 hg bisect --good
544 hg bisect --bad
544 hg bisect --bad
545
545
546 - mark the current revision, or a known revision, to be skipped (e.g. if
546 - mark the current revision, or a known revision, to be skipped (e.g. if
547 that revision is not usable because of another issue)::
547 that revision is not usable because of another issue)::
548
548
549 hg bisect --skip
549 hg bisect --skip
550 hg bisect --skip 23
550 hg bisect --skip 23
551
551
552 - forget the current bisection::
552 - forget the current bisection::
553
553
554 hg bisect --reset
554 hg bisect --reset
555
555
556 - use 'make && make tests' to automatically find the first broken
556 - use 'make && make tests' to automatically find the first broken
557 revision::
557 revision::
558
558
559 hg bisect --reset
559 hg bisect --reset
560 hg bisect --bad 34
560 hg bisect --bad 34
561 hg bisect --good 12
561 hg bisect --good 12
562 hg bisect --command 'make && make tests'
562 hg bisect --command 'make && make tests'
563
563
564 - see all changesets whose states are already known in the current
564 - see all changesets whose states are already known in the current
565 bisection::
565 bisection::
566
566
567 hg log -r "bisect(pruned)"
567 hg log -r "bisect(pruned)"
568
568
569 - see the changeset currently being bisected (especially useful
569 - see the changeset currently being bisected (especially useful
570 if running with -U/--noupdate)::
570 if running with -U/--noupdate)::
571
571
572 hg log -r "bisect(current)"
572 hg log -r "bisect(current)"
573
573
574 - see all changesets that took part in the current bisection::
574 - see all changesets that took part in the current bisection::
575
575
576 hg log -r "bisect(range)"
576 hg log -r "bisect(range)"
577
577
578 - with the graphlog extension, you can even get a nice graph::
578 - with the graphlog extension, you can even get a nice graph::
579
579
580 hg log --graph -r "bisect(range)"
580 hg log --graph -r "bisect(range)"
581
581
582 See :hg:`help revsets` for more about the `bisect()` keyword.
582 See :hg:`help revsets` for more about the `bisect()` keyword.
583
583
584 Returns 0 on success.
584 Returns 0 on success.
585 """
585 """
586 def extendbisectrange(nodes, good):
586 def extendbisectrange(nodes, good):
587 # bisect is incomplete when it ends on a merge node and
587 # bisect is incomplete when it ends on a merge node and
588 # one of the parent was not checked.
588 # one of the parent was not checked.
589 parents = repo[nodes[0]].parents()
589 parents = repo[nodes[0]].parents()
590 if len(parents) > 1:
590 if len(parents) > 1:
591 side = good and state['bad'] or state['good']
591 side = good and state['bad'] or state['good']
592 num = len(set(i.node() for i in parents) & set(side))
592 num = len(set(i.node() for i in parents) & set(side))
593 if num == 1:
593 if num == 1:
594 return parents[0].ancestor(parents[1])
594 return parents[0].ancestor(parents[1])
595 return None
595 return None
596
596
597 def print_result(nodes, good):
597 def print_result(nodes, good):
598 displayer = cmdutil.show_changeset(ui, repo, {})
598 displayer = cmdutil.show_changeset(ui, repo, {})
599 if len(nodes) == 1:
599 if len(nodes) == 1:
600 # narrowed it down to a single revision
600 # narrowed it down to a single revision
601 if good:
601 if good:
602 ui.write(_("The first good revision is:\n"))
602 ui.write(_("The first good revision is:\n"))
603 else:
603 else:
604 ui.write(_("The first bad revision is:\n"))
604 ui.write(_("The first bad revision is:\n"))
605 displayer.show(repo[nodes[0]])
605 displayer.show(repo[nodes[0]])
606 extendnode = extendbisectrange(nodes, good)
606 extendnode = extendbisectrange(nodes, good)
607 if extendnode is not None:
607 if extendnode is not None:
608 ui.write(_('Not all ancestors of this changeset have been'
608 ui.write(_('Not all ancestors of this changeset have been'
609 ' checked.\nUse bisect --extend to continue the '
609 ' checked.\nUse bisect --extend to continue the '
610 'bisection from\nthe common ancestor, %s.\n')
610 'bisection from\nthe common ancestor, %s.\n')
611 % extendnode)
611 % extendnode)
612 else:
612 else:
613 # multiple possible revisions
613 # multiple possible revisions
614 if good:
614 if good:
615 ui.write(_("Due to skipped revisions, the first "
615 ui.write(_("Due to skipped revisions, the first "
616 "good revision could be any of:\n"))
616 "good revision could be any of:\n"))
617 else:
617 else:
618 ui.write(_("Due to skipped revisions, the first "
618 ui.write(_("Due to skipped revisions, the first "
619 "bad revision could be any of:\n"))
619 "bad revision could be any of:\n"))
620 for n in nodes:
620 for n in nodes:
621 displayer.show(repo[n])
621 displayer.show(repo[n])
622 displayer.close()
622 displayer.close()
623
623
624 def check_state(state, interactive=True):
624 def check_state(state, interactive=True):
625 if not state['good'] or not state['bad']:
625 if not state['good'] or not state['bad']:
626 if (good or bad or skip or reset) and interactive:
626 if (good or bad or skip or reset) and interactive:
627 return
627 return
628 if not state['good']:
628 if not state['good']:
629 raise util.Abort(_('cannot bisect (no known good revisions)'))
629 raise util.Abort(_('cannot bisect (no known good revisions)'))
630 else:
630 else:
631 raise util.Abort(_('cannot bisect (no known bad revisions)'))
631 raise util.Abort(_('cannot bisect (no known bad revisions)'))
632 return True
632 return True
633
633
634 # backward compatibility
634 # backward compatibility
635 if rev in "good bad reset init".split():
635 if rev in "good bad reset init".split():
636 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
636 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
637 cmd, rev, extra = rev, extra, None
637 cmd, rev, extra = rev, extra, None
638 if cmd == "good":
638 if cmd == "good":
639 good = True
639 good = True
640 elif cmd == "bad":
640 elif cmd == "bad":
641 bad = True
641 bad = True
642 else:
642 else:
643 reset = True
643 reset = True
644 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
644 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
645 raise util.Abort(_('incompatible arguments'))
645 raise util.Abort(_('incompatible arguments'))
646
646
647 if reset:
647 if reset:
648 p = repo.join("bisect.state")
648 p = repo.join("bisect.state")
649 if os.path.exists(p):
649 if os.path.exists(p):
650 os.unlink(p)
650 os.unlink(p)
651 return
651 return
652
652
653 state = hbisect.load_state(repo)
653 state = hbisect.load_state(repo)
654
654
655 if command:
655 if command:
656 changesets = 1
656 changesets = 1
657 try:
657 try:
658 node = state['current'][0]
658 node = state['current'][0]
659 except LookupError:
659 except LookupError:
660 if noupdate:
660 if noupdate:
661 raise util.Abort(_('current bisect revision is unknown - '
661 raise util.Abort(_('current bisect revision is unknown - '
662 'start a new bisect to fix'))
662 'start a new bisect to fix'))
663 node, p2 = repo.dirstate.parents()
663 node, p2 = repo.dirstate.parents()
664 if p2 != nullid:
664 if p2 != nullid:
665 raise util.Abort(_('current bisect revision is a merge'))
665 raise util.Abort(_('current bisect revision is a merge'))
666 try:
666 try:
667 while changesets:
667 while changesets:
668 # update state
668 # update state
669 state['current'] = [node]
669 state['current'] = [node]
670 hbisect.save_state(repo, state)
670 hbisect.save_state(repo, state)
671 status = util.system(command,
671 status = util.system(command,
672 environ={'HG_NODE': hex(node)},
672 environ={'HG_NODE': hex(node)},
673 out=ui.fout)
673 out=ui.fout)
674 if status == 125:
674 if status == 125:
675 transition = "skip"
675 transition = "skip"
676 elif status == 0:
676 elif status == 0:
677 transition = "good"
677 transition = "good"
678 # status < 0 means process was killed
678 # status < 0 means process was killed
679 elif status == 127:
679 elif status == 127:
680 raise util.Abort(_("failed to execute %s") % command)
680 raise util.Abort(_("failed to execute %s") % command)
681 elif status < 0:
681 elif status < 0:
682 raise util.Abort(_("%s killed") % command)
682 raise util.Abort(_("%s killed") % command)
683 else:
683 else:
684 transition = "bad"
684 transition = "bad"
685 ctx = scmutil.revsingle(repo, rev, node)
685 ctx = scmutil.revsingle(repo, rev, node)
686 rev = None # clear for future iterations
686 rev = None # clear for future iterations
687 state[transition].append(ctx.node())
687 state[transition].append(ctx.node())
688 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
688 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
689 check_state(state, interactive=False)
689 check_state(state, interactive=False)
690 # bisect
690 # bisect
691 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
691 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
692 # update to next check
692 # update to next check
693 node = nodes[0]
693 node = nodes[0]
694 if not noupdate:
694 if not noupdate:
695 cmdutil.bailifchanged(repo)
695 cmdutil.bailifchanged(repo)
696 hg.clean(repo, node, show_stats=False)
696 hg.clean(repo, node, show_stats=False)
697 finally:
697 finally:
698 state['current'] = [node]
698 state['current'] = [node]
699 hbisect.save_state(repo, state)
699 hbisect.save_state(repo, state)
700 print_result(nodes, good)
700 print_result(nodes, good)
701 return
701 return
702
702
703 # update state
703 # update state
704
704
705 if rev:
705 if rev:
706 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
706 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
707 else:
707 else:
708 nodes = [repo.lookup('.')]
708 nodes = [repo.lookup('.')]
709
709
710 if good or bad or skip:
710 if good or bad or skip:
711 if good:
711 if good:
712 state['good'] += nodes
712 state['good'] += nodes
713 elif bad:
713 elif bad:
714 state['bad'] += nodes
714 state['bad'] += nodes
715 elif skip:
715 elif skip:
716 state['skip'] += nodes
716 state['skip'] += nodes
717 hbisect.save_state(repo, state)
717 hbisect.save_state(repo, state)
718
718
719 if not check_state(state):
719 if not check_state(state):
720 return
720 return
721
721
722 # actually bisect
722 # actually bisect
723 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
723 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
724 if extend:
724 if extend:
725 if not changesets:
725 if not changesets:
726 extendnode = extendbisectrange(nodes, good)
726 extendnode = extendbisectrange(nodes, good)
727 if extendnode is not None:
727 if extendnode is not None:
728 ui.write(_("Extending search to changeset %d:%s\n"
728 ui.write(_("Extending search to changeset %d:%s\n"
729 % (extendnode.rev(), extendnode)))
729 % (extendnode.rev(), extendnode)))
730 state['current'] = [extendnode.node()]
730 state['current'] = [extendnode.node()]
731 hbisect.save_state(repo, state)
731 hbisect.save_state(repo, state)
732 if noupdate:
732 if noupdate:
733 return
733 return
734 cmdutil.bailifchanged(repo)
734 cmdutil.bailifchanged(repo)
735 return hg.clean(repo, extendnode.node())
735 return hg.clean(repo, extendnode.node())
736 raise util.Abort(_("nothing to extend"))
736 raise util.Abort(_("nothing to extend"))
737
737
738 if changesets == 0:
738 if changesets == 0:
739 print_result(nodes, good)
739 print_result(nodes, good)
740 else:
740 else:
741 assert len(nodes) == 1 # only a single node can be tested next
741 assert len(nodes) == 1 # only a single node can be tested next
742 node = nodes[0]
742 node = nodes[0]
743 # compute the approximate number of remaining tests
743 # compute the approximate number of remaining tests
744 tests, size = 0, 2
744 tests, size = 0, 2
745 while size <= changesets:
745 while size <= changesets:
746 tests, size = tests + 1, size * 2
746 tests, size = tests + 1, size * 2
747 rev = repo.changelog.rev(node)
747 rev = repo.changelog.rev(node)
748 ui.write(_("Testing changeset %d:%s "
748 ui.write(_("Testing changeset %d:%s "
749 "(%d changesets remaining, ~%d tests)\n")
749 "(%d changesets remaining, ~%d tests)\n")
750 % (rev, short(node), changesets, tests))
750 % (rev, short(node), changesets, tests))
751 state['current'] = [node]
751 state['current'] = [node]
752 hbisect.save_state(repo, state)
752 hbisect.save_state(repo, state)
753 if not noupdate:
753 if not noupdate:
754 cmdutil.bailifchanged(repo)
754 cmdutil.bailifchanged(repo)
755 return hg.clean(repo, node)
755 return hg.clean(repo, node)
756
756
757 @command('bookmarks',
757 @command('bookmarks',
758 [('f', 'force', False, _('force')),
758 [('f', 'force', False, _('force')),
759 ('r', 'rev', '', _('revision'), _('REV')),
759 ('r', 'rev', '', _('revision'), _('REV')),
760 ('d', 'delete', False, _('delete a given bookmark')),
760 ('d', 'delete', False, _('delete a given bookmark')),
761 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
761 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
762 ('i', 'inactive', False, _('mark a bookmark inactive'))],
762 ('i', 'inactive', False, _('mark a bookmark inactive'))],
763 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
763 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
764 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
764 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
765 rename=None, inactive=False):
765 rename=None, inactive=False):
766 '''track a line of development with movable markers
766 '''track a line of development with movable markers
767
767
768 Bookmarks are pointers to certain commits that move when committing.
768 Bookmarks are pointers to certain commits that move when committing.
769 Bookmarks are local. They can be renamed, copied and deleted. It is
769 Bookmarks are local. They can be renamed, copied and deleted. It is
770 possible to use :hg:`merge NAME` to merge from a given bookmark, and
770 possible to use :hg:`merge NAME` to merge from a given bookmark, and
771 :hg:`update NAME` to update to a given bookmark.
771 :hg:`update NAME` to update to a given bookmark.
772
772
773 You can use :hg:`bookmark NAME` to set a bookmark on the working
773 You can use :hg:`bookmark NAME` to set a bookmark on the working
774 directory's parent revision with the given name. If you specify
774 directory's parent revision with the given name. If you specify
775 a revision using -r REV (where REV may be an existing bookmark),
775 a revision using -r REV (where REV may be an existing bookmark),
776 the bookmark is assigned to that revision.
776 the bookmark is assigned to that revision.
777
777
778 Bookmarks can be pushed and pulled between repositories (see :hg:`help
778 Bookmarks can be pushed and pulled between repositories (see :hg:`help
779 push` and :hg:`help pull`). This requires both the local and remote
779 push` and :hg:`help pull`). This requires both the local and remote
780 repositories to support bookmarks. For versions prior to 1.8, this means
780 repositories to support bookmarks. For versions prior to 1.8, this means
781 the bookmarks extension must be enabled.
781 the bookmarks extension must be enabled.
782
782
783 With -i/--inactive, the new bookmark will not be made the active
783 With -i/--inactive, the new bookmark will not be made the active
784 bookmark. If -r/--rev is given, the new bookmark will not be made
784 bookmark. If -r/--rev is given, the new bookmark will not be made
785 active even if -i/--inactive is not given. If no NAME is given, the
785 active even if -i/--inactive is not given. If no NAME is given, the
786 current active bookmark will be marked inactive.
786 current active bookmark will be marked inactive.
787 '''
787 '''
788 hexfn = ui.debugflag and hex or short
788 hexfn = ui.debugflag and hex or short
789 marks = repo._bookmarks
789 marks = repo._bookmarks
790 cur = repo.changectx('.').node()
790 cur = repo.changectx('.').node()
791
791
792 if delete:
792 if delete:
793 if mark is None:
793 if mark is None:
794 raise util.Abort(_("bookmark name required"))
794 raise util.Abort(_("bookmark name required"))
795 if mark not in marks:
795 if mark not in marks:
796 raise util.Abort(_("bookmark '%s' does not exist") % mark)
796 raise util.Abort(_("bookmark '%s' does not exist") % mark)
797 if mark == repo._bookmarkcurrent:
797 if mark == repo._bookmarkcurrent:
798 bookmarks.setcurrent(repo, None)
798 bookmarks.setcurrent(repo, None)
799 del marks[mark]
799 del marks[mark]
800 bookmarks.write(repo)
800 bookmarks.write(repo)
801 return
801 return
802
802
803 if rename:
803 if rename:
804 if rename not in marks:
804 if rename not in marks:
805 raise util.Abort(_("bookmark '%s' does not exist") % rename)
805 raise util.Abort(_("bookmark '%s' does not exist") % rename)
806 if mark in marks and not force:
806 if mark in marks and not force:
807 raise util.Abort(_("bookmark '%s' already exists "
807 raise util.Abort(_("bookmark '%s' already exists "
808 "(use -f to force)") % mark)
808 "(use -f to force)") % mark)
809 if mark is None:
809 if mark is None:
810 raise util.Abort(_("new bookmark name required"))
810 raise util.Abort(_("new bookmark name required"))
811 marks[mark] = marks[rename]
811 marks[mark] = marks[rename]
812 if repo._bookmarkcurrent == rename and not inactive:
812 if repo._bookmarkcurrent == rename and not inactive:
813 bookmarks.setcurrent(repo, mark)
813 bookmarks.setcurrent(repo, mark)
814 del marks[rename]
814 del marks[rename]
815 bookmarks.write(repo)
815 bookmarks.write(repo)
816 return
816 return
817
817
818 if mark is not None:
818 if mark is not None:
819 if "\n" in mark:
819 if "\n" in mark:
820 raise util.Abort(_("bookmark name cannot contain newlines"))
820 raise util.Abort(_("bookmark name cannot contain newlines"))
821 mark = mark.strip()
821 mark = mark.strip()
822 if not mark:
822 if not mark:
823 raise util.Abort(_("bookmark names cannot consist entirely of "
823 raise util.Abort(_("bookmark names cannot consist entirely of "
824 "whitespace"))
824 "whitespace"))
825 if inactive and mark == repo._bookmarkcurrent:
825 if inactive and mark == repo._bookmarkcurrent:
826 bookmarks.setcurrent(repo, None)
826 bookmarks.setcurrent(repo, None)
827 return
827 return
828 if mark in marks and not force:
828 if mark in marks and not force:
829 raise util.Abort(_("bookmark '%s' already exists "
829 raise util.Abort(_("bookmark '%s' already exists "
830 "(use -f to force)") % mark)
830 "(use -f to force)") % mark)
831 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
831 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
832 and not force):
832 and not force):
833 raise util.Abort(
833 raise util.Abort(
834 _("a bookmark cannot have the name of an existing branch"))
834 _("a bookmark cannot have the name of an existing branch"))
835 if rev:
835 if rev:
836 marks[mark] = repo.lookup(rev)
836 marks[mark] = repo.lookup(rev)
837 else:
837 else:
838 marks[mark] = cur
838 marks[mark] = cur
839 if not inactive and cur == marks[mark]:
839 if not inactive and cur == marks[mark]:
840 bookmarks.setcurrent(repo, mark)
840 bookmarks.setcurrent(repo, mark)
841 bookmarks.write(repo)
841 bookmarks.write(repo)
842 return
842 return
843
843
844 if mark is None:
844 if mark is None:
845 if rev:
845 if rev:
846 raise util.Abort(_("bookmark name required"))
846 raise util.Abort(_("bookmark name required"))
847 if len(marks) == 0:
847 if len(marks) == 0:
848 ui.status(_("no bookmarks set\n"))
848 ui.status(_("no bookmarks set\n"))
849 else:
849 else:
850 for bmark, n in sorted(marks.iteritems()):
850 for bmark, n in sorted(marks.iteritems()):
851 current = repo._bookmarkcurrent
851 current = repo._bookmarkcurrent
852 if bmark == current and n == cur:
852 if bmark == current and n == cur:
853 prefix, label = '*', 'bookmarks.current'
853 prefix, label = '*', 'bookmarks.current'
854 else:
854 else:
855 prefix, label = ' ', ''
855 prefix, label = ' ', ''
856
856
857 if ui.quiet:
857 if ui.quiet:
858 ui.write("%s\n" % bmark, label=label)
858 ui.write("%s\n" % bmark, label=label)
859 else:
859 else:
860 ui.write(" %s %-25s %d:%s\n" % (
860 ui.write(" %s %-25s %d:%s\n" % (
861 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
861 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
862 label=label)
862 label=label)
863 return
863 return
864
864
865 @command('branch',
865 @command('branch',
866 [('f', 'force', None,
866 [('f', 'force', None,
867 _('set branch name even if it shadows an existing branch')),
867 _('set branch name even if it shadows an existing branch')),
868 ('C', 'clean', None, _('reset branch name to parent branch name'))],
868 ('C', 'clean', None, _('reset branch name to parent branch name'))],
869 _('[-fC] [NAME]'))
869 _('[-fC] [NAME]'))
870 def branch(ui, repo, label=None, **opts):
870 def branch(ui, repo, label=None, **opts):
871 """set or show the current branch name
871 """set or show the current branch name
872
872
873 .. note::
873 .. note::
874 Branch names are permanent and global. Use :hg:`bookmark` to create a
874 Branch names are permanent and global. Use :hg:`bookmark` to create a
875 light-weight bookmark instead. See :hg:`help glossary` for more
875 light-weight bookmark instead. See :hg:`help glossary` for more
876 information about named branches and bookmarks.
876 information about named branches and bookmarks.
877
877
878 With no argument, show the current branch name. With one argument,
878 With no argument, show the current branch name. With one argument,
879 set the working directory branch name (the branch will not exist
879 set the working directory branch name (the branch will not exist
880 in the repository until the next commit). Standard practice
880 in the repository until the next commit). Standard practice
881 recommends that primary development take place on the 'default'
881 recommends that primary development take place on the 'default'
882 branch.
882 branch.
883
883
884 Unless -f/--force is specified, branch will not let you set a
884 Unless -f/--force is specified, branch will not let you set a
885 branch name that already exists, even if it's inactive.
885 branch name that already exists, even if it's inactive.
886
886
887 Use -C/--clean to reset the working directory branch to that of
887 Use -C/--clean to reset the working directory branch to that of
888 the parent of the working directory, negating a previous branch
888 the parent of the working directory, negating a previous branch
889 change.
889 change.
890
890
891 Use the command :hg:`update` to switch to an existing branch. Use
891 Use the command :hg:`update` to switch to an existing branch. Use
892 :hg:`commit --close-branch` to mark this branch as closed.
892 :hg:`commit --close-branch` to mark this branch as closed.
893
893
894 Returns 0 on success.
894 Returns 0 on success.
895 """
895 """
896 if not opts.get('clean') and not label:
896 if not opts.get('clean') and not label:
897 ui.write("%s\n" % repo.dirstate.branch())
897 ui.write("%s\n" % repo.dirstate.branch())
898 return
898 return
899
899
900 wlock = repo.wlock()
900 wlock = repo.wlock()
901 try:
901 try:
902 if opts.get('clean'):
902 if opts.get('clean'):
903 label = repo[None].p1().branch()
903 label = repo[None].p1().branch()
904 repo.dirstate.setbranch(label)
904 repo.dirstate.setbranch(label)
905 ui.status(_('reset working directory to branch %s\n') % label)
905 ui.status(_('reset working directory to branch %s\n') % label)
906 elif label:
906 elif label:
907 if not opts.get('force') and label in repo.branchmap():
907 if not opts.get('force') and label in repo.branchmap():
908 if label not in [p.branch() for p in repo.parents()]:
908 if label not in [p.branch() for p in repo.parents()]:
909 raise util.Abort(_('a branch of the same name already'
909 raise util.Abort(_('a branch of the same name already'
910 ' exists'),
910 ' exists'),
911 # i18n: "it" refers to an existing branch
911 # i18n: "it" refers to an existing branch
912 hint=_("use 'hg update' to switch to it"))
912 hint=_("use 'hg update' to switch to it"))
913 repo.dirstate.setbranch(label)
913 repo.dirstate.setbranch(label)
914 ui.status(_('marked working directory as branch %s\n') % label)
914 ui.status(_('marked working directory as branch %s\n') % label)
915 ui.status(_('(branches are permanent and global, '
915 ui.status(_('(branches are permanent and global, '
916 'did you want a bookmark?)\n'))
916 'did you want a bookmark?)\n'))
917 finally:
917 finally:
918 wlock.release()
918 wlock.release()
919
919
920 @command('branches',
920 @command('branches',
921 [('a', 'active', False, _('show only branches that have unmerged heads')),
921 [('a', 'active', False, _('show only branches that have unmerged heads')),
922 ('c', 'closed', False, _('show normal and closed branches'))],
922 ('c', 'closed', False, _('show normal and closed branches'))],
923 _('[-ac]'))
923 _('[-ac]'))
924 def branches(ui, repo, active=False, closed=False):
924 def branches(ui, repo, active=False, closed=False):
925 """list repository named branches
925 """list repository named branches
926
926
927 List the repository's named branches, indicating which ones are
927 List the repository's named branches, indicating which ones are
928 inactive. If -c/--closed is specified, also list branches which have
928 inactive. If -c/--closed is specified, also list branches which have
929 been marked closed (see :hg:`commit --close-branch`).
929 been marked closed (see :hg:`commit --close-branch`).
930
930
931 If -a/--active is specified, only show active branches. A branch
931 If -a/--active is specified, only show active branches. A branch
932 is considered active if it contains repository heads.
932 is considered active if it contains repository heads.
933
933
934 Use the command :hg:`update` to switch to an existing branch.
934 Use the command :hg:`update` to switch to an existing branch.
935
935
936 Returns 0.
936 Returns 0.
937 """
937 """
938
938
939 hexfunc = ui.debugflag and hex or short
939 hexfunc = ui.debugflag and hex or short
940
940
941 activebranches = set([repo[n].branch() for n in repo.heads()])
941 activebranches = set([repo[n].branch() for n in repo.heads()])
942 branches = []
942 branches = []
943 for tag, heads in repo.branchmap().iteritems():
943 for tag, heads in repo.branchmap().iteritems():
944 for h in reversed(heads):
944 for h in reversed(heads):
945 ctx = repo[h]
945 ctx = repo[h]
946 isopen = not ctx.closesbranch()
946 isopen = not ctx.closesbranch()
947 if isopen:
947 if isopen:
948 tip = ctx
948 tip = ctx
949 break
949 break
950 else:
950 else:
951 tip = repo[heads[-1]]
951 tip = repo[heads[-1]]
952 isactive = tag in activebranches and isopen
952 isactive = tag in activebranches and isopen
953 branches.append((tip, isactive, isopen))
953 branches.append((tip, isactive, isopen))
954 branches.sort(key=lambda i: (i[1], i[0].rev(), i[0].branch(), i[2]),
954 branches.sort(key=lambda i: (i[1], i[0].rev(), i[0].branch(), i[2]),
955 reverse=True)
955 reverse=True)
956
956
957 for ctx, isactive, isopen in branches:
957 for ctx, isactive, isopen in branches:
958 if (not active) or isactive:
958 if (not active) or isactive:
959 if isactive:
959 if isactive:
960 label = 'branches.active'
960 label = 'branches.active'
961 notice = ''
961 notice = ''
962 elif not isopen:
962 elif not isopen:
963 if not closed:
963 if not closed:
964 continue
964 continue
965 label = 'branches.closed'
965 label = 'branches.closed'
966 notice = _(' (closed)')
966 notice = _(' (closed)')
967 else:
967 else:
968 label = 'branches.inactive'
968 label = 'branches.inactive'
969 notice = _(' (inactive)')
969 notice = _(' (inactive)')
970 if ctx.branch() == repo.dirstate.branch():
970 if ctx.branch() == repo.dirstate.branch():
971 label = 'branches.current'
971 label = 'branches.current'
972 rev = str(ctx.rev()).rjust(31 - encoding.colwidth(ctx.branch()))
972 rev = str(ctx.rev()).rjust(31 - encoding.colwidth(ctx.branch()))
973 rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
973 rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
974 'log.changeset')
974 'log.changeset')
975 tag = ui.label(ctx.branch(), label)
975 tag = ui.label(ctx.branch(), label)
976 if ui.quiet:
976 if ui.quiet:
977 ui.write("%s\n" % tag)
977 ui.write("%s\n" % tag)
978 else:
978 else:
979 ui.write("%s %s%s\n" % (tag, rev, notice))
979 ui.write("%s %s%s\n" % (tag, rev, notice))
980
980
981 @command('bundle',
981 @command('bundle',
982 [('f', 'force', None, _('run even when the destination is unrelated')),
982 [('f', 'force', None, _('run even when the destination is unrelated')),
983 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
983 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
984 _('REV')),
984 _('REV')),
985 ('b', 'branch', [], _('a specific branch you would like to bundle'),
985 ('b', 'branch', [], _('a specific branch you would like to bundle'),
986 _('BRANCH')),
986 _('BRANCH')),
987 ('', 'base', [],
987 ('', 'base', [],
988 _('a base changeset assumed to be available at the destination'),
988 _('a base changeset assumed to be available at the destination'),
989 _('REV')),
989 _('REV')),
990 ('a', 'all', None, _('bundle all changesets in the repository')),
990 ('a', 'all', None, _('bundle all changesets in the repository')),
991 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
991 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
992 ] + remoteopts,
992 ] + remoteopts,
993 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
993 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
994 def bundle(ui, repo, fname, dest=None, **opts):
994 def bundle(ui, repo, fname, dest=None, **opts):
995 """create a changegroup file
995 """create a changegroup file
996
996
997 Generate a compressed changegroup file collecting changesets not
997 Generate a compressed changegroup file collecting changesets not
998 known to be in another repository.
998 known to be in another repository.
999
999
1000 If you omit the destination repository, then hg assumes the
1000 If you omit the destination repository, then hg assumes the
1001 destination will have all the nodes you specify with --base
1001 destination will have all the nodes you specify with --base
1002 parameters. To create a bundle containing all changesets, use
1002 parameters. To create a bundle containing all changesets, use
1003 -a/--all (or --base null).
1003 -a/--all (or --base null).
1004
1004
1005 You can change compression method with the -t/--type option.
1005 You can change compression method with the -t/--type option.
1006 The available compression methods are: none, bzip2, and
1006 The available compression methods are: none, bzip2, and
1007 gzip (by default, bundles are compressed using bzip2).
1007 gzip (by default, bundles are compressed using bzip2).
1008
1008
1009 The bundle file can then be transferred using conventional means
1009 The bundle file can then be transferred using conventional means
1010 and applied to another repository with the unbundle or pull
1010 and applied to another repository with the unbundle or pull
1011 command. This is useful when direct push and pull are not
1011 command. This is useful when direct push and pull are not
1012 available or when exporting an entire repository is undesirable.
1012 available or when exporting an entire repository is undesirable.
1013
1013
1014 Applying bundles preserves all changeset contents including
1014 Applying bundles preserves all changeset contents including
1015 permissions, copy/rename information, and revision history.
1015 permissions, copy/rename information, and revision history.
1016
1016
1017 Returns 0 on success, 1 if no changes found.
1017 Returns 0 on success, 1 if no changes found.
1018 """
1018 """
1019 revs = None
1019 revs = None
1020 if 'rev' in opts:
1020 if 'rev' in opts:
1021 revs = scmutil.revrange(repo, opts['rev'])
1021 revs = scmutil.revrange(repo, opts['rev'])
1022
1022
1023 bundletype = opts.get('type', 'bzip2').lower()
1023 bundletype = opts.get('type', 'bzip2').lower()
1024 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1024 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1025 bundletype = btypes.get(bundletype)
1025 bundletype = btypes.get(bundletype)
1026 if bundletype not in changegroup.bundletypes:
1026 if bundletype not in changegroup.bundletypes:
1027 raise util.Abort(_('unknown bundle type specified with --type'))
1027 raise util.Abort(_('unknown bundle type specified with --type'))
1028
1028
1029 if opts.get('all'):
1029 if opts.get('all'):
1030 base = ['null']
1030 base = ['null']
1031 else:
1031 else:
1032 base = scmutil.revrange(repo, opts.get('base'))
1032 base = scmutil.revrange(repo, opts.get('base'))
1033 if base:
1033 if base:
1034 if dest:
1034 if dest:
1035 raise util.Abort(_("--base is incompatible with specifying "
1035 raise util.Abort(_("--base is incompatible with specifying "
1036 "a destination"))
1036 "a destination"))
1037 common = [repo.lookup(rev) for rev in base]
1037 common = [repo.lookup(rev) for rev in base]
1038 heads = revs and map(repo.lookup, revs) or revs
1038 heads = revs and map(repo.lookup, revs) or revs
1039 cg = repo.getbundle('bundle', heads=heads, common=common)
1039 cg = repo.getbundle('bundle', heads=heads, common=common)
1040 outgoing = None
1040 outgoing = None
1041 else:
1041 else:
1042 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1042 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1043 dest, branches = hg.parseurl(dest, opts.get('branch'))
1043 dest, branches = hg.parseurl(dest, opts.get('branch'))
1044 other = hg.peer(repo, opts, dest)
1044 other = hg.peer(repo, opts, dest)
1045 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
1045 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
1046 heads = revs and map(repo.lookup, revs) or revs
1046 heads = revs and map(repo.lookup, revs) or revs
1047 outgoing = discovery.findcommonoutgoing(repo, other,
1047 outgoing = discovery.findcommonoutgoing(repo, other,
1048 onlyheads=heads,
1048 onlyheads=heads,
1049 force=opts.get('force'),
1049 force=opts.get('force'),
1050 portable=True)
1050 portable=True)
1051 cg = repo.getlocalbundle('bundle', outgoing)
1051 cg = repo.getlocalbundle('bundle', outgoing)
1052 if not cg:
1052 if not cg:
1053 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1053 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1054 return 1
1054 return 1
1055
1055
1056 changegroup.writebundle(cg, fname, bundletype)
1056 changegroup.writebundle(cg, fname, bundletype)
1057
1057
1058 @command('cat',
1058 @command('cat',
1059 [('o', 'output', '',
1059 [('o', 'output', '',
1060 _('print output to file with formatted name'), _('FORMAT')),
1060 _('print output to file with formatted name'), _('FORMAT')),
1061 ('r', 'rev', '', _('print the given revision'), _('REV')),
1061 ('r', 'rev', '', _('print the given revision'), _('REV')),
1062 ('', 'decode', None, _('apply any matching decode filter')),
1062 ('', 'decode', None, _('apply any matching decode filter')),
1063 ] + walkopts,
1063 ] + walkopts,
1064 _('[OPTION]... FILE...'))
1064 _('[OPTION]... FILE...'))
1065 def cat(ui, repo, file1, *pats, **opts):
1065 def cat(ui, repo, file1, *pats, **opts):
1066 """output the current or given revision of files
1066 """output the current or given revision of files
1067
1067
1068 Print the specified files as they were at the given revision. If
1068 Print the specified files as they were at the given revision. If
1069 no revision is given, the parent of the working directory is used,
1069 no revision is given, the parent of the working directory is used,
1070 or tip if no revision is checked out.
1070 or tip if no revision is checked out.
1071
1071
1072 Output may be to a file, in which case the name of the file is
1072 Output may be to a file, in which case the name of the file is
1073 given using a format string. The formatting rules are the same as
1073 given using a format string. The formatting rules are the same as
1074 for the export command, with the following additions:
1074 for the export command, with the following additions:
1075
1075
1076 :``%s``: basename of file being printed
1076 :``%s``: basename of file being printed
1077 :``%d``: dirname of file being printed, or '.' if in repository root
1077 :``%d``: dirname of file being printed, or '.' if in repository root
1078 :``%p``: root-relative path name of file being printed
1078 :``%p``: root-relative path name of file being printed
1079
1079
1080 Returns 0 on success.
1080 Returns 0 on success.
1081 """
1081 """
1082 ctx = scmutil.revsingle(repo, opts.get('rev'))
1082 ctx = scmutil.revsingle(repo, opts.get('rev'))
1083 err = 1
1083 err = 1
1084 m = scmutil.match(ctx, (file1,) + pats, opts)
1084 m = scmutil.match(ctx, (file1,) + pats, opts)
1085 for abs in ctx.walk(m):
1085 for abs in ctx.walk(m):
1086 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1086 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1087 pathname=abs)
1087 pathname=abs)
1088 data = ctx[abs].data()
1088 data = ctx[abs].data()
1089 if opts.get('decode'):
1089 if opts.get('decode'):
1090 data = repo.wwritedata(abs, data)
1090 data = repo.wwritedata(abs, data)
1091 fp.write(data)
1091 fp.write(data)
1092 fp.close()
1092 fp.close()
1093 err = 0
1093 err = 0
1094 return err
1094 return err
1095
1095
1096 @command('^clone',
1096 @command('^clone',
1097 [('U', 'noupdate', None,
1097 [('U', 'noupdate', None,
1098 _('the clone will include an empty working copy (only a repository)')),
1098 _('the clone will include an empty working copy (only a repository)')),
1099 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1099 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1100 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1100 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1101 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1101 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1102 ('', 'pull', None, _('use pull protocol to copy metadata')),
1102 ('', 'pull', None, _('use pull protocol to copy metadata')),
1103 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1103 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1104 ] + remoteopts,
1104 ] + remoteopts,
1105 _('[OPTION]... SOURCE [DEST]'))
1105 _('[OPTION]... SOURCE [DEST]'))
1106 def clone(ui, source, dest=None, **opts):
1106 def clone(ui, source, dest=None, **opts):
1107 """make a copy of an existing repository
1107 """make a copy of an existing repository
1108
1108
1109 Create a copy of an existing repository in a new directory.
1109 Create a copy of an existing repository in a new directory.
1110
1110
1111 If no destination directory name is specified, it defaults to the
1111 If no destination directory name is specified, it defaults to the
1112 basename of the source.
1112 basename of the source.
1113
1113
1114 The location of the source is added to the new repository's
1114 The location of the source is added to the new repository's
1115 ``.hg/hgrc`` file, as the default to be used for future pulls.
1115 ``.hg/hgrc`` file, as the default to be used for future pulls.
1116
1116
1117 Only local paths and ``ssh://`` URLs are supported as
1117 Only local paths and ``ssh://`` URLs are supported as
1118 destinations. For ``ssh://`` destinations, no working directory or
1118 destinations. For ``ssh://`` destinations, no working directory or
1119 ``.hg/hgrc`` will be created on the remote side.
1119 ``.hg/hgrc`` will be created on the remote side.
1120
1120
1121 To pull only a subset of changesets, specify one or more revisions
1121 To pull only a subset of changesets, specify one or more revisions
1122 identifiers with -r/--rev or branches with -b/--branch. The
1122 identifiers with -r/--rev or branches with -b/--branch. The
1123 resulting clone will contain only the specified changesets and
1123 resulting clone will contain only the specified changesets and
1124 their ancestors. These options (or 'clone src#rev dest') imply
1124 their ancestors. These options (or 'clone src#rev dest') imply
1125 --pull, even for local source repositories. Note that specifying a
1125 --pull, even for local source repositories. Note that specifying a
1126 tag will include the tagged changeset but not the changeset
1126 tag will include the tagged changeset but not the changeset
1127 containing the tag.
1127 containing the tag.
1128
1128
1129 To check out a particular version, use -u/--update, or
1129 To check out a particular version, use -u/--update, or
1130 -U/--noupdate to create a clone with no working directory.
1130 -U/--noupdate to create a clone with no working directory.
1131
1131
1132 .. container:: verbose
1132 .. container:: verbose
1133
1133
1134 For efficiency, hardlinks are used for cloning whenever the
1134 For efficiency, hardlinks are used for cloning whenever the
1135 source and destination are on the same filesystem (note this
1135 source and destination are on the same filesystem (note this
1136 applies only to the repository data, not to the working
1136 applies only to the repository data, not to the working
1137 directory). Some filesystems, such as AFS, implement hardlinking
1137 directory). Some filesystems, such as AFS, implement hardlinking
1138 incorrectly, but do not report errors. In these cases, use the
1138 incorrectly, but do not report errors. In these cases, use the
1139 --pull option to avoid hardlinking.
1139 --pull option to avoid hardlinking.
1140
1140
1141 In some cases, you can clone repositories and the working
1141 In some cases, you can clone repositories and the working
1142 directory using full hardlinks with ::
1142 directory using full hardlinks with ::
1143
1143
1144 $ cp -al REPO REPOCLONE
1144 $ cp -al REPO REPOCLONE
1145
1145
1146 This is the fastest way to clone, but it is not always safe. The
1146 This is the fastest way to clone, but it is not always safe. The
1147 operation is not atomic (making sure REPO is not modified during
1147 operation is not atomic (making sure REPO is not modified during
1148 the operation is up to you) and you have to make sure your
1148 the operation is up to you) and you have to make sure your
1149 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1149 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1150 so). Also, this is not compatible with certain extensions that
1150 so). Also, this is not compatible with certain extensions that
1151 place their metadata under the .hg directory, such as mq.
1151 place their metadata under the .hg directory, such as mq.
1152
1152
1153 Mercurial will update the working directory to the first applicable
1153 Mercurial will update the working directory to the first applicable
1154 revision from this list:
1154 revision from this list:
1155
1155
1156 a) null if -U or the source repository has no changesets
1156 a) null if -U or the source repository has no changesets
1157 b) if -u . and the source repository is local, the first parent of
1157 b) if -u . and the source repository is local, the first parent of
1158 the source repository's working directory
1158 the source repository's working directory
1159 c) the changeset specified with -u (if a branch name, this means the
1159 c) the changeset specified with -u (if a branch name, this means the
1160 latest head of that branch)
1160 latest head of that branch)
1161 d) the changeset specified with -r
1161 d) the changeset specified with -r
1162 e) the tipmost head specified with -b
1162 e) the tipmost head specified with -b
1163 f) the tipmost head specified with the url#branch source syntax
1163 f) the tipmost head specified with the url#branch source syntax
1164 g) the tipmost head of the default branch
1164 g) the tipmost head of the default branch
1165 h) tip
1165 h) tip
1166
1166
1167 Examples:
1167 Examples:
1168
1168
1169 - clone a remote repository to a new directory named hg/::
1169 - clone a remote repository to a new directory named hg/::
1170
1170
1171 hg clone http://selenic.com/hg
1171 hg clone http://selenic.com/hg
1172
1172
1173 - create a lightweight local clone::
1173 - create a lightweight local clone::
1174
1174
1175 hg clone project/ project-feature/
1175 hg clone project/ project-feature/
1176
1176
1177 - clone from an absolute path on an ssh server (note double-slash)::
1177 - clone from an absolute path on an ssh server (note double-slash)::
1178
1178
1179 hg clone ssh://user@server//home/projects/alpha/
1179 hg clone ssh://user@server//home/projects/alpha/
1180
1180
1181 - do a high-speed clone over a LAN while checking out a
1181 - do a high-speed clone over a LAN while checking out a
1182 specified version::
1182 specified version::
1183
1183
1184 hg clone --uncompressed http://server/repo -u 1.5
1184 hg clone --uncompressed http://server/repo -u 1.5
1185
1185
1186 - create a repository without changesets after a particular revision::
1186 - create a repository without changesets after a particular revision::
1187
1187
1188 hg clone -r 04e544 experimental/ good/
1188 hg clone -r 04e544 experimental/ good/
1189
1189
1190 - clone (and track) a particular named branch::
1190 - clone (and track) a particular named branch::
1191
1191
1192 hg clone http://selenic.com/hg#stable
1192 hg clone http://selenic.com/hg#stable
1193
1193
1194 See :hg:`help urls` for details on specifying URLs.
1194 See :hg:`help urls` for details on specifying URLs.
1195
1195
1196 Returns 0 on success.
1196 Returns 0 on success.
1197 """
1197 """
1198 if opts.get('noupdate') and opts.get('updaterev'):
1198 if opts.get('noupdate') and opts.get('updaterev'):
1199 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1199 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1200
1200
1201 r = hg.clone(ui, opts, source, dest,
1201 r = hg.clone(ui, opts, source, dest,
1202 pull=opts.get('pull'),
1202 pull=opts.get('pull'),
1203 stream=opts.get('uncompressed'),
1203 stream=opts.get('uncompressed'),
1204 rev=opts.get('rev'),
1204 rev=opts.get('rev'),
1205 update=opts.get('updaterev') or not opts.get('noupdate'),
1205 update=opts.get('updaterev') or not opts.get('noupdate'),
1206 branch=opts.get('branch'))
1206 branch=opts.get('branch'))
1207
1207
1208 return r is None
1208 return r is None
1209
1209
1210 @command('^commit|ci',
1210 @command('^commit|ci',
1211 [('A', 'addremove', None,
1211 [('A', 'addremove', None,
1212 _('mark new/missing files as added/removed before committing')),
1212 _('mark new/missing files as added/removed before committing')),
1213 ('', 'close-branch', None,
1213 ('', 'close-branch', None,
1214 _('mark a branch as closed, hiding it from the branch list')),
1214 _('mark a branch as closed, hiding it from the branch list')),
1215 ('', 'amend', None, _('amend the parent of the working dir')),
1215 ('', 'amend', None, _('amend the parent of the working dir')),
1216 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1216 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1217 _('[OPTION]... [FILE]...'))
1217 _('[OPTION]... [FILE]...'))
1218 def commit(ui, repo, *pats, **opts):
1218 def commit(ui, repo, *pats, **opts):
1219 """commit the specified files or all outstanding changes
1219 """commit the specified files or all outstanding changes
1220
1220
1221 Commit changes to the given files into the repository. Unlike a
1221 Commit changes to the given files into the repository. Unlike a
1222 centralized SCM, this operation is a local operation. See
1222 centralized SCM, this operation is a local operation. See
1223 :hg:`push` for a way to actively distribute your changes.
1223 :hg:`push` for a way to actively distribute your changes.
1224
1224
1225 If a list of files is omitted, all changes reported by :hg:`status`
1225 If a list of files is omitted, all changes reported by :hg:`status`
1226 will be committed.
1226 will be committed.
1227
1227
1228 If you are committing the result of a merge, do not provide any
1228 If you are committing the result of a merge, do not provide any
1229 filenames or -I/-X filters.
1229 filenames or -I/-X filters.
1230
1230
1231 If no commit message is specified, Mercurial starts your
1231 If no commit message is specified, Mercurial starts your
1232 configured editor where you can enter a message. In case your
1232 configured editor where you can enter a message. In case your
1233 commit fails, you will find a backup of your message in
1233 commit fails, you will find a backup of your message in
1234 ``.hg/last-message.txt``.
1234 ``.hg/last-message.txt``.
1235
1235
1236 The --amend flag can be used to amend the parent of the
1236 The --amend flag can be used to amend the parent of the
1237 working directory with a new commit that contains the changes
1237 working directory with a new commit that contains the changes
1238 in the parent in addition to those currently reported by :hg:`status`,
1238 in the parent in addition to those currently reported by :hg:`status`,
1239 if there are any. The old commit is stored in a backup bundle in
1239 if there are any. The old commit is stored in a backup bundle in
1240 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1240 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1241 on how to restore it).
1241 on how to restore it).
1242
1242
1243 Message, user and date are taken from the amended commit unless
1243 Message, user and date are taken from the amended commit unless
1244 specified. When a message isn't specified on the command line,
1244 specified. When a message isn't specified on the command line,
1245 the editor will open with the message of the amended commit.
1245 the editor will open with the message of the amended commit.
1246
1246
1247 It is not possible to amend public changesets (see :hg:`help phases`)
1247 It is not possible to amend public changesets (see :hg:`help phases`)
1248 or changesets that have children.
1248 or changesets that have children.
1249
1249
1250 See :hg:`help dates` for a list of formats valid for -d/--date.
1250 See :hg:`help dates` for a list of formats valid for -d/--date.
1251
1251
1252 Returns 0 on success, 1 if nothing changed.
1252 Returns 0 on success, 1 if nothing changed.
1253 """
1253 """
1254 if opts.get('subrepos'):
1254 if opts.get('subrepos'):
1255 # Let --subrepos on the command line override config setting.
1255 # Let --subrepos on the command line override config setting.
1256 ui.setconfig('ui', 'commitsubrepos', True)
1256 ui.setconfig('ui', 'commitsubrepos', True)
1257
1257
1258 extra = {}
1258 extra = {}
1259 if opts.get('close_branch'):
1259 if opts.get('close_branch'):
1260 if repo['.'].node() not in repo.branchheads():
1260 if repo['.'].node() not in repo.branchheads():
1261 # The topo heads set is included in the branch heads set of the
1261 # The topo heads set is included in the branch heads set of the
1262 # current branch, so it's sufficient to test branchheads
1262 # current branch, so it's sufficient to test branchheads
1263 raise util.Abort(_('can only close branch heads'))
1263 raise util.Abort(_('can only close branch heads'))
1264 extra['close'] = 1
1264 extra['close'] = 1
1265
1265
1266 branch = repo[None].branch()
1266 branch = repo[None].branch()
1267 bheads = repo.branchheads(branch)
1267 bheads = repo.branchheads(branch)
1268
1268
1269 if opts.get('amend'):
1269 if opts.get('amend'):
1270 if ui.configbool('ui', 'commitsubrepos'):
1270 if ui.configbool('ui', 'commitsubrepos'):
1271 raise util.Abort(_('cannot amend recursively'))
1271 raise util.Abort(_('cannot amend recursively'))
1272
1272
1273 old = repo['.']
1273 old = repo['.']
1274 if old.phase() == phases.public:
1274 if old.phase() == phases.public:
1275 raise util.Abort(_('cannot amend public changesets'))
1275 raise util.Abort(_('cannot amend public changesets'))
1276 if len(old.parents()) > 1:
1276 if len(old.parents()) > 1:
1277 raise util.Abort(_('cannot amend merge changesets'))
1277 raise util.Abort(_('cannot amend merge changesets'))
1278 if len(repo[None].parents()) > 1:
1278 if len(repo[None].parents()) > 1:
1279 raise util.Abort(_('cannot amend while merging'))
1279 raise util.Abort(_('cannot amend while merging'))
1280 if old.children():
1280 if old.children():
1281 raise util.Abort(_('cannot amend changeset with children'))
1281 raise util.Abort(_('cannot amend changeset with children'))
1282
1282
1283 e = cmdutil.commiteditor
1283 e = cmdutil.commiteditor
1284 if opts.get('force_editor'):
1284 if opts.get('force_editor'):
1285 e = cmdutil.commitforceeditor
1285 e = cmdutil.commitforceeditor
1286
1286
1287 def commitfunc(ui, repo, message, match, opts):
1287 def commitfunc(ui, repo, message, match, opts):
1288 editor = e
1288 editor = e
1289 # message contains text from -m or -l, if it's empty,
1289 # message contains text from -m or -l, if it's empty,
1290 # open the editor with the old message
1290 # open the editor with the old message
1291 if not message:
1291 if not message:
1292 message = old.description()
1292 message = old.description()
1293 editor = cmdutil.commitforceeditor
1293 editor = cmdutil.commitforceeditor
1294 return repo.commit(message,
1294 return repo.commit(message,
1295 opts.get('user') or old.user(),
1295 opts.get('user') or old.user(),
1296 opts.get('date') or old.date(),
1296 opts.get('date') or old.date(),
1297 match,
1297 match,
1298 editor=editor,
1298 editor=editor,
1299 extra=extra)
1299 extra=extra)
1300
1300
1301 current = repo._bookmarkcurrent
1301 current = repo._bookmarkcurrent
1302 marks = old.bookmarks()
1302 marks = old.bookmarks()
1303 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1303 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1304 if node == old.node():
1304 if node == old.node():
1305 ui.status(_("nothing changed\n"))
1305 ui.status(_("nothing changed\n"))
1306 return 1
1306 return 1
1307 elif marks:
1307 elif marks:
1308 ui.debug('moving bookmarks %r from %s to %s\n' %
1308 ui.debug('moving bookmarks %r from %s to %s\n' %
1309 (marks, old.hex(), hex(node)))
1309 (marks, old.hex(), hex(node)))
1310 for bm in marks:
1310 for bm in marks:
1311 repo._bookmarks[bm] = node
1311 repo._bookmarks[bm] = node
1312 if bm == current:
1312 if bm == current:
1313 bookmarks.setcurrent(repo, bm)
1313 bookmarks.setcurrent(repo, bm)
1314 bookmarks.write(repo)
1314 bookmarks.write(repo)
1315 else:
1315 else:
1316 e = cmdutil.commiteditor
1316 e = cmdutil.commiteditor
1317 if opts.get('force_editor'):
1317 if opts.get('force_editor'):
1318 e = cmdutil.commitforceeditor
1318 e = cmdutil.commitforceeditor
1319
1319
1320 def commitfunc(ui, repo, message, match, opts):
1320 def commitfunc(ui, repo, message, match, opts):
1321 return repo.commit(message, opts.get('user'), opts.get('date'),
1321 return repo.commit(message, opts.get('user'), opts.get('date'),
1322 match, editor=e, extra=extra)
1322 match, editor=e, extra=extra)
1323
1323
1324 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1324 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1325
1325
1326 if not node:
1326 if not node:
1327 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1327 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1328 if stat[3]:
1328 if stat[3]:
1329 ui.status(_("nothing changed (%d missing files, see "
1329 ui.status(_("nothing changed (%d missing files, see "
1330 "'hg status')\n") % len(stat[3]))
1330 "'hg status')\n") % len(stat[3]))
1331 else:
1331 else:
1332 ui.status(_("nothing changed\n"))
1332 ui.status(_("nothing changed\n"))
1333 return 1
1333 return 1
1334
1334
1335 ctx = repo[node]
1335 ctx = repo[node]
1336 parents = ctx.parents()
1336 parents = ctx.parents()
1337
1337
1338 if (not opts.get('amend') and bheads and node not in bheads and not
1338 if (not opts.get('amend') and bheads and node not in bheads and not
1339 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1339 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1340 ui.status(_('created new head\n'))
1340 ui.status(_('created new head\n'))
1341 # The message is not printed for initial roots. For the other
1341 # The message is not printed for initial roots. For the other
1342 # changesets, it is printed in the following situations:
1342 # changesets, it is printed in the following situations:
1343 #
1343 #
1344 # Par column: for the 2 parents with ...
1344 # Par column: for the 2 parents with ...
1345 # N: null or no parent
1345 # N: null or no parent
1346 # B: parent is on another named branch
1346 # B: parent is on another named branch
1347 # C: parent is a regular non head changeset
1347 # C: parent is a regular non head changeset
1348 # H: parent was a branch head of the current branch
1348 # H: parent was a branch head of the current branch
1349 # Msg column: whether we print "created new head" message
1349 # Msg column: whether we print "created new head" message
1350 # In the following, it is assumed that there already exists some
1350 # In the following, it is assumed that there already exists some
1351 # initial branch heads of the current branch, otherwise nothing is
1351 # initial branch heads of the current branch, otherwise nothing is
1352 # printed anyway.
1352 # printed anyway.
1353 #
1353 #
1354 # Par Msg Comment
1354 # Par Msg Comment
1355 # NN y additional topo root
1355 # N N y additional topo root
1356 #
1356 #
1357 # BN y additional branch root
1357 # B N y additional branch root
1358 # CN y additional topo head
1358 # C N y additional topo head
1359 # HN n usual case
1359 # H N n usual case
1360 #
1360 #
1361 # BB y weird additional branch root
1361 # B B y weird additional branch root
1362 # CB y branch merge
1362 # C B y branch merge
1363 # HB n merge with named branch
1363 # H B n merge with named branch
1364 #
1364 #
1365 # CC y additional head from merge
1365 # C C y additional head from merge
1366 # CH n merge with a head
1366 # C H n merge with a head
1367 #
1367 #
1368 # HH n head merge: head count decreases
1368 # H H n head merge: head count decreases
1369
1369
1370 if not opts.get('close_branch'):
1370 if not opts.get('close_branch'):
1371 for r in parents:
1371 for r in parents:
1372 if r.closesbranch() and r.branch() == branch:
1372 if r.closesbranch() and r.branch() == branch:
1373 ui.status(_('reopening closed branch head %d\n') % r)
1373 ui.status(_('reopening closed branch head %d\n') % r)
1374
1374
1375 if ui.debugflag:
1375 if ui.debugflag:
1376 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1376 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1377 elif ui.verbose:
1377 elif ui.verbose:
1378 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1378 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1379
1379
1380 @command('copy|cp',
1380 @command('copy|cp',
1381 [('A', 'after', None, _('record a copy that has already occurred')),
1381 [('A', 'after', None, _('record a copy that has already occurred')),
1382 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1382 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1383 ] + walkopts + dryrunopts,
1383 ] + walkopts + dryrunopts,
1384 _('[OPTION]... [SOURCE]... DEST'))
1384 _('[OPTION]... [SOURCE]... DEST'))
1385 def copy(ui, repo, *pats, **opts):
1385 def copy(ui, repo, *pats, **opts):
1386 """mark files as copied for the next commit
1386 """mark files as copied for the next commit
1387
1387
1388 Mark dest as having copies of source files. If dest is a
1388 Mark dest as having copies of source files. If dest is a
1389 directory, copies are put in that directory. If dest is a file,
1389 directory, copies are put in that directory. If dest is a file,
1390 the source must be a single file.
1390 the source must be a single file.
1391
1391
1392 By default, this command copies the contents of files as they
1392 By default, this command copies the contents of files as they
1393 exist in the working directory. If invoked with -A/--after, the
1393 exist in the working directory. If invoked with -A/--after, the
1394 operation is recorded, but no copying is performed.
1394 operation is recorded, but no copying is performed.
1395
1395
1396 This command takes effect with the next commit. To undo a copy
1396 This command takes effect with the next commit. To undo a copy
1397 before that, see :hg:`revert`.
1397 before that, see :hg:`revert`.
1398
1398
1399 Returns 0 on success, 1 if errors are encountered.
1399 Returns 0 on success, 1 if errors are encountered.
1400 """
1400 """
1401 wlock = repo.wlock(False)
1401 wlock = repo.wlock(False)
1402 try:
1402 try:
1403 return cmdutil.copy(ui, repo, pats, opts)
1403 return cmdutil.copy(ui, repo, pats, opts)
1404 finally:
1404 finally:
1405 wlock.release()
1405 wlock.release()
1406
1406
1407 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1407 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1408 def debugancestor(ui, repo, *args):
1408 def debugancestor(ui, repo, *args):
1409 """find the ancestor revision of two revisions in a given index"""
1409 """find the ancestor revision of two revisions in a given index"""
1410 if len(args) == 3:
1410 if len(args) == 3:
1411 index, rev1, rev2 = args
1411 index, rev1, rev2 = args
1412 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1412 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1413 lookup = r.lookup
1413 lookup = r.lookup
1414 elif len(args) == 2:
1414 elif len(args) == 2:
1415 if not repo:
1415 if not repo:
1416 raise util.Abort(_("there is no Mercurial repository here "
1416 raise util.Abort(_("there is no Mercurial repository here "
1417 "(.hg not found)"))
1417 "(.hg not found)"))
1418 rev1, rev2 = args
1418 rev1, rev2 = args
1419 r = repo.changelog
1419 r = repo.changelog
1420 lookup = repo.lookup
1420 lookup = repo.lookup
1421 else:
1421 else:
1422 raise util.Abort(_('either two or three arguments required'))
1422 raise util.Abort(_('either two or three arguments required'))
1423 a = r.ancestor(lookup(rev1), lookup(rev2))
1423 a = r.ancestor(lookup(rev1), lookup(rev2))
1424 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1424 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1425
1425
1426 @command('debugbuilddag',
1426 @command('debugbuilddag',
1427 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1427 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1428 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1428 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1429 ('n', 'new-file', None, _('add new file at each rev'))],
1429 ('n', 'new-file', None, _('add new file at each rev'))],
1430 _('[OPTION]... [TEXT]'))
1430 _('[OPTION]... [TEXT]'))
1431 def debugbuilddag(ui, repo, text=None,
1431 def debugbuilddag(ui, repo, text=None,
1432 mergeable_file=False,
1432 mergeable_file=False,
1433 overwritten_file=False,
1433 overwritten_file=False,
1434 new_file=False):
1434 new_file=False):
1435 """builds a repo with a given DAG from scratch in the current empty repo
1435 """builds a repo with a given DAG from scratch in the current empty repo
1436
1436
1437 The description of the DAG is read from stdin if not given on the
1437 The description of the DAG is read from stdin if not given on the
1438 command line.
1438 command line.
1439
1439
1440 Elements:
1440 Elements:
1441
1441
1442 - "+n" is a linear run of n nodes based on the current default parent
1442 - "+n" is a linear run of n nodes based on the current default parent
1443 - "." is a single node based on the current default parent
1443 - "." is a single node based on the current default parent
1444 - "$" resets the default parent to null (implied at the start);
1444 - "$" resets the default parent to null (implied at the start);
1445 otherwise the default parent is always the last node created
1445 otherwise the default parent is always the last node created
1446 - "<p" sets the default parent to the backref p
1446 - "<p" sets the default parent to the backref p
1447 - "*p" is a fork at parent p, which is a backref
1447 - "*p" is a fork at parent p, which is a backref
1448 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1448 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1449 - "/p2" is a merge of the preceding node and p2
1449 - "/p2" is a merge of the preceding node and p2
1450 - ":tag" defines a local tag for the preceding node
1450 - ":tag" defines a local tag for the preceding node
1451 - "@branch" sets the named branch for subsequent nodes
1451 - "@branch" sets the named branch for subsequent nodes
1452 - "#...\\n" is a comment up to the end of the line
1452 - "#...\\n" is a comment up to the end of the line
1453
1453
1454 Whitespace between the above elements is ignored.
1454 Whitespace between the above elements is ignored.
1455
1455
1456 A backref is either
1456 A backref is either
1457
1457
1458 - a number n, which references the node curr-n, where curr is the current
1458 - a number n, which references the node curr-n, where curr is the current
1459 node, or
1459 node, or
1460 - the name of a local tag you placed earlier using ":tag", or
1460 - the name of a local tag you placed earlier using ":tag", or
1461 - empty to denote the default parent.
1461 - empty to denote the default parent.
1462
1462
1463 All string valued-elements are either strictly alphanumeric, or must
1463 All string valued-elements are either strictly alphanumeric, or must
1464 be enclosed in double quotes ("..."), with "\\" as escape character.
1464 be enclosed in double quotes ("..."), with "\\" as escape character.
1465 """
1465 """
1466
1466
1467 if text is None:
1467 if text is None:
1468 ui.status(_("reading DAG from stdin\n"))
1468 ui.status(_("reading DAG from stdin\n"))
1469 text = ui.fin.read()
1469 text = ui.fin.read()
1470
1470
1471 cl = repo.changelog
1471 cl = repo.changelog
1472 if len(cl) > 0:
1472 if len(cl) > 0:
1473 raise util.Abort(_('repository is not empty'))
1473 raise util.Abort(_('repository is not empty'))
1474
1474
1475 # determine number of revs in DAG
1475 # determine number of revs in DAG
1476 total = 0
1476 total = 0
1477 for type, data in dagparser.parsedag(text):
1477 for type, data in dagparser.parsedag(text):
1478 if type == 'n':
1478 if type == 'n':
1479 total += 1
1479 total += 1
1480
1480
1481 if mergeable_file:
1481 if mergeable_file:
1482 linesperrev = 2
1482 linesperrev = 2
1483 # make a file with k lines per rev
1483 # make a file with k lines per rev
1484 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1484 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1485 initialmergedlines.append("")
1485 initialmergedlines.append("")
1486
1486
1487 tags = []
1487 tags = []
1488
1488
1489 lock = tr = None
1489 lock = tr = None
1490 try:
1490 try:
1491 lock = repo.lock()
1491 lock = repo.lock()
1492 tr = repo.transaction("builddag")
1492 tr = repo.transaction("builddag")
1493
1493
1494 at = -1
1494 at = -1
1495 atbranch = 'default'
1495 atbranch = 'default'
1496 nodeids = []
1496 nodeids = []
1497 id = 0
1497 id = 0
1498 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1498 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1499 for type, data in dagparser.parsedag(text):
1499 for type, data in dagparser.parsedag(text):
1500 if type == 'n':
1500 if type == 'n':
1501 ui.note('node %s\n' % str(data))
1501 ui.note('node %s\n' % str(data))
1502 id, ps = data
1502 id, ps = data
1503
1503
1504 files = []
1504 files = []
1505 fctxs = {}
1505 fctxs = {}
1506
1506
1507 p2 = None
1507 p2 = None
1508 if mergeable_file:
1508 if mergeable_file:
1509 fn = "mf"
1509 fn = "mf"
1510 p1 = repo[ps[0]]
1510 p1 = repo[ps[0]]
1511 if len(ps) > 1:
1511 if len(ps) > 1:
1512 p2 = repo[ps[1]]
1512 p2 = repo[ps[1]]
1513 pa = p1.ancestor(p2)
1513 pa = p1.ancestor(p2)
1514 base, local, other = [x[fn].data() for x in pa, p1, p2]
1514 base, local, other = [x[fn].data() for x in pa, p1, p2]
1515 m3 = simplemerge.Merge3Text(base, local, other)
1515 m3 = simplemerge.Merge3Text(base, local, other)
1516 ml = [l.strip() for l in m3.merge_lines()]
1516 ml = [l.strip() for l in m3.merge_lines()]
1517 ml.append("")
1517 ml.append("")
1518 elif at > 0:
1518 elif at > 0:
1519 ml = p1[fn].data().split("\n")
1519 ml = p1[fn].data().split("\n")
1520 else:
1520 else:
1521 ml = initialmergedlines
1521 ml = initialmergedlines
1522 ml[id * linesperrev] += " r%i" % id
1522 ml[id * linesperrev] += " r%i" % id
1523 mergedtext = "\n".join(ml)
1523 mergedtext = "\n".join(ml)
1524 files.append(fn)
1524 files.append(fn)
1525 fctxs[fn] = context.memfilectx(fn, mergedtext)
1525 fctxs[fn] = context.memfilectx(fn, mergedtext)
1526
1526
1527 if overwritten_file:
1527 if overwritten_file:
1528 fn = "of"
1528 fn = "of"
1529 files.append(fn)
1529 files.append(fn)
1530 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1530 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1531
1531
1532 if new_file:
1532 if new_file:
1533 fn = "nf%i" % id
1533 fn = "nf%i" % id
1534 files.append(fn)
1534 files.append(fn)
1535 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1535 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1536 if len(ps) > 1:
1536 if len(ps) > 1:
1537 if not p2:
1537 if not p2:
1538 p2 = repo[ps[1]]
1538 p2 = repo[ps[1]]
1539 for fn in p2:
1539 for fn in p2:
1540 if fn.startswith("nf"):
1540 if fn.startswith("nf"):
1541 files.append(fn)
1541 files.append(fn)
1542 fctxs[fn] = p2[fn]
1542 fctxs[fn] = p2[fn]
1543
1543
1544 def fctxfn(repo, cx, path):
1544 def fctxfn(repo, cx, path):
1545 return fctxs.get(path)
1545 return fctxs.get(path)
1546
1546
1547 if len(ps) == 0 or ps[0] < 0:
1547 if len(ps) == 0 or ps[0] < 0:
1548 pars = [None, None]
1548 pars = [None, None]
1549 elif len(ps) == 1:
1549 elif len(ps) == 1:
1550 pars = [nodeids[ps[0]], None]
1550 pars = [nodeids[ps[0]], None]
1551 else:
1551 else:
1552 pars = [nodeids[p] for p in ps]
1552 pars = [nodeids[p] for p in ps]
1553 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1553 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1554 date=(id, 0),
1554 date=(id, 0),
1555 user="debugbuilddag",
1555 user="debugbuilddag",
1556 extra={'branch': atbranch})
1556 extra={'branch': atbranch})
1557 nodeid = repo.commitctx(cx)
1557 nodeid = repo.commitctx(cx)
1558 nodeids.append(nodeid)
1558 nodeids.append(nodeid)
1559 at = id
1559 at = id
1560 elif type == 'l':
1560 elif type == 'l':
1561 id, name = data
1561 id, name = data
1562 ui.note('tag %s\n' % name)
1562 ui.note('tag %s\n' % name)
1563 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1563 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1564 elif type == 'a':
1564 elif type == 'a':
1565 ui.note('branch %s\n' % data)
1565 ui.note('branch %s\n' % data)
1566 atbranch = data
1566 atbranch = data
1567 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1567 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1568 tr.close()
1568 tr.close()
1569
1569
1570 if tags:
1570 if tags:
1571 repo.opener.write("localtags", "".join(tags))
1571 repo.opener.write("localtags", "".join(tags))
1572 finally:
1572 finally:
1573 ui.progress(_('building'), None)
1573 ui.progress(_('building'), None)
1574 release(tr, lock)
1574 release(tr, lock)
1575
1575
1576 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1576 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1577 def debugbundle(ui, bundlepath, all=None, **opts):
1577 def debugbundle(ui, bundlepath, all=None, **opts):
1578 """lists the contents of a bundle"""
1578 """lists the contents of a bundle"""
1579 f = url.open(ui, bundlepath)
1579 f = url.open(ui, bundlepath)
1580 try:
1580 try:
1581 gen = changegroup.readbundle(f, bundlepath)
1581 gen = changegroup.readbundle(f, bundlepath)
1582 if all:
1582 if all:
1583 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1583 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1584
1584
1585 def showchunks(named):
1585 def showchunks(named):
1586 ui.write("\n%s\n" % named)
1586 ui.write("\n%s\n" % named)
1587 chain = None
1587 chain = None
1588 while True:
1588 while True:
1589 chunkdata = gen.deltachunk(chain)
1589 chunkdata = gen.deltachunk(chain)
1590 if not chunkdata:
1590 if not chunkdata:
1591 break
1591 break
1592 node = chunkdata['node']
1592 node = chunkdata['node']
1593 p1 = chunkdata['p1']
1593 p1 = chunkdata['p1']
1594 p2 = chunkdata['p2']
1594 p2 = chunkdata['p2']
1595 cs = chunkdata['cs']
1595 cs = chunkdata['cs']
1596 deltabase = chunkdata['deltabase']
1596 deltabase = chunkdata['deltabase']
1597 delta = chunkdata['delta']
1597 delta = chunkdata['delta']
1598 ui.write("%s %s %s %s %s %s\n" %
1598 ui.write("%s %s %s %s %s %s\n" %
1599 (hex(node), hex(p1), hex(p2),
1599 (hex(node), hex(p1), hex(p2),
1600 hex(cs), hex(deltabase), len(delta)))
1600 hex(cs), hex(deltabase), len(delta)))
1601 chain = node
1601 chain = node
1602
1602
1603 chunkdata = gen.changelogheader()
1603 chunkdata = gen.changelogheader()
1604 showchunks("changelog")
1604 showchunks("changelog")
1605 chunkdata = gen.manifestheader()
1605 chunkdata = gen.manifestheader()
1606 showchunks("manifest")
1606 showchunks("manifest")
1607 while True:
1607 while True:
1608 chunkdata = gen.filelogheader()
1608 chunkdata = gen.filelogheader()
1609 if not chunkdata:
1609 if not chunkdata:
1610 break
1610 break
1611 fname = chunkdata['filename']
1611 fname = chunkdata['filename']
1612 showchunks(fname)
1612 showchunks(fname)
1613 else:
1613 else:
1614 chunkdata = gen.changelogheader()
1614 chunkdata = gen.changelogheader()
1615 chain = None
1615 chain = None
1616 while True:
1616 while True:
1617 chunkdata = gen.deltachunk(chain)
1617 chunkdata = gen.deltachunk(chain)
1618 if not chunkdata:
1618 if not chunkdata:
1619 break
1619 break
1620 node = chunkdata['node']
1620 node = chunkdata['node']
1621 ui.write("%s\n" % hex(node))
1621 ui.write("%s\n" % hex(node))
1622 chain = node
1622 chain = node
1623 finally:
1623 finally:
1624 f.close()
1624 f.close()
1625
1625
1626 @command('debugcheckstate', [], '')
1626 @command('debugcheckstate', [], '')
1627 def debugcheckstate(ui, repo):
1627 def debugcheckstate(ui, repo):
1628 """validate the correctness of the current dirstate"""
1628 """validate the correctness of the current dirstate"""
1629 parent1, parent2 = repo.dirstate.parents()
1629 parent1, parent2 = repo.dirstate.parents()
1630 m1 = repo[parent1].manifest()
1630 m1 = repo[parent1].manifest()
1631 m2 = repo[parent2].manifest()
1631 m2 = repo[parent2].manifest()
1632 errors = 0
1632 errors = 0
1633 for f in repo.dirstate:
1633 for f in repo.dirstate:
1634 state = repo.dirstate[f]
1634 state = repo.dirstate[f]
1635 if state in "nr" and f not in m1:
1635 if state in "nr" and f not in m1:
1636 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1636 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1637 errors += 1
1637 errors += 1
1638 if state in "a" and f in m1:
1638 if state in "a" and f in m1:
1639 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1639 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1640 errors += 1
1640 errors += 1
1641 if state in "m" and f not in m1 and f not in m2:
1641 if state in "m" and f not in m1 and f not in m2:
1642 ui.warn(_("%s in state %s, but not in either manifest\n") %
1642 ui.warn(_("%s in state %s, but not in either manifest\n") %
1643 (f, state))
1643 (f, state))
1644 errors += 1
1644 errors += 1
1645 for f in m1:
1645 for f in m1:
1646 state = repo.dirstate[f]
1646 state = repo.dirstate[f]
1647 if state not in "nrm":
1647 if state not in "nrm":
1648 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1648 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1649 errors += 1
1649 errors += 1
1650 if errors:
1650 if errors:
1651 error = _(".hg/dirstate inconsistent with current parent's manifest")
1651 error = _(".hg/dirstate inconsistent with current parent's manifest")
1652 raise util.Abort(error)
1652 raise util.Abort(error)
1653
1653
1654 @command('debugcommands', [], _('[COMMAND]'))
1654 @command('debugcommands', [], _('[COMMAND]'))
1655 def debugcommands(ui, cmd='', *args):
1655 def debugcommands(ui, cmd='', *args):
1656 """list all available commands and options"""
1656 """list all available commands and options"""
1657 for cmd, vals in sorted(table.iteritems()):
1657 for cmd, vals in sorted(table.iteritems()):
1658 cmd = cmd.split('|')[0].strip('^')
1658 cmd = cmd.split('|')[0].strip('^')
1659 opts = ', '.join([i[1] for i in vals[1]])
1659 opts = ', '.join([i[1] for i in vals[1]])
1660 ui.write('%s: %s\n' % (cmd, opts))
1660 ui.write('%s: %s\n' % (cmd, opts))
1661
1661
1662 @command('debugcomplete',
1662 @command('debugcomplete',
1663 [('o', 'options', None, _('show the command options'))],
1663 [('o', 'options', None, _('show the command options'))],
1664 _('[-o] CMD'))
1664 _('[-o] CMD'))
1665 def debugcomplete(ui, cmd='', **opts):
1665 def debugcomplete(ui, cmd='', **opts):
1666 """returns the completion list associated with the given command"""
1666 """returns the completion list associated with the given command"""
1667
1667
1668 if opts.get('options'):
1668 if opts.get('options'):
1669 options = []
1669 options = []
1670 otables = [globalopts]
1670 otables = [globalopts]
1671 if cmd:
1671 if cmd:
1672 aliases, entry = cmdutil.findcmd(cmd, table, False)
1672 aliases, entry = cmdutil.findcmd(cmd, table, False)
1673 otables.append(entry[1])
1673 otables.append(entry[1])
1674 for t in otables:
1674 for t in otables:
1675 for o in t:
1675 for o in t:
1676 if "(DEPRECATED)" in o[3]:
1676 if "(DEPRECATED)" in o[3]:
1677 continue
1677 continue
1678 if o[0]:
1678 if o[0]:
1679 options.append('-%s' % o[0])
1679 options.append('-%s' % o[0])
1680 options.append('--%s' % o[1])
1680 options.append('--%s' % o[1])
1681 ui.write("%s\n" % "\n".join(options))
1681 ui.write("%s\n" % "\n".join(options))
1682 return
1682 return
1683
1683
1684 cmdlist = cmdutil.findpossible(cmd, table)
1684 cmdlist = cmdutil.findpossible(cmd, table)
1685 if ui.verbose:
1685 if ui.verbose:
1686 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1686 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1687 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1687 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1688
1688
1689 @command('debugdag',
1689 @command('debugdag',
1690 [('t', 'tags', None, _('use tags as labels')),
1690 [('t', 'tags', None, _('use tags as labels')),
1691 ('b', 'branches', None, _('annotate with branch names')),
1691 ('b', 'branches', None, _('annotate with branch names')),
1692 ('', 'dots', None, _('use dots for runs')),
1692 ('', 'dots', None, _('use dots for runs')),
1693 ('s', 'spaces', None, _('separate elements by spaces'))],
1693 ('s', 'spaces', None, _('separate elements by spaces'))],
1694 _('[OPTION]... [FILE [REV]...]'))
1694 _('[OPTION]... [FILE [REV]...]'))
1695 def debugdag(ui, repo, file_=None, *revs, **opts):
1695 def debugdag(ui, repo, file_=None, *revs, **opts):
1696 """format the changelog or an index DAG as a concise textual description
1696 """format the changelog or an index DAG as a concise textual description
1697
1697
1698 If you pass a revlog index, the revlog's DAG is emitted. If you list
1698 If you pass a revlog index, the revlog's DAG is emitted. If you list
1699 revision numbers, they get labelled in the output as rN.
1699 revision numbers, they get labelled in the output as rN.
1700
1700
1701 Otherwise, the changelog DAG of the current repo is emitted.
1701 Otherwise, the changelog DAG of the current repo is emitted.
1702 """
1702 """
1703 spaces = opts.get('spaces')
1703 spaces = opts.get('spaces')
1704 dots = opts.get('dots')
1704 dots = opts.get('dots')
1705 if file_:
1705 if file_:
1706 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1706 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1707 revs = set((int(r) for r in revs))
1707 revs = set((int(r) for r in revs))
1708 def events():
1708 def events():
1709 for r in rlog:
1709 for r in rlog:
1710 yield 'n', (r, list(set(p for p in rlog.parentrevs(r)
1710 yield 'n', (r, list(set(p for p in rlog.parentrevs(r)
1711 if p != -1)))
1711 if p != -1)))
1712 if r in revs:
1712 if r in revs:
1713 yield 'l', (r, "r%i" % r)
1713 yield 'l', (r, "r%i" % r)
1714 elif repo:
1714 elif repo:
1715 cl = repo.changelog
1715 cl = repo.changelog
1716 tags = opts.get('tags')
1716 tags = opts.get('tags')
1717 branches = opts.get('branches')
1717 branches = opts.get('branches')
1718 if tags:
1718 if tags:
1719 labels = {}
1719 labels = {}
1720 for l, n in repo.tags().items():
1720 for l, n in repo.tags().items():
1721 labels.setdefault(cl.rev(n), []).append(l)
1721 labels.setdefault(cl.rev(n), []).append(l)
1722 def events():
1722 def events():
1723 b = "default"
1723 b = "default"
1724 for r in cl:
1724 for r in cl:
1725 if branches:
1725 if branches:
1726 newb = cl.read(cl.node(r))[5]['branch']
1726 newb = cl.read(cl.node(r))[5]['branch']
1727 if newb != b:
1727 if newb != b:
1728 yield 'a', newb
1728 yield 'a', newb
1729 b = newb
1729 b = newb
1730 yield 'n', (r, list(set(p for p in cl.parentrevs(r)
1730 yield 'n', (r, list(set(p for p in cl.parentrevs(r)
1731 if p != -1)))
1731 if p != -1)))
1732 if tags:
1732 if tags:
1733 ls = labels.get(r)
1733 ls = labels.get(r)
1734 if ls:
1734 if ls:
1735 for l in ls:
1735 for l in ls:
1736 yield 'l', (r, l)
1736 yield 'l', (r, l)
1737 else:
1737 else:
1738 raise util.Abort(_('need repo for changelog dag'))
1738 raise util.Abort(_('need repo for changelog dag'))
1739
1739
1740 for line in dagparser.dagtextlines(events(),
1740 for line in dagparser.dagtextlines(events(),
1741 addspaces=spaces,
1741 addspaces=spaces,
1742 wraplabels=True,
1742 wraplabels=True,
1743 wrapannotations=True,
1743 wrapannotations=True,
1744 wrapnonlinear=dots,
1744 wrapnonlinear=dots,
1745 usedots=dots,
1745 usedots=dots,
1746 maxlinewidth=70):
1746 maxlinewidth=70):
1747 ui.write(line)
1747 ui.write(line)
1748 ui.write("\n")
1748 ui.write("\n")
1749
1749
1750 @command('debugdata',
1750 @command('debugdata',
1751 [('c', 'changelog', False, _('open changelog')),
1751 [('c', 'changelog', False, _('open changelog')),
1752 ('m', 'manifest', False, _('open manifest'))],
1752 ('m', 'manifest', False, _('open manifest'))],
1753 _('-c|-m|FILE REV'))
1753 _('-c|-m|FILE REV'))
1754 def debugdata(ui, repo, file_, rev = None, **opts):
1754 def debugdata(ui, repo, file_, rev = None, **opts):
1755 """dump the contents of a data file revision"""
1755 """dump the contents of a data file revision"""
1756 if opts.get('changelog') or opts.get('manifest'):
1756 if opts.get('changelog') or opts.get('manifest'):
1757 file_, rev = None, file_
1757 file_, rev = None, file_
1758 elif rev is None:
1758 elif rev is None:
1759 raise error.CommandError('debugdata', _('invalid arguments'))
1759 raise error.CommandError('debugdata', _('invalid arguments'))
1760 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1760 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1761 try:
1761 try:
1762 ui.write(r.revision(r.lookup(rev)))
1762 ui.write(r.revision(r.lookup(rev)))
1763 except KeyError:
1763 except KeyError:
1764 raise util.Abort(_('invalid revision identifier %s') % rev)
1764 raise util.Abort(_('invalid revision identifier %s') % rev)
1765
1765
1766 @command('debugdate',
1766 @command('debugdate',
1767 [('e', 'extended', None, _('try extended date formats'))],
1767 [('e', 'extended', None, _('try extended date formats'))],
1768 _('[-e] DATE [RANGE]'))
1768 _('[-e] DATE [RANGE]'))
1769 def debugdate(ui, date, range=None, **opts):
1769 def debugdate(ui, date, range=None, **opts):
1770 """parse and display a date"""
1770 """parse and display a date"""
1771 if opts["extended"]:
1771 if opts["extended"]:
1772 d = util.parsedate(date, util.extendeddateformats)
1772 d = util.parsedate(date, util.extendeddateformats)
1773 else:
1773 else:
1774 d = util.parsedate(date)
1774 d = util.parsedate(date)
1775 ui.write("internal: %s %s\n" % d)
1775 ui.write("internal: %s %s\n" % d)
1776 ui.write("standard: %s\n" % util.datestr(d))
1776 ui.write("standard: %s\n" % util.datestr(d))
1777 if range:
1777 if range:
1778 m = util.matchdate(range)
1778 m = util.matchdate(range)
1779 ui.write("match: %s\n" % m(d[0]))
1779 ui.write("match: %s\n" % m(d[0]))
1780
1780
1781 @command('debugdiscovery',
1781 @command('debugdiscovery',
1782 [('', 'old', None, _('use old-style discovery')),
1782 [('', 'old', None, _('use old-style discovery')),
1783 ('', 'nonheads', None,
1783 ('', 'nonheads', None,
1784 _('use old-style discovery with non-heads included')),
1784 _('use old-style discovery with non-heads included')),
1785 ] + remoteopts,
1785 ] + remoteopts,
1786 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1786 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1787 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1787 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1788 """runs the changeset discovery protocol in isolation"""
1788 """runs the changeset discovery protocol in isolation"""
1789 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1789 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1790 opts.get('branch'))
1790 opts.get('branch'))
1791 remote = hg.peer(repo, opts, remoteurl)
1791 remote = hg.peer(repo, opts, remoteurl)
1792 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1792 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1793
1793
1794 # make sure tests are repeatable
1794 # make sure tests are repeatable
1795 random.seed(12323)
1795 random.seed(12323)
1796
1796
1797 def doit(localheads, remoteheads, remote=remote):
1797 def doit(localheads, remoteheads, remote=remote):
1798 if opts.get('old'):
1798 if opts.get('old'):
1799 if localheads:
1799 if localheads:
1800 raise util.Abort('cannot use localheads with old style '
1800 raise util.Abort('cannot use localheads with old style '
1801 'discovery')
1801 'discovery')
1802 if not util.safehasattr(remote, 'branches'):
1802 if not util.safehasattr(remote, 'branches'):
1803 # enable in-client legacy support
1803 # enable in-client legacy support
1804 remote = localrepo.locallegacypeer(remote.local())
1804 remote = localrepo.locallegacypeer(remote.local())
1805 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1805 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1806 force=True)
1806 force=True)
1807 common = set(common)
1807 common = set(common)
1808 if not opts.get('nonheads'):
1808 if not opts.get('nonheads'):
1809 ui.write("unpruned common: %s\n" % " ".join([short(n)
1809 ui.write("unpruned common: %s\n" % " ".join([short(n)
1810 for n in common]))
1810 for n in common]))
1811 dag = dagutil.revlogdag(repo.changelog)
1811 dag = dagutil.revlogdag(repo.changelog)
1812 all = dag.ancestorset(dag.internalizeall(common))
1812 all = dag.ancestorset(dag.internalizeall(common))
1813 common = dag.externalizeall(dag.headsetofconnecteds(all))
1813 common = dag.externalizeall(dag.headsetofconnecteds(all))
1814 else:
1814 else:
1815 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1815 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1816 common = set(common)
1816 common = set(common)
1817 rheads = set(hds)
1817 rheads = set(hds)
1818 lheads = set(repo.heads())
1818 lheads = set(repo.heads())
1819 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1819 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1820 if lheads <= common:
1820 if lheads <= common:
1821 ui.write("local is subset\n")
1821 ui.write("local is subset\n")
1822 elif rheads <= common:
1822 elif rheads <= common:
1823 ui.write("remote is subset\n")
1823 ui.write("remote is subset\n")
1824
1824
1825 serverlogs = opts.get('serverlog')
1825 serverlogs = opts.get('serverlog')
1826 if serverlogs:
1826 if serverlogs:
1827 for filename in serverlogs:
1827 for filename in serverlogs:
1828 logfile = open(filename, 'r')
1828 logfile = open(filename, 'r')
1829 try:
1829 try:
1830 line = logfile.readline()
1830 line = logfile.readline()
1831 while line:
1831 while line:
1832 parts = line.strip().split(';')
1832 parts = line.strip().split(';')
1833 op = parts[1]
1833 op = parts[1]
1834 if op == 'cg':
1834 if op == 'cg':
1835 pass
1835 pass
1836 elif op == 'cgss':
1836 elif op == 'cgss':
1837 doit(parts[2].split(' '), parts[3].split(' '))
1837 doit(parts[2].split(' '), parts[3].split(' '))
1838 elif op == 'unb':
1838 elif op == 'unb':
1839 doit(parts[3].split(' '), parts[2].split(' '))
1839 doit(parts[3].split(' '), parts[2].split(' '))
1840 line = logfile.readline()
1840 line = logfile.readline()
1841 finally:
1841 finally:
1842 logfile.close()
1842 logfile.close()
1843
1843
1844 else:
1844 else:
1845 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1845 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1846 opts.get('remote_head'))
1846 opts.get('remote_head'))
1847 localrevs = opts.get('local_head')
1847 localrevs = opts.get('local_head')
1848 doit(localrevs, remoterevs)
1848 doit(localrevs, remoterevs)
1849
1849
1850 @command('debugfileset',
1850 @command('debugfileset',
1851 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
1851 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
1852 _('[-r REV] FILESPEC'))
1852 _('[-r REV] FILESPEC'))
1853 def debugfileset(ui, repo, expr, **opts):
1853 def debugfileset(ui, repo, expr, **opts):
1854 '''parse and apply a fileset specification'''
1854 '''parse and apply a fileset specification'''
1855 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
1855 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
1856 if ui.verbose:
1856 if ui.verbose:
1857 tree = fileset.parse(expr)[0]
1857 tree = fileset.parse(expr)[0]
1858 ui.note(tree, "\n")
1858 ui.note(tree, "\n")
1859
1859
1860 for f in fileset.getfileset(ctx, expr):
1860 for f in fileset.getfileset(ctx, expr):
1861 ui.write("%s\n" % f)
1861 ui.write("%s\n" % f)
1862
1862
1863 @command('debugfsinfo', [], _('[PATH]'))
1863 @command('debugfsinfo', [], _('[PATH]'))
1864 def debugfsinfo(ui, path = "."):
1864 def debugfsinfo(ui, path = "."):
1865 """show information detected about current filesystem"""
1865 """show information detected about current filesystem"""
1866 util.writefile('.debugfsinfo', '')
1866 util.writefile('.debugfsinfo', '')
1867 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1867 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1868 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1868 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1869 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1869 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1870 and 'yes' or 'no'))
1870 and 'yes' or 'no'))
1871 os.unlink('.debugfsinfo')
1871 os.unlink('.debugfsinfo')
1872
1872
1873 @command('debuggetbundle',
1873 @command('debuggetbundle',
1874 [('H', 'head', [], _('id of head node'), _('ID')),
1874 [('H', 'head', [], _('id of head node'), _('ID')),
1875 ('C', 'common', [], _('id of common node'), _('ID')),
1875 ('C', 'common', [], _('id of common node'), _('ID')),
1876 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1876 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1877 _('REPO FILE [-H|-C ID]...'))
1877 _('REPO FILE [-H|-C ID]...'))
1878 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1878 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1879 """retrieves a bundle from a repo
1879 """retrieves a bundle from a repo
1880
1880
1881 Every ID must be a full-length hex node id string. Saves the bundle to the
1881 Every ID must be a full-length hex node id string. Saves the bundle to the
1882 given file.
1882 given file.
1883 """
1883 """
1884 repo = hg.peer(ui, opts, repopath)
1884 repo = hg.peer(ui, opts, repopath)
1885 if not repo.capable('getbundle'):
1885 if not repo.capable('getbundle'):
1886 raise util.Abort("getbundle() not supported by target repository")
1886 raise util.Abort("getbundle() not supported by target repository")
1887 args = {}
1887 args = {}
1888 if common:
1888 if common:
1889 args['common'] = [bin(s) for s in common]
1889 args['common'] = [bin(s) for s in common]
1890 if head:
1890 if head:
1891 args['heads'] = [bin(s) for s in head]
1891 args['heads'] = [bin(s) for s in head]
1892 bundle = repo.getbundle('debug', **args)
1892 bundle = repo.getbundle('debug', **args)
1893
1893
1894 bundletype = opts.get('type', 'bzip2').lower()
1894 bundletype = opts.get('type', 'bzip2').lower()
1895 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1895 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1896 bundletype = btypes.get(bundletype)
1896 bundletype = btypes.get(bundletype)
1897 if bundletype not in changegroup.bundletypes:
1897 if bundletype not in changegroup.bundletypes:
1898 raise util.Abort(_('unknown bundle type specified with --type'))
1898 raise util.Abort(_('unknown bundle type specified with --type'))
1899 changegroup.writebundle(bundle, bundlepath, bundletype)
1899 changegroup.writebundle(bundle, bundlepath, bundletype)
1900
1900
1901 @command('debugignore', [], '')
1901 @command('debugignore', [], '')
1902 def debugignore(ui, repo, *values, **opts):
1902 def debugignore(ui, repo, *values, **opts):
1903 """display the combined ignore pattern"""
1903 """display the combined ignore pattern"""
1904 ignore = repo.dirstate._ignore
1904 ignore = repo.dirstate._ignore
1905 includepat = getattr(ignore, 'includepat', None)
1905 includepat = getattr(ignore, 'includepat', None)
1906 if includepat is not None:
1906 if includepat is not None:
1907 ui.write("%s\n" % includepat)
1907 ui.write("%s\n" % includepat)
1908 else:
1908 else:
1909 raise util.Abort(_("no ignore patterns found"))
1909 raise util.Abort(_("no ignore patterns found"))
1910
1910
1911 @command('debugindex',
1911 @command('debugindex',
1912 [('c', 'changelog', False, _('open changelog')),
1912 [('c', 'changelog', False, _('open changelog')),
1913 ('m', 'manifest', False, _('open manifest')),
1913 ('m', 'manifest', False, _('open manifest')),
1914 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1914 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1915 _('[-f FORMAT] -c|-m|FILE'))
1915 _('[-f FORMAT] -c|-m|FILE'))
1916 def debugindex(ui, repo, file_ = None, **opts):
1916 def debugindex(ui, repo, file_ = None, **opts):
1917 """dump the contents of an index file"""
1917 """dump the contents of an index file"""
1918 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1918 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1919 format = opts.get('format', 0)
1919 format = opts.get('format', 0)
1920 if format not in (0, 1):
1920 if format not in (0, 1):
1921 raise util.Abort(_("unknown format %d") % format)
1921 raise util.Abort(_("unknown format %d") % format)
1922
1922
1923 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1923 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1924 if generaldelta:
1924 if generaldelta:
1925 basehdr = ' delta'
1925 basehdr = ' delta'
1926 else:
1926 else:
1927 basehdr = ' base'
1927 basehdr = ' base'
1928
1928
1929 if format == 0:
1929 if format == 0:
1930 ui.write(" rev offset length " + basehdr + " linkrev"
1930 ui.write(" rev offset length " + basehdr + " linkrev"
1931 " nodeid p1 p2\n")
1931 " nodeid p1 p2\n")
1932 elif format == 1:
1932 elif format == 1:
1933 ui.write(" rev flag offset length"
1933 ui.write(" rev flag offset length"
1934 " size " + basehdr + " link p1 p2"
1934 " size " + basehdr + " link p1 p2"
1935 " nodeid\n")
1935 " nodeid\n")
1936
1936
1937 for i in r:
1937 for i in r:
1938 node = r.node(i)
1938 node = r.node(i)
1939 if generaldelta:
1939 if generaldelta:
1940 base = r.deltaparent(i)
1940 base = r.deltaparent(i)
1941 else:
1941 else:
1942 base = r.chainbase(i)
1942 base = r.chainbase(i)
1943 if format == 0:
1943 if format == 0:
1944 try:
1944 try:
1945 pp = r.parents(node)
1945 pp = r.parents(node)
1946 except Exception:
1946 except Exception:
1947 pp = [nullid, nullid]
1947 pp = [nullid, nullid]
1948 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1948 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1949 i, r.start(i), r.length(i), base, r.linkrev(i),
1949 i, r.start(i), r.length(i), base, r.linkrev(i),
1950 short(node), short(pp[0]), short(pp[1])))
1950 short(node), short(pp[0]), short(pp[1])))
1951 elif format == 1:
1951 elif format == 1:
1952 pr = r.parentrevs(i)
1952 pr = r.parentrevs(i)
1953 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1953 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1954 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1954 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1955 base, r.linkrev(i), pr[0], pr[1], short(node)))
1955 base, r.linkrev(i), pr[0], pr[1], short(node)))
1956
1956
1957 @command('debugindexdot', [], _('FILE'))
1957 @command('debugindexdot', [], _('FILE'))
1958 def debugindexdot(ui, repo, file_):
1958 def debugindexdot(ui, repo, file_):
1959 """dump an index DAG as a graphviz dot file"""
1959 """dump an index DAG as a graphviz dot file"""
1960 r = None
1960 r = None
1961 if repo:
1961 if repo:
1962 filelog = repo.file(file_)
1962 filelog = repo.file(file_)
1963 if len(filelog):
1963 if len(filelog):
1964 r = filelog
1964 r = filelog
1965 if not r:
1965 if not r:
1966 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1966 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1967 ui.write("digraph G {\n")
1967 ui.write("digraph G {\n")
1968 for i in r:
1968 for i in r:
1969 node = r.node(i)
1969 node = r.node(i)
1970 pp = r.parents(node)
1970 pp = r.parents(node)
1971 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1971 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1972 if pp[1] != nullid:
1972 if pp[1] != nullid:
1973 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1973 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1974 ui.write("}\n")
1974 ui.write("}\n")
1975
1975
1976 @command('debuginstall', [], '')
1976 @command('debuginstall', [], '')
1977 def debuginstall(ui):
1977 def debuginstall(ui):
1978 '''test Mercurial installation
1978 '''test Mercurial installation
1979
1979
1980 Returns 0 on success.
1980 Returns 0 on success.
1981 '''
1981 '''
1982
1982
1983 def writetemp(contents):
1983 def writetemp(contents):
1984 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1984 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1985 f = os.fdopen(fd, "wb")
1985 f = os.fdopen(fd, "wb")
1986 f.write(contents)
1986 f.write(contents)
1987 f.close()
1987 f.close()
1988 return name
1988 return name
1989
1989
1990 problems = 0
1990 problems = 0
1991
1991
1992 # encoding
1992 # encoding
1993 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
1993 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
1994 try:
1994 try:
1995 encoding.fromlocal("test")
1995 encoding.fromlocal("test")
1996 except util.Abort, inst:
1996 except util.Abort, inst:
1997 ui.write(" %s\n" % inst)
1997 ui.write(" %s\n" % inst)
1998 ui.write(_(" (check that your locale is properly set)\n"))
1998 ui.write(_(" (check that your locale is properly set)\n"))
1999 problems += 1
1999 problems += 1
2000
2000
2001 # Python lib
2001 # Python lib
2002 ui.status(_("checking Python lib (%s)...\n")
2002 ui.status(_("checking Python lib (%s)...\n")
2003 % os.path.dirname(os.__file__))
2003 % os.path.dirname(os.__file__))
2004
2004
2005 # compiled modules
2005 # compiled modules
2006 ui.status(_("checking installed modules (%s)...\n")
2006 ui.status(_("checking installed modules (%s)...\n")
2007 % os.path.dirname(__file__))
2007 % os.path.dirname(__file__))
2008 try:
2008 try:
2009 import bdiff, mpatch, base85, osutil
2009 import bdiff, mpatch, base85, osutil
2010 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2010 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2011 except Exception, inst:
2011 except Exception, inst:
2012 ui.write(" %s\n" % inst)
2012 ui.write(" %s\n" % inst)
2013 ui.write(_(" One or more extensions could not be found"))
2013 ui.write(_(" One or more extensions could not be found"))
2014 ui.write(_(" (check that you compiled the extensions)\n"))
2014 ui.write(_(" (check that you compiled the extensions)\n"))
2015 problems += 1
2015 problems += 1
2016
2016
2017 # templates
2017 # templates
2018 import templater
2018 import templater
2019 p = templater.templatepath()
2019 p = templater.templatepath()
2020 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2020 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2021 try:
2021 try:
2022 templater.templater(templater.templatepath("map-cmdline.default"))
2022 templater.templater(templater.templatepath("map-cmdline.default"))
2023 except Exception, inst:
2023 except Exception, inst:
2024 ui.write(" %s\n" % inst)
2024 ui.write(" %s\n" % inst)
2025 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2025 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2026 problems += 1
2026 problems += 1
2027
2027
2028 # editor
2028 # editor
2029 ui.status(_("checking commit editor...\n"))
2029 ui.status(_("checking commit editor...\n"))
2030 editor = ui.geteditor()
2030 editor = ui.geteditor()
2031 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
2031 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
2032 if not cmdpath:
2032 if not cmdpath:
2033 if editor == 'vi':
2033 if editor == 'vi':
2034 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2034 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2035 ui.write(_(" (specify a commit editor in your configuration"
2035 ui.write(_(" (specify a commit editor in your configuration"
2036 " file)\n"))
2036 " file)\n"))
2037 else:
2037 else:
2038 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2038 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2039 ui.write(_(" (specify a commit editor in your configuration"
2039 ui.write(_(" (specify a commit editor in your configuration"
2040 " file)\n"))
2040 " file)\n"))
2041 problems += 1
2041 problems += 1
2042
2042
2043 # check username
2043 # check username
2044 ui.status(_("checking username...\n"))
2044 ui.status(_("checking username...\n"))
2045 try:
2045 try:
2046 ui.username()
2046 ui.username()
2047 except util.Abort, e:
2047 except util.Abort, e:
2048 ui.write(" %s\n" % e)
2048 ui.write(" %s\n" % e)
2049 ui.write(_(" (specify a username in your configuration file)\n"))
2049 ui.write(_(" (specify a username in your configuration file)\n"))
2050 problems += 1
2050 problems += 1
2051
2051
2052 if not problems:
2052 if not problems:
2053 ui.status(_("no problems detected\n"))
2053 ui.status(_("no problems detected\n"))
2054 else:
2054 else:
2055 ui.write(_("%s problems detected,"
2055 ui.write(_("%s problems detected,"
2056 " please check your install!\n") % problems)
2056 " please check your install!\n") % problems)
2057
2057
2058 return problems
2058 return problems
2059
2059
2060 @command('debugknown', [], _('REPO ID...'))
2060 @command('debugknown', [], _('REPO ID...'))
2061 def debugknown(ui, repopath, *ids, **opts):
2061 def debugknown(ui, repopath, *ids, **opts):
2062 """test whether node ids are known to a repo
2062 """test whether node ids are known to a repo
2063
2063
2064 Every ID must be a full-length hex node id string. Returns a list of 0s
2064 Every ID must be a full-length hex node id string. Returns a list of 0s
2065 and 1s indicating unknown/known.
2065 and 1s indicating unknown/known.
2066 """
2066 """
2067 repo = hg.peer(ui, opts, repopath)
2067 repo = hg.peer(ui, opts, repopath)
2068 if not repo.capable('known'):
2068 if not repo.capable('known'):
2069 raise util.Abort("known() not supported by target repository")
2069 raise util.Abort("known() not supported by target repository")
2070 flags = repo.known([bin(s) for s in ids])
2070 flags = repo.known([bin(s) for s in ids])
2071 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2071 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2072
2072
2073 @command('debugobsolete', [] + commitopts2,
2073 @command('debugobsolete', [] + commitopts2,
2074 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2074 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2075 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2075 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2076 """create arbitrary obsolete marker"""
2076 """create arbitrary obsolete marker"""
2077 def parsenodeid(s):
2077 def parsenodeid(s):
2078 try:
2078 try:
2079 # We do not use revsingle/revrange functions here to accept
2079 # We do not use revsingle/revrange functions here to accept
2080 # arbitrary node identifiers, possibly not present in the
2080 # arbitrary node identifiers, possibly not present in the
2081 # local repository.
2081 # local repository.
2082 n = bin(s)
2082 n = bin(s)
2083 if len(n) != len(nullid):
2083 if len(n) != len(nullid):
2084 raise TypeError()
2084 raise TypeError()
2085 return n
2085 return n
2086 except TypeError:
2086 except TypeError:
2087 raise util.Abort('changeset references must be full hexadecimal '
2087 raise util.Abort('changeset references must be full hexadecimal '
2088 'node identifiers')
2088 'node identifiers')
2089
2089
2090 if precursor is not None:
2090 if precursor is not None:
2091 metadata = {}
2091 metadata = {}
2092 if 'date' in opts:
2092 if 'date' in opts:
2093 metadata['date'] = opts['date']
2093 metadata['date'] = opts['date']
2094 metadata['user'] = opts['user'] or ui.username()
2094 metadata['user'] = opts['user'] or ui.username()
2095 succs = tuple(parsenodeid(succ) for succ in successors)
2095 succs = tuple(parsenodeid(succ) for succ in successors)
2096 l = repo.lock()
2096 l = repo.lock()
2097 try:
2097 try:
2098 tr = repo.transaction('debugobsolete')
2098 tr = repo.transaction('debugobsolete')
2099 try:
2099 try:
2100 repo.obsstore.create(tr, parsenodeid(precursor), succs, 0,
2100 repo.obsstore.create(tr, parsenodeid(precursor), succs, 0,
2101 metadata)
2101 metadata)
2102 tr.close()
2102 tr.close()
2103 finally:
2103 finally:
2104 tr.release()
2104 tr.release()
2105 finally:
2105 finally:
2106 l.release()
2106 l.release()
2107 else:
2107 else:
2108 for m in obsolete.allmarkers(repo):
2108 for m in obsolete.allmarkers(repo):
2109 ui.write(hex(m.precnode()))
2109 ui.write(hex(m.precnode()))
2110 for repl in m.succnodes():
2110 for repl in m.succnodes():
2111 ui.write(' ')
2111 ui.write(' ')
2112 ui.write(hex(repl))
2112 ui.write(hex(repl))
2113 ui.write(' %X ' % m._data[2])
2113 ui.write(' %X ' % m._data[2])
2114 ui.write(m.metadata())
2114 ui.write(m.metadata())
2115 ui.write('\n')
2115 ui.write('\n')
2116
2116
2117 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2117 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2118 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2118 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2119 '''access the pushkey key/value protocol
2119 '''access the pushkey key/value protocol
2120
2120
2121 With two args, list the keys in the given namespace.
2121 With two args, list the keys in the given namespace.
2122
2122
2123 With five args, set a key to new if it currently is set to old.
2123 With five args, set a key to new if it currently is set to old.
2124 Reports success or failure.
2124 Reports success or failure.
2125 '''
2125 '''
2126
2126
2127 target = hg.peer(ui, {}, repopath)
2127 target = hg.peer(ui, {}, repopath)
2128 if keyinfo:
2128 if keyinfo:
2129 key, old, new = keyinfo
2129 key, old, new = keyinfo
2130 r = target.pushkey(namespace, key, old, new)
2130 r = target.pushkey(namespace, key, old, new)
2131 ui.status(str(r) + '\n')
2131 ui.status(str(r) + '\n')
2132 return not r
2132 return not r
2133 else:
2133 else:
2134 for k, v in target.listkeys(namespace).iteritems():
2134 for k, v in target.listkeys(namespace).iteritems():
2135 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2135 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2136 v.encode('string-escape')))
2136 v.encode('string-escape')))
2137
2137
2138 @command('debugpvec', [], _('A B'))
2138 @command('debugpvec', [], _('A B'))
2139 def debugpvec(ui, repo, a, b=None):
2139 def debugpvec(ui, repo, a, b=None):
2140 ca = scmutil.revsingle(repo, a)
2140 ca = scmutil.revsingle(repo, a)
2141 cb = scmutil.revsingle(repo, b)
2141 cb = scmutil.revsingle(repo, b)
2142 pa = pvec.ctxpvec(ca)
2142 pa = pvec.ctxpvec(ca)
2143 pb = pvec.ctxpvec(cb)
2143 pb = pvec.ctxpvec(cb)
2144 if pa == pb:
2144 if pa == pb:
2145 rel = "="
2145 rel = "="
2146 elif pa > pb:
2146 elif pa > pb:
2147 rel = ">"
2147 rel = ">"
2148 elif pa < pb:
2148 elif pa < pb:
2149 rel = "<"
2149 rel = "<"
2150 elif pa | pb:
2150 elif pa | pb:
2151 rel = "|"
2151 rel = "|"
2152 ui.write(_("a: %s\n") % pa)
2152 ui.write(_("a: %s\n") % pa)
2153 ui.write(_("b: %s\n") % pb)
2153 ui.write(_("b: %s\n") % pb)
2154 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2154 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2155 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2155 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2156 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2156 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2157 pa.distance(pb), rel))
2157 pa.distance(pb), rel))
2158
2158
2159 @command('debugrebuildstate',
2159 @command('debugrebuildstate',
2160 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2160 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2161 _('[-r REV] [REV]'))
2161 _('[-r REV] [REV]'))
2162 def debugrebuildstate(ui, repo, rev="tip"):
2162 def debugrebuildstate(ui, repo, rev="tip"):
2163 """rebuild the dirstate as it would look like for the given revision"""
2163 """rebuild the dirstate as it would look like for the given revision"""
2164 ctx = scmutil.revsingle(repo, rev)
2164 ctx = scmutil.revsingle(repo, rev)
2165 wlock = repo.wlock()
2165 wlock = repo.wlock()
2166 try:
2166 try:
2167 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2167 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2168 finally:
2168 finally:
2169 wlock.release()
2169 wlock.release()
2170
2170
2171 @command('debugrename',
2171 @command('debugrename',
2172 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2172 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2173 _('[-r REV] FILE'))
2173 _('[-r REV] FILE'))
2174 def debugrename(ui, repo, file1, *pats, **opts):
2174 def debugrename(ui, repo, file1, *pats, **opts):
2175 """dump rename information"""
2175 """dump rename information"""
2176
2176
2177 ctx = scmutil.revsingle(repo, opts.get('rev'))
2177 ctx = scmutil.revsingle(repo, opts.get('rev'))
2178 m = scmutil.match(ctx, (file1,) + pats, opts)
2178 m = scmutil.match(ctx, (file1,) + pats, opts)
2179 for abs in ctx.walk(m):
2179 for abs in ctx.walk(m):
2180 fctx = ctx[abs]
2180 fctx = ctx[abs]
2181 o = fctx.filelog().renamed(fctx.filenode())
2181 o = fctx.filelog().renamed(fctx.filenode())
2182 rel = m.rel(abs)
2182 rel = m.rel(abs)
2183 if o:
2183 if o:
2184 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2184 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2185 else:
2185 else:
2186 ui.write(_("%s not renamed\n") % rel)
2186 ui.write(_("%s not renamed\n") % rel)
2187
2187
2188 @command('debugrevlog',
2188 @command('debugrevlog',
2189 [('c', 'changelog', False, _('open changelog')),
2189 [('c', 'changelog', False, _('open changelog')),
2190 ('m', 'manifest', False, _('open manifest')),
2190 ('m', 'manifest', False, _('open manifest')),
2191 ('d', 'dump', False, _('dump index data'))],
2191 ('d', 'dump', False, _('dump index data'))],
2192 _('-c|-m|FILE'))
2192 _('-c|-m|FILE'))
2193 def debugrevlog(ui, repo, file_ = None, **opts):
2193 def debugrevlog(ui, repo, file_ = None, **opts):
2194 """show data and statistics about a revlog"""
2194 """show data and statistics about a revlog"""
2195 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2195 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2196
2196
2197 if opts.get("dump"):
2197 if opts.get("dump"):
2198 numrevs = len(r)
2198 numrevs = len(r)
2199 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2199 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2200 " rawsize totalsize compression heads\n")
2200 " rawsize totalsize compression heads\n")
2201 ts = 0
2201 ts = 0
2202 heads = set()
2202 heads = set()
2203 for rev in xrange(numrevs):
2203 for rev in xrange(numrevs):
2204 dbase = r.deltaparent(rev)
2204 dbase = r.deltaparent(rev)
2205 if dbase == -1:
2205 if dbase == -1:
2206 dbase = rev
2206 dbase = rev
2207 cbase = r.chainbase(rev)
2207 cbase = r.chainbase(rev)
2208 p1, p2 = r.parentrevs(rev)
2208 p1, p2 = r.parentrevs(rev)
2209 rs = r.rawsize(rev)
2209 rs = r.rawsize(rev)
2210 ts = ts + rs
2210 ts = ts + rs
2211 heads -= set(r.parentrevs(rev))
2211 heads -= set(r.parentrevs(rev))
2212 heads.add(rev)
2212 heads.add(rev)
2213 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2213 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2214 (rev, p1, p2, r.start(rev), r.end(rev),
2214 (rev, p1, p2, r.start(rev), r.end(rev),
2215 r.start(dbase), r.start(cbase),
2215 r.start(dbase), r.start(cbase),
2216 r.start(p1), r.start(p2),
2216 r.start(p1), r.start(p2),
2217 rs, ts, ts / r.end(rev), len(heads)))
2217 rs, ts, ts / r.end(rev), len(heads)))
2218 return 0
2218 return 0
2219
2219
2220 v = r.version
2220 v = r.version
2221 format = v & 0xFFFF
2221 format = v & 0xFFFF
2222 flags = []
2222 flags = []
2223 gdelta = False
2223 gdelta = False
2224 if v & revlog.REVLOGNGINLINEDATA:
2224 if v & revlog.REVLOGNGINLINEDATA:
2225 flags.append('inline')
2225 flags.append('inline')
2226 if v & revlog.REVLOGGENERALDELTA:
2226 if v & revlog.REVLOGGENERALDELTA:
2227 gdelta = True
2227 gdelta = True
2228 flags.append('generaldelta')
2228 flags.append('generaldelta')
2229 if not flags:
2229 if not flags:
2230 flags = ['(none)']
2230 flags = ['(none)']
2231
2231
2232 nummerges = 0
2232 nummerges = 0
2233 numfull = 0
2233 numfull = 0
2234 numprev = 0
2234 numprev = 0
2235 nump1 = 0
2235 nump1 = 0
2236 nump2 = 0
2236 nump2 = 0
2237 numother = 0
2237 numother = 0
2238 nump1prev = 0
2238 nump1prev = 0
2239 nump2prev = 0
2239 nump2prev = 0
2240 chainlengths = []
2240 chainlengths = []
2241
2241
2242 datasize = [None, 0, 0L]
2242 datasize = [None, 0, 0L]
2243 fullsize = [None, 0, 0L]
2243 fullsize = [None, 0, 0L]
2244 deltasize = [None, 0, 0L]
2244 deltasize = [None, 0, 0L]
2245
2245
2246 def addsize(size, l):
2246 def addsize(size, l):
2247 if l[0] is None or size < l[0]:
2247 if l[0] is None or size < l[0]:
2248 l[0] = size
2248 l[0] = size
2249 if size > l[1]:
2249 if size > l[1]:
2250 l[1] = size
2250 l[1] = size
2251 l[2] += size
2251 l[2] += size
2252
2252
2253 numrevs = len(r)
2253 numrevs = len(r)
2254 for rev in xrange(numrevs):
2254 for rev in xrange(numrevs):
2255 p1, p2 = r.parentrevs(rev)
2255 p1, p2 = r.parentrevs(rev)
2256 delta = r.deltaparent(rev)
2256 delta = r.deltaparent(rev)
2257 if format > 0:
2257 if format > 0:
2258 addsize(r.rawsize(rev), datasize)
2258 addsize(r.rawsize(rev), datasize)
2259 if p2 != nullrev:
2259 if p2 != nullrev:
2260 nummerges += 1
2260 nummerges += 1
2261 size = r.length(rev)
2261 size = r.length(rev)
2262 if delta == nullrev:
2262 if delta == nullrev:
2263 chainlengths.append(0)
2263 chainlengths.append(0)
2264 numfull += 1
2264 numfull += 1
2265 addsize(size, fullsize)
2265 addsize(size, fullsize)
2266 else:
2266 else:
2267 chainlengths.append(chainlengths[delta] + 1)
2267 chainlengths.append(chainlengths[delta] + 1)
2268 addsize(size, deltasize)
2268 addsize(size, deltasize)
2269 if delta == rev - 1:
2269 if delta == rev - 1:
2270 numprev += 1
2270 numprev += 1
2271 if delta == p1:
2271 if delta == p1:
2272 nump1prev += 1
2272 nump1prev += 1
2273 elif delta == p2:
2273 elif delta == p2:
2274 nump2prev += 1
2274 nump2prev += 1
2275 elif delta == p1:
2275 elif delta == p1:
2276 nump1 += 1
2276 nump1 += 1
2277 elif delta == p2:
2277 elif delta == p2:
2278 nump2 += 1
2278 nump2 += 1
2279 elif delta != nullrev:
2279 elif delta != nullrev:
2280 numother += 1
2280 numother += 1
2281
2281
2282 # Adjust size min value for empty cases
2282 # Adjust size min value for empty cases
2283 for size in (datasize, fullsize, deltasize):
2283 for size in (datasize, fullsize, deltasize):
2284 if size[0] is None:
2284 if size[0] is None:
2285 size[0] = 0
2285 size[0] = 0
2286
2286
2287 numdeltas = numrevs - numfull
2287 numdeltas = numrevs - numfull
2288 numoprev = numprev - nump1prev - nump2prev
2288 numoprev = numprev - nump1prev - nump2prev
2289 totalrawsize = datasize[2]
2289 totalrawsize = datasize[2]
2290 datasize[2] /= numrevs
2290 datasize[2] /= numrevs
2291 fulltotal = fullsize[2]
2291 fulltotal = fullsize[2]
2292 fullsize[2] /= numfull
2292 fullsize[2] /= numfull
2293 deltatotal = deltasize[2]
2293 deltatotal = deltasize[2]
2294 if numrevs - numfull > 0:
2294 if numrevs - numfull > 0:
2295 deltasize[2] /= numrevs - numfull
2295 deltasize[2] /= numrevs - numfull
2296 totalsize = fulltotal + deltatotal
2296 totalsize = fulltotal + deltatotal
2297 avgchainlen = sum(chainlengths) / numrevs
2297 avgchainlen = sum(chainlengths) / numrevs
2298 compratio = totalrawsize / totalsize
2298 compratio = totalrawsize / totalsize
2299
2299
2300 basedfmtstr = '%%%dd\n'
2300 basedfmtstr = '%%%dd\n'
2301 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2301 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2302
2302
2303 def dfmtstr(max):
2303 def dfmtstr(max):
2304 return basedfmtstr % len(str(max))
2304 return basedfmtstr % len(str(max))
2305 def pcfmtstr(max, padding=0):
2305 def pcfmtstr(max, padding=0):
2306 return basepcfmtstr % (len(str(max)), ' ' * padding)
2306 return basepcfmtstr % (len(str(max)), ' ' * padding)
2307
2307
2308 def pcfmt(value, total):
2308 def pcfmt(value, total):
2309 return (value, 100 * float(value) / total)
2309 return (value, 100 * float(value) / total)
2310
2310
2311 ui.write('format : %d\n' % format)
2311 ui.write('format : %d\n' % format)
2312 ui.write('flags : %s\n' % ', '.join(flags))
2312 ui.write('flags : %s\n' % ', '.join(flags))
2313
2313
2314 ui.write('\n')
2314 ui.write('\n')
2315 fmt = pcfmtstr(totalsize)
2315 fmt = pcfmtstr(totalsize)
2316 fmt2 = dfmtstr(totalsize)
2316 fmt2 = dfmtstr(totalsize)
2317 ui.write('revisions : ' + fmt2 % numrevs)
2317 ui.write('revisions : ' + fmt2 % numrevs)
2318 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
2318 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
2319 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
2319 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
2320 ui.write('revisions : ' + fmt2 % numrevs)
2320 ui.write('revisions : ' + fmt2 % numrevs)
2321 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
2321 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
2322 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2322 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2323 ui.write('revision size : ' + fmt2 % totalsize)
2323 ui.write('revision size : ' + fmt2 % totalsize)
2324 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2324 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2325 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2325 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2326
2326
2327 ui.write('\n')
2327 ui.write('\n')
2328 fmt = dfmtstr(max(avgchainlen, compratio))
2328 fmt = dfmtstr(max(avgchainlen, compratio))
2329 ui.write('avg chain length : ' + fmt % avgchainlen)
2329 ui.write('avg chain length : ' + fmt % avgchainlen)
2330 ui.write('compression ratio : ' + fmt % compratio)
2330 ui.write('compression ratio : ' + fmt % compratio)
2331
2331
2332 if format > 0:
2332 if format > 0:
2333 ui.write('\n')
2333 ui.write('\n')
2334 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2334 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2335 % tuple(datasize))
2335 % tuple(datasize))
2336 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2336 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2337 % tuple(fullsize))
2337 % tuple(fullsize))
2338 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2338 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2339 % tuple(deltasize))
2339 % tuple(deltasize))
2340
2340
2341 if numdeltas > 0:
2341 if numdeltas > 0:
2342 ui.write('\n')
2342 ui.write('\n')
2343 fmt = pcfmtstr(numdeltas)
2343 fmt = pcfmtstr(numdeltas)
2344 fmt2 = pcfmtstr(numdeltas, 4)
2344 fmt2 = pcfmtstr(numdeltas, 4)
2345 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2345 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2346 if numprev > 0:
2346 if numprev > 0:
2347 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev,
2347 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev,
2348 numprev))
2348 numprev))
2349 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev,
2349 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev,
2350 numprev))
2350 numprev))
2351 ui.write(' other : ' + fmt2 % pcfmt(numoprev,
2351 ui.write(' other : ' + fmt2 % pcfmt(numoprev,
2352 numprev))
2352 numprev))
2353 if gdelta:
2353 if gdelta:
2354 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2354 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2355 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2355 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2356 ui.write('deltas against other : ' + fmt % pcfmt(numother,
2356 ui.write('deltas against other : ' + fmt % pcfmt(numother,
2357 numdeltas))
2357 numdeltas))
2358
2358
2359 @command('debugrevspec', [], ('REVSPEC'))
2359 @command('debugrevspec', [], ('REVSPEC'))
2360 def debugrevspec(ui, repo, expr):
2360 def debugrevspec(ui, repo, expr):
2361 """parse and apply a revision specification
2361 """parse and apply a revision specification
2362
2362
2363 Use --verbose to print the parsed tree before and after aliases
2363 Use --verbose to print the parsed tree before and after aliases
2364 expansion.
2364 expansion.
2365 """
2365 """
2366 if ui.verbose:
2366 if ui.verbose:
2367 tree = revset.parse(expr)[0]
2367 tree = revset.parse(expr)[0]
2368 ui.note(revset.prettyformat(tree), "\n")
2368 ui.note(revset.prettyformat(tree), "\n")
2369 newtree = revset.findaliases(ui, tree)
2369 newtree = revset.findaliases(ui, tree)
2370 if newtree != tree:
2370 if newtree != tree:
2371 ui.note(revset.prettyformat(newtree), "\n")
2371 ui.note(revset.prettyformat(newtree), "\n")
2372 func = revset.match(ui, expr)
2372 func = revset.match(ui, expr)
2373 for c in func(repo, range(len(repo))):
2373 for c in func(repo, range(len(repo))):
2374 ui.write("%s\n" % c)
2374 ui.write("%s\n" % c)
2375
2375
2376 @command('debugsetparents', [], _('REV1 [REV2]'))
2376 @command('debugsetparents', [], _('REV1 [REV2]'))
2377 def debugsetparents(ui, repo, rev1, rev2=None):
2377 def debugsetparents(ui, repo, rev1, rev2=None):
2378 """manually set the parents of the current working directory
2378 """manually set the parents of the current working directory
2379
2379
2380 This is useful for writing repository conversion tools, but should
2380 This is useful for writing repository conversion tools, but should
2381 be used with care.
2381 be used with care.
2382
2382
2383 Returns 0 on success.
2383 Returns 0 on success.
2384 """
2384 """
2385
2385
2386 r1 = scmutil.revsingle(repo, rev1).node()
2386 r1 = scmutil.revsingle(repo, rev1).node()
2387 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2387 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2388
2388
2389 wlock = repo.wlock()
2389 wlock = repo.wlock()
2390 try:
2390 try:
2391 repo.setparents(r1, r2)
2391 repo.setparents(r1, r2)
2392 finally:
2392 finally:
2393 wlock.release()
2393 wlock.release()
2394
2394
2395 @command('debugstate',
2395 @command('debugstate',
2396 [('', 'nodates', None, _('do not display the saved mtime')),
2396 [('', 'nodates', None, _('do not display the saved mtime')),
2397 ('', 'datesort', None, _('sort by saved mtime'))],
2397 ('', 'datesort', None, _('sort by saved mtime'))],
2398 _('[OPTION]...'))
2398 _('[OPTION]...'))
2399 def debugstate(ui, repo, nodates=None, datesort=None):
2399 def debugstate(ui, repo, nodates=None, datesort=None):
2400 """show the contents of the current dirstate"""
2400 """show the contents of the current dirstate"""
2401 timestr = ""
2401 timestr = ""
2402 showdate = not nodates
2402 showdate = not nodates
2403 if datesort:
2403 if datesort:
2404 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2404 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2405 else:
2405 else:
2406 keyfunc = None # sort by filename
2406 keyfunc = None # sort by filename
2407 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2407 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2408 if showdate:
2408 if showdate:
2409 if ent[3] == -1:
2409 if ent[3] == -1:
2410 # Pad or slice to locale representation
2410 # Pad or slice to locale representation
2411 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2411 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2412 time.localtime(0)))
2412 time.localtime(0)))
2413 timestr = 'unset'
2413 timestr = 'unset'
2414 timestr = (timestr[:locale_len] +
2414 timestr = (timestr[:locale_len] +
2415 ' ' * (locale_len - len(timestr)))
2415 ' ' * (locale_len - len(timestr)))
2416 else:
2416 else:
2417 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2417 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2418 time.localtime(ent[3]))
2418 time.localtime(ent[3]))
2419 if ent[1] & 020000:
2419 if ent[1] & 020000:
2420 mode = 'lnk'
2420 mode = 'lnk'
2421 else:
2421 else:
2422 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2422 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2423 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2423 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2424 for f in repo.dirstate.copies():
2424 for f in repo.dirstate.copies():
2425 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2425 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2426
2426
2427 @command('debugsub',
2427 @command('debugsub',
2428 [('r', 'rev', '',
2428 [('r', 'rev', '',
2429 _('revision to check'), _('REV'))],
2429 _('revision to check'), _('REV'))],
2430 _('[-r REV] [REV]'))
2430 _('[-r REV] [REV]'))
2431 def debugsub(ui, repo, rev=None):
2431 def debugsub(ui, repo, rev=None):
2432 ctx = scmutil.revsingle(repo, rev, None)
2432 ctx = scmutil.revsingle(repo, rev, None)
2433 for k, v in sorted(ctx.substate.items()):
2433 for k, v in sorted(ctx.substate.items()):
2434 ui.write('path %s\n' % k)
2434 ui.write('path %s\n' % k)
2435 ui.write(' source %s\n' % v[0])
2435 ui.write(' source %s\n' % v[0])
2436 ui.write(' revision %s\n' % v[1])
2436 ui.write(' revision %s\n' % v[1])
2437
2437
2438 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2438 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2439 def debugwalk(ui, repo, *pats, **opts):
2439 def debugwalk(ui, repo, *pats, **opts):
2440 """show how files match on given patterns"""
2440 """show how files match on given patterns"""
2441 m = scmutil.match(repo[None], pats, opts)
2441 m = scmutil.match(repo[None], pats, opts)
2442 items = list(repo.walk(m))
2442 items = list(repo.walk(m))
2443 if not items:
2443 if not items:
2444 return
2444 return
2445 f = lambda fn: fn
2445 f = lambda fn: fn
2446 if ui.configbool('ui', 'slash') and os.sep != '/':
2446 if ui.configbool('ui', 'slash') and os.sep != '/':
2447 f = lambda fn: util.normpath(fn)
2447 f = lambda fn: util.normpath(fn)
2448 fmt = 'f %%-%ds %%-%ds %%s' % (
2448 fmt = 'f %%-%ds %%-%ds %%s' % (
2449 max([len(abs) for abs in items]),
2449 max([len(abs) for abs in items]),
2450 max([len(m.rel(abs)) for abs in items]))
2450 max([len(m.rel(abs)) for abs in items]))
2451 for abs in items:
2451 for abs in items:
2452 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2452 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2453 ui.write("%s\n" % line.rstrip())
2453 ui.write("%s\n" % line.rstrip())
2454
2454
2455 @command('debugwireargs',
2455 @command('debugwireargs',
2456 [('', 'three', '', 'three'),
2456 [('', 'three', '', 'three'),
2457 ('', 'four', '', 'four'),
2457 ('', 'four', '', 'four'),
2458 ('', 'five', '', 'five'),
2458 ('', 'five', '', 'five'),
2459 ] + remoteopts,
2459 ] + remoteopts,
2460 _('REPO [OPTIONS]... [ONE [TWO]]'))
2460 _('REPO [OPTIONS]... [ONE [TWO]]'))
2461 def debugwireargs(ui, repopath, *vals, **opts):
2461 def debugwireargs(ui, repopath, *vals, **opts):
2462 repo = hg.peer(ui, opts, repopath)
2462 repo = hg.peer(ui, opts, repopath)
2463 for opt in remoteopts:
2463 for opt in remoteopts:
2464 del opts[opt[1]]
2464 del opts[opt[1]]
2465 args = {}
2465 args = {}
2466 for k, v in opts.iteritems():
2466 for k, v in opts.iteritems():
2467 if v:
2467 if v:
2468 args[k] = v
2468 args[k] = v
2469 # run twice to check that we don't mess up the stream for the next command
2469 # run twice to check that we don't mess up the stream for the next command
2470 res1 = repo.debugwireargs(*vals, **args)
2470 res1 = repo.debugwireargs(*vals, **args)
2471 res2 = repo.debugwireargs(*vals, **args)
2471 res2 = repo.debugwireargs(*vals, **args)
2472 ui.write("%s\n" % res1)
2472 ui.write("%s\n" % res1)
2473 if res1 != res2:
2473 if res1 != res2:
2474 ui.warn("%s\n" % res2)
2474 ui.warn("%s\n" % res2)
2475
2475
2476 @command('^diff',
2476 @command('^diff',
2477 [('r', 'rev', [], _('revision'), _('REV')),
2477 [('r', 'rev', [], _('revision'), _('REV')),
2478 ('c', 'change', '', _('change made by revision'), _('REV'))
2478 ('c', 'change', '', _('change made by revision'), _('REV'))
2479 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2479 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2480 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2480 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2481 def diff(ui, repo, *pats, **opts):
2481 def diff(ui, repo, *pats, **opts):
2482 """diff repository (or selected files)
2482 """diff repository (or selected files)
2483
2483
2484 Show differences between revisions for the specified files.
2484 Show differences between revisions for the specified files.
2485
2485
2486 Differences between files are shown using the unified diff format.
2486 Differences between files are shown using the unified diff format.
2487
2487
2488 .. note::
2488 .. note::
2489 diff may generate unexpected results for merges, as it will
2489 diff may generate unexpected results for merges, as it will
2490 default to comparing against the working directory's first
2490 default to comparing against the working directory's first
2491 parent changeset if no revisions are specified.
2491 parent changeset if no revisions are specified.
2492
2492
2493 When two revision arguments are given, then changes are shown
2493 When two revision arguments are given, then changes are shown
2494 between those revisions. If only one revision is specified then
2494 between those revisions. If only one revision is specified then
2495 that revision is compared to the working directory, and, when no
2495 that revision is compared to the working directory, and, when no
2496 revisions are specified, the working directory files are compared
2496 revisions are specified, the working directory files are compared
2497 to its parent.
2497 to its parent.
2498
2498
2499 Alternatively you can specify -c/--change with a revision to see
2499 Alternatively you can specify -c/--change with a revision to see
2500 the changes in that changeset relative to its first parent.
2500 the changes in that changeset relative to its first parent.
2501
2501
2502 Without the -a/--text option, diff will avoid generating diffs of
2502 Without the -a/--text option, diff will avoid generating diffs of
2503 files it detects as binary. With -a, diff will generate a diff
2503 files it detects as binary. With -a, diff will generate a diff
2504 anyway, probably with undesirable results.
2504 anyway, probably with undesirable results.
2505
2505
2506 Use the -g/--git option to generate diffs in the git extended diff
2506 Use the -g/--git option to generate diffs in the git extended diff
2507 format. For more information, read :hg:`help diffs`.
2507 format. For more information, read :hg:`help diffs`.
2508
2508
2509 .. container:: verbose
2509 .. container:: verbose
2510
2510
2511 Examples:
2511 Examples:
2512
2512
2513 - compare a file in the current working directory to its parent::
2513 - compare a file in the current working directory to its parent::
2514
2514
2515 hg diff foo.c
2515 hg diff foo.c
2516
2516
2517 - compare two historical versions of a directory, with rename info::
2517 - compare two historical versions of a directory, with rename info::
2518
2518
2519 hg diff --git -r 1.0:1.2 lib/
2519 hg diff --git -r 1.0:1.2 lib/
2520
2520
2521 - get change stats relative to the last change on some date::
2521 - get change stats relative to the last change on some date::
2522
2522
2523 hg diff --stat -r "date('may 2')"
2523 hg diff --stat -r "date('may 2')"
2524
2524
2525 - diff all newly-added files that contain a keyword::
2525 - diff all newly-added files that contain a keyword::
2526
2526
2527 hg diff "set:added() and grep(GNU)"
2527 hg diff "set:added() and grep(GNU)"
2528
2528
2529 - compare a revision and its parents::
2529 - compare a revision and its parents::
2530
2530
2531 hg diff -c 9353 # compare against first parent
2531 hg diff -c 9353 # compare against first parent
2532 hg diff -r 9353^:9353 # same using revset syntax
2532 hg diff -r 9353^:9353 # same using revset syntax
2533 hg diff -r 9353^2:9353 # compare against the second parent
2533 hg diff -r 9353^2:9353 # compare against the second parent
2534
2534
2535 Returns 0 on success.
2535 Returns 0 on success.
2536 """
2536 """
2537
2537
2538 revs = opts.get('rev')
2538 revs = opts.get('rev')
2539 change = opts.get('change')
2539 change = opts.get('change')
2540 stat = opts.get('stat')
2540 stat = opts.get('stat')
2541 reverse = opts.get('reverse')
2541 reverse = opts.get('reverse')
2542
2542
2543 if revs and change:
2543 if revs and change:
2544 msg = _('cannot specify --rev and --change at the same time')
2544 msg = _('cannot specify --rev and --change at the same time')
2545 raise util.Abort(msg)
2545 raise util.Abort(msg)
2546 elif change:
2546 elif change:
2547 node2 = scmutil.revsingle(repo, change, None).node()
2547 node2 = scmutil.revsingle(repo, change, None).node()
2548 node1 = repo[node2].p1().node()
2548 node1 = repo[node2].p1().node()
2549 else:
2549 else:
2550 node1, node2 = scmutil.revpair(repo, revs)
2550 node1, node2 = scmutil.revpair(repo, revs)
2551
2551
2552 if reverse:
2552 if reverse:
2553 node1, node2 = node2, node1
2553 node1, node2 = node2, node1
2554
2554
2555 diffopts = patch.diffopts(ui, opts)
2555 diffopts = patch.diffopts(ui, opts)
2556 m = scmutil.match(repo[node2], pats, opts)
2556 m = scmutil.match(repo[node2], pats, opts)
2557 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2557 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2558 listsubrepos=opts.get('subrepos'))
2558 listsubrepos=opts.get('subrepos'))
2559
2559
2560 @command('^export',
2560 @command('^export',
2561 [('o', 'output', '',
2561 [('o', 'output', '',
2562 _('print output to file with formatted name'), _('FORMAT')),
2562 _('print output to file with formatted name'), _('FORMAT')),
2563 ('', 'switch-parent', None, _('diff against the second parent')),
2563 ('', 'switch-parent', None, _('diff against the second parent')),
2564 ('r', 'rev', [], _('revisions to export'), _('REV')),
2564 ('r', 'rev', [], _('revisions to export'), _('REV')),
2565 ] + diffopts,
2565 ] + diffopts,
2566 _('[OPTION]... [-o OUTFILESPEC] [-r] REV...'))
2566 _('[OPTION]... [-o OUTFILESPEC] [-r] REV...'))
2567 def export(ui, repo, *changesets, **opts):
2567 def export(ui, repo, *changesets, **opts):
2568 """dump the header and diffs for one or more changesets
2568 """dump the header and diffs for one or more changesets
2569
2569
2570 Print the changeset header and diffs for one or more revisions.
2570 Print the changeset header and diffs for one or more revisions.
2571
2571
2572 The information shown in the changeset header is: author, date,
2572 The information shown in the changeset header is: author, date,
2573 branch name (if non-default), changeset hash, parent(s) and commit
2573 branch name (if non-default), changeset hash, parent(s) and commit
2574 comment.
2574 comment.
2575
2575
2576 .. note::
2576 .. note::
2577 export may generate unexpected diff output for merge
2577 export may generate unexpected diff output for merge
2578 changesets, as it will compare the merge changeset against its
2578 changesets, as it will compare the merge changeset against its
2579 first parent only.
2579 first parent only.
2580
2580
2581 Output may be to a file, in which case the name of the file is
2581 Output may be to a file, in which case the name of the file is
2582 given using a format string. The formatting rules are as follows:
2582 given using a format string. The formatting rules are as follows:
2583
2583
2584 :``%%``: literal "%" character
2584 :``%%``: literal "%" character
2585 :``%H``: changeset hash (40 hexadecimal digits)
2585 :``%H``: changeset hash (40 hexadecimal digits)
2586 :``%N``: number of patches being generated
2586 :``%N``: number of patches being generated
2587 :``%R``: changeset revision number
2587 :``%R``: changeset revision number
2588 :``%b``: basename of the exporting repository
2588 :``%b``: basename of the exporting repository
2589 :``%h``: short-form changeset hash (12 hexadecimal digits)
2589 :``%h``: short-form changeset hash (12 hexadecimal digits)
2590 :``%m``: first line of the commit message (only alphanumeric characters)
2590 :``%m``: first line of the commit message (only alphanumeric characters)
2591 :``%n``: zero-padded sequence number, starting at 1
2591 :``%n``: zero-padded sequence number, starting at 1
2592 :``%r``: zero-padded changeset revision number
2592 :``%r``: zero-padded changeset revision number
2593
2593
2594 Without the -a/--text option, export will avoid generating diffs
2594 Without the -a/--text option, export will avoid generating diffs
2595 of files it detects as binary. With -a, export will generate a
2595 of files it detects as binary. With -a, export will generate a
2596 diff anyway, probably with undesirable results.
2596 diff anyway, probably with undesirable results.
2597
2597
2598 Use the -g/--git option to generate diffs in the git extended diff
2598 Use the -g/--git option to generate diffs in the git extended diff
2599 format. See :hg:`help diffs` for more information.
2599 format. See :hg:`help diffs` for more information.
2600
2600
2601 With the --switch-parent option, the diff will be against the
2601 With the --switch-parent option, the diff will be against the
2602 second parent. It can be useful to review a merge.
2602 second parent. It can be useful to review a merge.
2603
2603
2604 .. container:: verbose
2604 .. container:: verbose
2605
2605
2606 Examples:
2606 Examples:
2607
2607
2608 - use export and import to transplant a bugfix to the current
2608 - use export and import to transplant a bugfix to the current
2609 branch::
2609 branch::
2610
2610
2611 hg export -r 9353 | hg import -
2611 hg export -r 9353 | hg import -
2612
2612
2613 - export all the changesets between two revisions to a file with
2613 - export all the changesets between two revisions to a file with
2614 rename information::
2614 rename information::
2615
2615
2616 hg export --git -r 123:150 > changes.txt
2616 hg export --git -r 123:150 > changes.txt
2617
2617
2618 - split outgoing changes into a series of patches with
2618 - split outgoing changes into a series of patches with
2619 descriptive names::
2619 descriptive names::
2620
2620
2621 hg export -r "outgoing()" -o "%n-%m.patch"
2621 hg export -r "outgoing()" -o "%n-%m.patch"
2622
2622
2623 Returns 0 on success.
2623 Returns 0 on success.
2624 """
2624 """
2625 changesets += tuple(opts.get('rev', []))
2625 changesets += tuple(opts.get('rev', []))
2626 revs = scmutil.revrange(repo, changesets)
2626 revs = scmutil.revrange(repo, changesets)
2627 if not revs:
2627 if not revs:
2628 raise util.Abort(_("export requires at least one changeset"))
2628 raise util.Abort(_("export requires at least one changeset"))
2629 if len(revs) > 1:
2629 if len(revs) > 1:
2630 ui.note(_('exporting patches:\n'))
2630 ui.note(_('exporting patches:\n'))
2631 else:
2631 else:
2632 ui.note(_('exporting patch:\n'))
2632 ui.note(_('exporting patch:\n'))
2633 cmdutil.export(repo, revs, template=opts.get('output'),
2633 cmdutil.export(repo, revs, template=opts.get('output'),
2634 switch_parent=opts.get('switch_parent'),
2634 switch_parent=opts.get('switch_parent'),
2635 opts=patch.diffopts(ui, opts))
2635 opts=patch.diffopts(ui, opts))
2636
2636
2637 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2637 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2638 def forget(ui, repo, *pats, **opts):
2638 def forget(ui, repo, *pats, **opts):
2639 """forget the specified files on the next commit
2639 """forget the specified files on the next commit
2640
2640
2641 Mark the specified files so they will no longer be tracked
2641 Mark the specified files so they will no longer be tracked
2642 after the next commit.
2642 after the next commit.
2643
2643
2644 This only removes files from the current branch, not from the
2644 This only removes files from the current branch, not from the
2645 entire project history, and it does not delete them from the
2645 entire project history, and it does not delete them from the
2646 working directory.
2646 working directory.
2647
2647
2648 To undo a forget before the next commit, see :hg:`add`.
2648 To undo a forget before the next commit, see :hg:`add`.
2649
2649
2650 .. container:: verbose
2650 .. container:: verbose
2651
2651
2652 Examples:
2652 Examples:
2653
2653
2654 - forget newly-added binary files::
2654 - forget newly-added binary files::
2655
2655
2656 hg forget "set:added() and binary()"
2656 hg forget "set:added() and binary()"
2657
2657
2658 - forget files that would be excluded by .hgignore::
2658 - forget files that would be excluded by .hgignore::
2659
2659
2660 hg forget "set:hgignore()"
2660 hg forget "set:hgignore()"
2661
2661
2662 Returns 0 on success.
2662 Returns 0 on success.
2663 """
2663 """
2664
2664
2665 if not pats:
2665 if not pats:
2666 raise util.Abort(_('no files specified'))
2666 raise util.Abort(_('no files specified'))
2667
2667
2668 m = scmutil.match(repo[None], pats, opts)
2668 m = scmutil.match(repo[None], pats, opts)
2669 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2669 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2670 return rejected and 1 or 0
2670 return rejected and 1 or 0
2671
2671
2672 @command(
2672 @command(
2673 'graft',
2673 'graft',
2674 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2674 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2675 ('c', 'continue', False, _('resume interrupted graft')),
2675 ('c', 'continue', False, _('resume interrupted graft')),
2676 ('e', 'edit', False, _('invoke editor on commit messages')),
2676 ('e', 'edit', False, _('invoke editor on commit messages')),
2677 ('', 'log', None, _('append graft info to log message')),
2677 ('', 'log', None, _('append graft info to log message')),
2678 ('D', 'currentdate', False,
2678 ('D', 'currentdate', False,
2679 _('record the current date as commit date')),
2679 _('record the current date as commit date')),
2680 ('U', 'currentuser', False,
2680 ('U', 'currentuser', False,
2681 _('record the current user as committer'), _('DATE'))]
2681 _('record the current user as committer'), _('DATE'))]
2682 + commitopts2 + mergetoolopts + dryrunopts,
2682 + commitopts2 + mergetoolopts + dryrunopts,
2683 _('[OPTION]... [-r] REV...'))
2683 _('[OPTION]... [-r] REV...'))
2684 def graft(ui, repo, *revs, **opts):
2684 def graft(ui, repo, *revs, **opts):
2685 '''copy changes from other branches onto the current branch
2685 '''copy changes from other branches onto the current branch
2686
2686
2687 This command uses Mercurial's merge logic to copy individual
2687 This command uses Mercurial's merge logic to copy individual
2688 changes from other branches without merging branches in the
2688 changes from other branches without merging branches in the
2689 history graph. This is sometimes known as 'backporting' or
2689 history graph. This is sometimes known as 'backporting' or
2690 'cherry-picking'. By default, graft will copy user, date, and
2690 'cherry-picking'. By default, graft will copy user, date, and
2691 description from the source changesets.
2691 description from the source changesets.
2692
2692
2693 Changesets that are ancestors of the current revision, that have
2693 Changesets that are ancestors of the current revision, that have
2694 already been grafted, or that are merges will be skipped.
2694 already been grafted, or that are merges will be skipped.
2695
2695
2696 If --log is specified, log messages will have a comment appended
2696 If --log is specified, log messages will have a comment appended
2697 of the form::
2697 of the form::
2698
2698
2699 (grafted from CHANGESETHASH)
2699 (grafted from CHANGESETHASH)
2700
2700
2701 If a graft merge results in conflicts, the graft process is
2701 If a graft merge results in conflicts, the graft process is
2702 interrupted so that the current merge can be manually resolved.
2702 interrupted so that the current merge can be manually resolved.
2703 Once all conflicts are addressed, the graft process can be
2703 Once all conflicts are addressed, the graft process can be
2704 continued with the -c/--continue option.
2704 continued with the -c/--continue option.
2705
2705
2706 .. note::
2706 .. note::
2707 The -c/--continue option does not reapply earlier options.
2707 The -c/--continue option does not reapply earlier options.
2708
2708
2709 .. container:: verbose
2709 .. container:: verbose
2710
2710
2711 Examples:
2711 Examples:
2712
2712
2713 - copy a single change to the stable branch and edit its description::
2713 - copy a single change to the stable branch and edit its description::
2714
2714
2715 hg update stable
2715 hg update stable
2716 hg graft --edit 9393
2716 hg graft --edit 9393
2717
2717
2718 - graft a range of changesets with one exception, updating dates::
2718 - graft a range of changesets with one exception, updating dates::
2719
2719
2720 hg graft -D "2085::2093 and not 2091"
2720 hg graft -D "2085::2093 and not 2091"
2721
2721
2722 - continue a graft after resolving conflicts::
2722 - continue a graft after resolving conflicts::
2723
2723
2724 hg graft -c
2724 hg graft -c
2725
2725
2726 - show the source of a grafted changeset::
2726 - show the source of a grafted changeset::
2727
2727
2728 hg log --debug -r tip
2728 hg log --debug -r tip
2729
2729
2730 Returns 0 on successful completion.
2730 Returns 0 on successful completion.
2731 '''
2731 '''
2732
2732
2733 revs = list(revs)
2733 revs = list(revs)
2734 revs.extend(opts['rev'])
2734 revs.extend(opts['rev'])
2735
2735
2736 if not opts.get('user') and opts.get('currentuser'):
2736 if not opts.get('user') and opts.get('currentuser'):
2737 opts['user'] = ui.username()
2737 opts['user'] = ui.username()
2738 if not opts.get('date') and opts.get('currentdate'):
2738 if not opts.get('date') and opts.get('currentdate'):
2739 opts['date'] = "%d %d" % util.makedate()
2739 opts['date'] = "%d %d" % util.makedate()
2740
2740
2741 editor = None
2741 editor = None
2742 if opts.get('edit'):
2742 if opts.get('edit'):
2743 editor = cmdutil.commitforceeditor
2743 editor = cmdutil.commitforceeditor
2744
2744
2745 cont = False
2745 cont = False
2746 if opts['continue']:
2746 if opts['continue']:
2747 cont = True
2747 cont = True
2748 if revs:
2748 if revs:
2749 raise util.Abort(_("can't specify --continue and revisions"))
2749 raise util.Abort(_("can't specify --continue and revisions"))
2750 # read in unfinished revisions
2750 # read in unfinished revisions
2751 try:
2751 try:
2752 nodes = repo.opener.read('graftstate').splitlines()
2752 nodes = repo.opener.read('graftstate').splitlines()
2753 revs = [repo[node].rev() for node in nodes]
2753 revs = [repo[node].rev() for node in nodes]
2754 except IOError, inst:
2754 except IOError, inst:
2755 if inst.errno != errno.ENOENT:
2755 if inst.errno != errno.ENOENT:
2756 raise
2756 raise
2757 raise util.Abort(_("no graft state found, can't continue"))
2757 raise util.Abort(_("no graft state found, can't continue"))
2758 else:
2758 else:
2759 cmdutil.bailifchanged(repo)
2759 cmdutil.bailifchanged(repo)
2760 if not revs:
2760 if not revs:
2761 raise util.Abort(_('no revisions specified'))
2761 raise util.Abort(_('no revisions specified'))
2762 revs = scmutil.revrange(repo, revs)
2762 revs = scmutil.revrange(repo, revs)
2763
2763
2764 # check for merges
2764 # check for merges
2765 for rev in repo.revs('%ld and merge()', revs):
2765 for rev in repo.revs('%ld and merge()', revs):
2766 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2766 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2767 revs.remove(rev)
2767 revs.remove(rev)
2768 if not revs:
2768 if not revs:
2769 return -1
2769 return -1
2770
2770
2771 # check for ancestors of dest branch
2771 # check for ancestors of dest branch
2772 for rev in repo.revs('::. and %ld', revs):
2772 for rev in repo.revs('::. and %ld', revs):
2773 ui.warn(_('skipping ancestor revision %s\n') % rev)
2773 ui.warn(_('skipping ancestor revision %s\n') % rev)
2774 revs.remove(rev)
2774 revs.remove(rev)
2775 if not revs:
2775 if not revs:
2776 return -1
2776 return -1
2777
2777
2778 # analyze revs for earlier grafts
2778 # analyze revs for earlier grafts
2779 ids = {}
2779 ids = {}
2780 for ctx in repo.set("%ld", revs):
2780 for ctx in repo.set("%ld", revs):
2781 ids[ctx.hex()] = ctx.rev()
2781 ids[ctx.hex()] = ctx.rev()
2782 n = ctx.extra().get('source')
2782 n = ctx.extra().get('source')
2783 if n:
2783 if n:
2784 ids[n] = ctx.rev()
2784 ids[n] = ctx.rev()
2785
2785
2786 # check ancestors for earlier grafts
2786 # check ancestors for earlier grafts
2787 ui.debug('scanning for duplicate grafts\n')
2787 ui.debug('scanning for duplicate grafts\n')
2788 for ctx in repo.set("::. - ::%ld", revs):
2788 for ctx in repo.set("::. - ::%ld", revs):
2789 n = ctx.extra().get('source')
2789 n = ctx.extra().get('source')
2790 if n in ids:
2790 if n in ids:
2791 r = repo[n].rev()
2791 r = repo[n].rev()
2792 if r in revs:
2792 if r in revs:
2793 ui.warn(_('skipping already grafted revision %s\n') % r)
2793 ui.warn(_('skipping already grafted revision %s\n') % r)
2794 revs.remove(r)
2794 revs.remove(r)
2795 elif ids[n] in revs:
2795 elif ids[n] in revs:
2796 ui.warn(_('skipping already grafted revision %s '
2796 ui.warn(_('skipping already grafted revision %s '
2797 '(same origin %d)\n') % (ids[n], r))
2797 '(same origin %d)\n') % (ids[n], r))
2798 revs.remove(ids[n])
2798 revs.remove(ids[n])
2799 elif ctx.hex() in ids:
2799 elif ctx.hex() in ids:
2800 r = ids[ctx.hex()]
2800 r = ids[ctx.hex()]
2801 ui.warn(_('skipping already grafted revision %s '
2801 ui.warn(_('skipping already grafted revision %s '
2802 '(was grafted from %d)\n') % (r, ctx.rev()))
2802 '(was grafted from %d)\n') % (r, ctx.rev()))
2803 revs.remove(r)
2803 revs.remove(r)
2804 if not revs:
2804 if not revs:
2805 return -1
2805 return -1
2806
2806
2807 wlock = repo.wlock()
2807 wlock = repo.wlock()
2808 try:
2808 try:
2809 for pos, ctx in enumerate(repo.set("%ld", revs)):
2809 for pos, ctx in enumerate(repo.set("%ld", revs)):
2810 current = repo['.']
2810 current = repo['.']
2811
2811
2812 ui.status(_('grafting revision %s\n') % ctx.rev())
2812 ui.status(_('grafting revision %s\n') % ctx.rev())
2813 if opts.get('dry_run'):
2813 if opts.get('dry_run'):
2814 continue
2814 continue
2815
2815
2816 # we don't merge the first commit when continuing
2816 # we don't merge the first commit when continuing
2817 if not cont:
2817 if not cont:
2818 # perform the graft merge with p1(rev) as 'ancestor'
2818 # perform the graft merge with p1(rev) as 'ancestor'
2819 try:
2819 try:
2820 # ui.forcemerge is an internal variable, do not document
2820 # ui.forcemerge is an internal variable, do not document
2821 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2821 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2822 stats = mergemod.update(repo, ctx.node(), True, True, False,
2822 stats = mergemod.update(repo, ctx.node(), True, True, False,
2823 ctx.p1().node())
2823 ctx.p1().node())
2824 finally:
2824 finally:
2825 repo.ui.setconfig('ui', 'forcemerge', '')
2825 repo.ui.setconfig('ui', 'forcemerge', '')
2826 # report any conflicts
2826 # report any conflicts
2827 if stats and stats[3] > 0:
2827 if stats and stats[3] > 0:
2828 # write out state for --continue
2828 # write out state for --continue
2829 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2829 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2830 repo.opener.write('graftstate', ''.join(nodelines))
2830 repo.opener.write('graftstate', ''.join(nodelines))
2831 raise util.Abort(
2831 raise util.Abort(
2832 _("unresolved conflicts, can't continue"),
2832 _("unresolved conflicts, can't continue"),
2833 hint=_('use hg resolve and hg graft --continue'))
2833 hint=_('use hg resolve and hg graft --continue'))
2834 else:
2834 else:
2835 cont = False
2835 cont = False
2836
2836
2837 # drop the second merge parent
2837 # drop the second merge parent
2838 repo.setparents(current.node(), nullid)
2838 repo.setparents(current.node(), nullid)
2839 repo.dirstate.write()
2839 repo.dirstate.write()
2840 # fix up dirstate for copies and renames
2840 # fix up dirstate for copies and renames
2841 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
2841 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
2842
2842
2843 # commit
2843 # commit
2844 source = ctx.extra().get('source')
2844 source = ctx.extra().get('source')
2845 if not source:
2845 if not source:
2846 source = ctx.hex()
2846 source = ctx.hex()
2847 extra = {'source': source}
2847 extra = {'source': source}
2848 user = ctx.user()
2848 user = ctx.user()
2849 if opts.get('user'):
2849 if opts.get('user'):
2850 user = opts['user']
2850 user = opts['user']
2851 date = ctx.date()
2851 date = ctx.date()
2852 if opts.get('date'):
2852 if opts.get('date'):
2853 date = opts['date']
2853 date = opts['date']
2854 message = ctx.description()
2854 message = ctx.description()
2855 if opts.get('log'):
2855 if opts.get('log'):
2856 message += '\n(grafted from %s)' % ctx.hex()
2856 message += '\n(grafted from %s)' % ctx.hex()
2857 node = repo.commit(text=message, user=user,
2857 node = repo.commit(text=message, user=user,
2858 date=date, extra=extra, editor=editor)
2858 date=date, extra=extra, editor=editor)
2859 if node is None:
2859 if node is None:
2860 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
2860 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
2861 finally:
2861 finally:
2862 wlock.release()
2862 wlock.release()
2863
2863
2864 # remove state when we complete successfully
2864 # remove state when we complete successfully
2865 if not opts.get('dry_run') and os.path.exists(repo.join('graftstate')):
2865 if not opts.get('dry_run') and os.path.exists(repo.join('graftstate')):
2866 util.unlinkpath(repo.join('graftstate'))
2866 util.unlinkpath(repo.join('graftstate'))
2867
2867
2868 return 0
2868 return 0
2869
2869
2870 @command('grep',
2870 @command('grep',
2871 [('0', 'print0', None, _('end fields with NUL')),
2871 [('0', 'print0', None, _('end fields with NUL')),
2872 ('', 'all', None, _('print all revisions that match')),
2872 ('', 'all', None, _('print all revisions that match')),
2873 ('a', 'text', None, _('treat all files as text')),
2873 ('a', 'text', None, _('treat all files as text')),
2874 ('f', 'follow', None,
2874 ('f', 'follow', None,
2875 _('follow changeset history,'
2875 _('follow changeset history,'
2876 ' or file history across copies and renames')),
2876 ' or file history across copies and renames')),
2877 ('i', 'ignore-case', None, _('ignore case when matching')),
2877 ('i', 'ignore-case', None, _('ignore case when matching')),
2878 ('l', 'files-with-matches', None,
2878 ('l', 'files-with-matches', None,
2879 _('print only filenames and revisions that match')),
2879 _('print only filenames and revisions that match')),
2880 ('n', 'line-number', None, _('print matching line numbers')),
2880 ('n', 'line-number', None, _('print matching line numbers')),
2881 ('r', 'rev', [],
2881 ('r', 'rev', [],
2882 _('only search files changed within revision range'), _('REV')),
2882 _('only search files changed within revision range'), _('REV')),
2883 ('u', 'user', None, _('list the author (long with -v)')),
2883 ('u', 'user', None, _('list the author (long with -v)')),
2884 ('d', 'date', None, _('list the date (short with -q)')),
2884 ('d', 'date', None, _('list the date (short with -q)')),
2885 ] + walkopts,
2885 ] + walkopts,
2886 _('[OPTION]... PATTERN [FILE]...'))
2886 _('[OPTION]... PATTERN [FILE]...'))
2887 def grep(ui, repo, pattern, *pats, **opts):
2887 def grep(ui, repo, pattern, *pats, **opts):
2888 """search for a pattern in specified files and revisions
2888 """search for a pattern in specified files and revisions
2889
2889
2890 Search revisions of files for a regular expression.
2890 Search revisions of files for a regular expression.
2891
2891
2892 This command behaves differently than Unix grep. It only accepts
2892 This command behaves differently than Unix grep. It only accepts
2893 Python/Perl regexps. It searches repository history, not the
2893 Python/Perl regexps. It searches repository history, not the
2894 working directory. It always prints the revision number in which a
2894 working directory. It always prints the revision number in which a
2895 match appears.
2895 match appears.
2896
2896
2897 By default, grep only prints output for the first revision of a
2897 By default, grep only prints output for the first revision of a
2898 file in which it finds a match. To get it to print every revision
2898 file in which it finds a match. To get it to print every revision
2899 that contains a change in match status ("-" for a match that
2899 that contains a change in match status ("-" for a match that
2900 becomes a non-match, or "+" for a non-match that becomes a match),
2900 becomes a non-match, or "+" for a non-match that becomes a match),
2901 use the --all flag.
2901 use the --all flag.
2902
2902
2903 Returns 0 if a match is found, 1 otherwise.
2903 Returns 0 if a match is found, 1 otherwise.
2904 """
2904 """
2905 reflags = re.M
2905 reflags = re.M
2906 if opts.get('ignore_case'):
2906 if opts.get('ignore_case'):
2907 reflags |= re.I
2907 reflags |= re.I
2908 try:
2908 try:
2909 regexp = re.compile(pattern, reflags)
2909 regexp = re.compile(pattern, reflags)
2910 except re.error, inst:
2910 except re.error, inst:
2911 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2911 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2912 return 1
2912 return 1
2913 sep, eol = ':', '\n'
2913 sep, eol = ':', '\n'
2914 if opts.get('print0'):
2914 if opts.get('print0'):
2915 sep = eol = '\0'
2915 sep = eol = '\0'
2916
2916
2917 getfile = util.lrucachefunc(repo.file)
2917 getfile = util.lrucachefunc(repo.file)
2918
2918
2919 def matchlines(body):
2919 def matchlines(body):
2920 begin = 0
2920 begin = 0
2921 linenum = 0
2921 linenum = 0
2922 while True:
2922 while True:
2923 match = regexp.search(body, begin)
2923 match = regexp.search(body, begin)
2924 if not match:
2924 if not match:
2925 break
2925 break
2926 mstart, mend = match.span()
2926 mstart, mend = match.span()
2927 linenum += body.count('\n', begin, mstart) + 1
2927 linenum += body.count('\n', begin, mstart) + 1
2928 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2928 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2929 begin = body.find('\n', mend) + 1 or len(body) + 1
2929 begin = body.find('\n', mend) + 1 or len(body) + 1
2930 lend = begin - 1
2930 lend = begin - 1
2931 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2931 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2932
2932
2933 class linestate(object):
2933 class linestate(object):
2934 def __init__(self, line, linenum, colstart, colend):
2934 def __init__(self, line, linenum, colstart, colend):
2935 self.line = line
2935 self.line = line
2936 self.linenum = linenum
2936 self.linenum = linenum
2937 self.colstart = colstart
2937 self.colstart = colstart
2938 self.colend = colend
2938 self.colend = colend
2939
2939
2940 def __hash__(self):
2940 def __hash__(self):
2941 return hash((self.linenum, self.line))
2941 return hash((self.linenum, self.line))
2942
2942
2943 def __eq__(self, other):
2943 def __eq__(self, other):
2944 return self.line == other.line
2944 return self.line == other.line
2945
2945
2946 matches = {}
2946 matches = {}
2947 copies = {}
2947 copies = {}
2948 def grepbody(fn, rev, body):
2948 def grepbody(fn, rev, body):
2949 matches[rev].setdefault(fn, [])
2949 matches[rev].setdefault(fn, [])
2950 m = matches[rev][fn]
2950 m = matches[rev][fn]
2951 for lnum, cstart, cend, line in matchlines(body):
2951 for lnum, cstart, cend, line in matchlines(body):
2952 s = linestate(line, lnum, cstart, cend)
2952 s = linestate(line, lnum, cstart, cend)
2953 m.append(s)
2953 m.append(s)
2954
2954
2955 def difflinestates(a, b):
2955 def difflinestates(a, b):
2956 sm = difflib.SequenceMatcher(None, a, b)
2956 sm = difflib.SequenceMatcher(None, a, b)
2957 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2957 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2958 if tag == 'insert':
2958 if tag == 'insert':
2959 for i in xrange(blo, bhi):
2959 for i in xrange(blo, bhi):
2960 yield ('+', b[i])
2960 yield ('+', b[i])
2961 elif tag == 'delete':
2961 elif tag == 'delete':
2962 for i in xrange(alo, ahi):
2962 for i in xrange(alo, ahi):
2963 yield ('-', a[i])
2963 yield ('-', a[i])
2964 elif tag == 'replace':
2964 elif tag == 'replace':
2965 for i in xrange(alo, ahi):
2965 for i in xrange(alo, ahi):
2966 yield ('-', a[i])
2966 yield ('-', a[i])
2967 for i in xrange(blo, bhi):
2967 for i in xrange(blo, bhi):
2968 yield ('+', b[i])
2968 yield ('+', b[i])
2969
2969
2970 def display(fn, ctx, pstates, states):
2970 def display(fn, ctx, pstates, states):
2971 rev = ctx.rev()
2971 rev = ctx.rev()
2972 datefunc = ui.quiet and util.shortdate or util.datestr
2972 datefunc = ui.quiet and util.shortdate or util.datestr
2973 found = False
2973 found = False
2974 filerevmatches = {}
2974 filerevmatches = {}
2975 def binary():
2975 def binary():
2976 flog = getfile(fn)
2976 flog = getfile(fn)
2977 return util.binary(flog.read(ctx.filenode(fn)))
2977 return util.binary(flog.read(ctx.filenode(fn)))
2978
2978
2979 if opts.get('all'):
2979 if opts.get('all'):
2980 iter = difflinestates(pstates, states)
2980 iter = difflinestates(pstates, states)
2981 else:
2981 else:
2982 iter = [('', l) for l in states]
2982 iter = [('', l) for l in states]
2983 for change, l in iter:
2983 for change, l in iter:
2984 cols = [fn, str(rev)]
2984 cols = [fn, str(rev)]
2985 before, match, after = None, None, None
2985 before, match, after = None, None, None
2986 if opts.get('line_number'):
2986 if opts.get('line_number'):
2987 cols.append(str(l.linenum))
2987 cols.append(str(l.linenum))
2988 if opts.get('all'):
2988 if opts.get('all'):
2989 cols.append(change)
2989 cols.append(change)
2990 if opts.get('user'):
2990 if opts.get('user'):
2991 cols.append(ui.shortuser(ctx.user()))
2991 cols.append(ui.shortuser(ctx.user()))
2992 if opts.get('date'):
2992 if opts.get('date'):
2993 cols.append(datefunc(ctx.date()))
2993 cols.append(datefunc(ctx.date()))
2994 if opts.get('files_with_matches'):
2994 if opts.get('files_with_matches'):
2995 c = (fn, rev)
2995 c = (fn, rev)
2996 if c in filerevmatches:
2996 if c in filerevmatches:
2997 continue
2997 continue
2998 filerevmatches[c] = 1
2998 filerevmatches[c] = 1
2999 else:
2999 else:
3000 before = l.line[:l.colstart]
3000 before = l.line[:l.colstart]
3001 match = l.line[l.colstart:l.colend]
3001 match = l.line[l.colstart:l.colend]
3002 after = l.line[l.colend:]
3002 after = l.line[l.colend:]
3003 ui.write(sep.join(cols))
3003 ui.write(sep.join(cols))
3004 if before is not None:
3004 if before is not None:
3005 if not opts.get('text') and binary():
3005 if not opts.get('text') and binary():
3006 ui.write(sep + " Binary file matches")
3006 ui.write(sep + " Binary file matches")
3007 else:
3007 else:
3008 ui.write(sep + before)
3008 ui.write(sep + before)
3009 ui.write(match, label='grep.match')
3009 ui.write(match, label='grep.match')
3010 ui.write(after)
3010 ui.write(after)
3011 ui.write(eol)
3011 ui.write(eol)
3012 found = True
3012 found = True
3013 return found
3013 return found
3014
3014
3015 skip = {}
3015 skip = {}
3016 revfiles = {}
3016 revfiles = {}
3017 matchfn = scmutil.match(repo[None], pats, opts)
3017 matchfn = scmutil.match(repo[None], pats, opts)
3018 found = False
3018 found = False
3019 follow = opts.get('follow')
3019 follow = opts.get('follow')
3020
3020
3021 def prep(ctx, fns):
3021 def prep(ctx, fns):
3022 rev = ctx.rev()
3022 rev = ctx.rev()
3023 pctx = ctx.p1()
3023 pctx = ctx.p1()
3024 parent = pctx.rev()
3024 parent = pctx.rev()
3025 matches.setdefault(rev, {})
3025 matches.setdefault(rev, {})
3026 matches.setdefault(parent, {})
3026 matches.setdefault(parent, {})
3027 files = revfiles.setdefault(rev, [])
3027 files = revfiles.setdefault(rev, [])
3028 for fn in fns:
3028 for fn in fns:
3029 flog = getfile(fn)
3029 flog = getfile(fn)
3030 try:
3030 try:
3031 fnode = ctx.filenode(fn)
3031 fnode = ctx.filenode(fn)
3032 except error.LookupError:
3032 except error.LookupError:
3033 continue
3033 continue
3034
3034
3035 copied = flog.renamed(fnode)
3035 copied = flog.renamed(fnode)
3036 copy = follow and copied and copied[0]
3036 copy = follow and copied and copied[0]
3037 if copy:
3037 if copy:
3038 copies.setdefault(rev, {})[fn] = copy
3038 copies.setdefault(rev, {})[fn] = copy
3039 if fn in skip:
3039 if fn in skip:
3040 if copy:
3040 if copy:
3041 skip[copy] = True
3041 skip[copy] = True
3042 continue
3042 continue
3043 files.append(fn)
3043 files.append(fn)
3044
3044
3045 if fn not in matches[rev]:
3045 if fn not in matches[rev]:
3046 grepbody(fn, rev, flog.read(fnode))
3046 grepbody(fn, rev, flog.read(fnode))
3047
3047
3048 pfn = copy or fn
3048 pfn = copy or fn
3049 if pfn not in matches[parent]:
3049 if pfn not in matches[parent]:
3050 try:
3050 try:
3051 fnode = pctx.filenode(pfn)
3051 fnode = pctx.filenode(pfn)
3052 grepbody(pfn, parent, flog.read(fnode))
3052 grepbody(pfn, parent, flog.read(fnode))
3053 except error.LookupError:
3053 except error.LookupError:
3054 pass
3054 pass
3055
3055
3056 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3056 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3057 rev = ctx.rev()
3057 rev = ctx.rev()
3058 parent = ctx.p1().rev()
3058 parent = ctx.p1().rev()
3059 for fn in sorted(revfiles.get(rev, [])):
3059 for fn in sorted(revfiles.get(rev, [])):
3060 states = matches[rev][fn]
3060 states = matches[rev][fn]
3061 copy = copies.get(rev, {}).get(fn)
3061 copy = copies.get(rev, {}).get(fn)
3062 if fn in skip:
3062 if fn in skip:
3063 if copy:
3063 if copy:
3064 skip[copy] = True
3064 skip[copy] = True
3065 continue
3065 continue
3066 pstates = matches.get(parent, {}).get(copy or fn, [])
3066 pstates = matches.get(parent, {}).get(copy or fn, [])
3067 if pstates or states:
3067 if pstates or states:
3068 r = display(fn, ctx, pstates, states)
3068 r = display(fn, ctx, pstates, states)
3069 found = found or r
3069 found = found or r
3070 if r and not opts.get('all'):
3070 if r and not opts.get('all'):
3071 skip[fn] = True
3071 skip[fn] = True
3072 if copy:
3072 if copy:
3073 skip[copy] = True
3073 skip[copy] = True
3074 del matches[rev]
3074 del matches[rev]
3075 del revfiles[rev]
3075 del revfiles[rev]
3076
3076
3077 return not found
3077 return not found
3078
3078
3079 @command('heads',
3079 @command('heads',
3080 [('r', 'rev', '',
3080 [('r', 'rev', '',
3081 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3081 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3082 ('t', 'topo', False, _('show topological heads only')),
3082 ('t', 'topo', False, _('show topological heads only')),
3083 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3083 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3084 ('c', 'closed', False, _('show normal and closed branch heads')),
3084 ('c', 'closed', False, _('show normal and closed branch heads')),
3085 ] + templateopts,
3085 ] + templateopts,
3086 _('[-ct] [-r STARTREV] [REV]...'))
3086 _('[-ct] [-r STARTREV] [REV]...'))
3087 def heads(ui, repo, *branchrevs, **opts):
3087 def heads(ui, repo, *branchrevs, **opts):
3088 """show current repository heads or show branch heads
3088 """show current repository heads or show branch heads
3089
3089
3090 With no arguments, show all repository branch heads.
3090 With no arguments, show all repository branch heads.
3091
3091
3092 Repository "heads" are changesets with no child changesets. They are
3092 Repository "heads" are changesets with no child changesets. They are
3093 where development generally takes place and are the usual targets
3093 where development generally takes place and are the usual targets
3094 for update and merge operations. Branch heads are changesets that have
3094 for update and merge operations. Branch heads are changesets that have
3095 no child changeset on the same branch.
3095 no child changeset on the same branch.
3096
3096
3097 If one or more REVs are given, only branch heads on the branches
3097 If one or more REVs are given, only branch heads on the branches
3098 associated with the specified changesets are shown. This means
3098 associated with the specified changesets are shown. This means
3099 that you can use :hg:`heads foo` to see the heads on a branch
3099 that you can use :hg:`heads foo` to see the heads on a branch
3100 named ``foo``.
3100 named ``foo``.
3101
3101
3102 If -c/--closed is specified, also show branch heads marked closed
3102 If -c/--closed is specified, also show branch heads marked closed
3103 (see :hg:`commit --close-branch`).
3103 (see :hg:`commit --close-branch`).
3104
3104
3105 If STARTREV is specified, only those heads that are descendants of
3105 If STARTREV is specified, only those heads that are descendants of
3106 STARTREV will be displayed.
3106 STARTREV will be displayed.
3107
3107
3108 If -t/--topo is specified, named branch mechanics will be ignored and only
3108 If -t/--topo is specified, named branch mechanics will be ignored and only
3109 changesets without children will be shown.
3109 changesets without children will be shown.
3110
3110
3111 Returns 0 if matching heads are found, 1 if not.
3111 Returns 0 if matching heads are found, 1 if not.
3112 """
3112 """
3113
3113
3114 start = None
3114 start = None
3115 if 'rev' in opts:
3115 if 'rev' in opts:
3116 start = scmutil.revsingle(repo, opts['rev'], None).node()
3116 start = scmutil.revsingle(repo, opts['rev'], None).node()
3117
3117
3118 if opts.get('topo'):
3118 if opts.get('topo'):
3119 heads = [repo[h] for h in repo.heads(start)]
3119 heads = [repo[h] for h in repo.heads(start)]
3120 else:
3120 else:
3121 heads = []
3121 heads = []
3122 for branch in repo.branchmap():
3122 for branch in repo.branchmap():
3123 heads += repo.branchheads(branch, start, opts.get('closed'))
3123 heads += repo.branchheads(branch, start, opts.get('closed'))
3124 heads = [repo[h] for h in heads]
3124 heads = [repo[h] for h in heads]
3125
3125
3126 if branchrevs:
3126 if branchrevs:
3127 branches = set(repo[br].branch() for br in branchrevs)
3127 branches = set(repo[br].branch() for br in branchrevs)
3128 heads = [h for h in heads if h.branch() in branches]
3128 heads = [h for h in heads if h.branch() in branches]
3129
3129
3130 if opts.get('active') and branchrevs:
3130 if opts.get('active') and branchrevs:
3131 dagheads = repo.heads(start)
3131 dagheads = repo.heads(start)
3132 heads = [h for h in heads if h.node() in dagheads]
3132 heads = [h for h in heads if h.node() in dagheads]
3133
3133
3134 if branchrevs:
3134 if branchrevs:
3135 haveheads = set(h.branch() for h in heads)
3135 haveheads = set(h.branch() for h in heads)
3136 if branches - haveheads:
3136 if branches - haveheads:
3137 headless = ', '.join(b for b in branches - haveheads)
3137 headless = ', '.join(b for b in branches - haveheads)
3138 msg = _('no open branch heads found on branches %s')
3138 msg = _('no open branch heads found on branches %s')
3139 if opts.get('rev'):
3139 if opts.get('rev'):
3140 msg += _(' (started at %s)') % opts['rev']
3140 msg += _(' (started at %s)') % opts['rev']
3141 ui.warn((msg + '\n') % headless)
3141 ui.warn((msg + '\n') % headless)
3142
3142
3143 if not heads:
3143 if not heads:
3144 return 1
3144 return 1
3145
3145
3146 heads = sorted(heads, key=lambda x: -x.rev())
3146 heads = sorted(heads, key=lambda x: -x.rev())
3147 displayer = cmdutil.show_changeset(ui, repo, opts)
3147 displayer = cmdutil.show_changeset(ui, repo, opts)
3148 for ctx in heads:
3148 for ctx in heads:
3149 displayer.show(ctx)
3149 displayer.show(ctx)
3150 displayer.close()
3150 displayer.close()
3151
3151
3152 @command('help',
3152 @command('help',
3153 [('e', 'extension', None, _('show only help for extensions')),
3153 [('e', 'extension', None, _('show only help for extensions')),
3154 ('c', 'command', None, _('show only help for commands')),
3154 ('c', 'command', None, _('show only help for commands')),
3155 ('k', 'keyword', '', _('show topics matching keyword')),
3155 ('k', 'keyword', '', _('show topics matching keyword')),
3156 ],
3156 ],
3157 _('[-ec] [TOPIC]'))
3157 _('[-ec] [TOPIC]'))
3158 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
3158 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
3159 """show help for a given topic or a help overview
3159 """show help for a given topic or a help overview
3160
3160
3161 With no arguments, print a list of commands with short help messages.
3161 With no arguments, print a list of commands with short help messages.
3162
3162
3163 Given a topic, extension, or command name, print help for that
3163 Given a topic, extension, or command name, print help for that
3164 topic.
3164 topic.
3165
3165
3166 Returns 0 if successful.
3166 Returns 0 if successful.
3167 """
3167 """
3168
3168
3169 textwidth = min(ui.termwidth(), 80) - 2
3169 textwidth = min(ui.termwidth(), 80) - 2
3170
3170
3171 def helpcmd(name):
3171 def helpcmd(name):
3172 try:
3172 try:
3173 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3173 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3174 except error.AmbiguousCommand, inst:
3174 except error.AmbiguousCommand, inst:
3175 # py3k fix: except vars can't be used outside the scope of the
3175 # py3k fix: except vars can't be used outside the scope of the
3176 # except block, nor can be used inside a lambda. python issue4617
3176 # except block, nor can be used inside a lambda. python issue4617
3177 prefix = inst.args[0]
3177 prefix = inst.args[0]
3178 select = lambda c: c.lstrip('^').startswith(prefix)
3178 select = lambda c: c.lstrip('^').startswith(prefix)
3179 rst = helplist(select)
3179 rst = helplist(select)
3180 return rst
3180 return rst
3181
3181
3182 rst = []
3182 rst = []
3183
3183
3184 # check if it's an invalid alias and display its error if it is
3184 # check if it's an invalid alias and display its error if it is
3185 if getattr(entry[0], 'badalias', False):
3185 if getattr(entry[0], 'badalias', False):
3186 if not unknowncmd:
3186 if not unknowncmd:
3187 ui.pushbuffer()
3187 ui.pushbuffer()
3188 entry[0](ui)
3188 entry[0](ui)
3189 rst.append(ui.popbuffer())
3189 rst.append(ui.popbuffer())
3190 return rst
3190 return rst
3191
3191
3192 # synopsis
3192 # synopsis
3193 if len(entry) > 2:
3193 if len(entry) > 2:
3194 if entry[2].startswith('hg'):
3194 if entry[2].startswith('hg'):
3195 rst.append("%s\n" % entry[2])
3195 rst.append("%s\n" % entry[2])
3196 else:
3196 else:
3197 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
3197 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
3198 else:
3198 else:
3199 rst.append('hg %s\n' % aliases[0])
3199 rst.append('hg %s\n' % aliases[0])
3200 # aliases
3200 # aliases
3201 if full and not ui.quiet and len(aliases) > 1:
3201 if full and not ui.quiet and len(aliases) > 1:
3202 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
3202 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
3203 rst.append('\n')
3203 rst.append('\n')
3204
3204
3205 # description
3205 # description
3206 doc = gettext(entry[0].__doc__)
3206 doc = gettext(entry[0].__doc__)
3207 if not doc:
3207 if not doc:
3208 doc = _("(no help text available)")
3208 doc = _("(no help text available)")
3209 if util.safehasattr(entry[0], 'definition'): # aliased command
3209 if util.safehasattr(entry[0], 'definition'): # aliased command
3210 if entry[0].definition.startswith('!'): # shell alias
3210 if entry[0].definition.startswith('!'): # shell alias
3211 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3211 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3212 else:
3212 else:
3213 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3213 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3214 doc = doc.splitlines(True)
3214 doc = doc.splitlines(True)
3215 if ui.quiet or not full:
3215 if ui.quiet or not full:
3216 rst.append(doc[0])
3216 rst.append(doc[0])
3217 else:
3217 else:
3218 rst.extend(doc)
3218 rst.extend(doc)
3219 rst.append('\n')
3219 rst.append('\n')
3220
3220
3221 # check if this command shadows a non-trivial (multi-line)
3221 # check if this command shadows a non-trivial (multi-line)
3222 # extension help text
3222 # extension help text
3223 try:
3223 try:
3224 mod = extensions.find(name)
3224 mod = extensions.find(name)
3225 doc = gettext(mod.__doc__) or ''
3225 doc = gettext(mod.__doc__) or ''
3226 if '\n' in doc.strip():
3226 if '\n' in doc.strip():
3227 msg = _('use "hg help -e %s" to show help for '
3227 msg = _('use "hg help -e %s" to show help for '
3228 'the %s extension') % (name, name)
3228 'the %s extension') % (name, name)
3229 rst.append('\n%s\n' % msg)
3229 rst.append('\n%s\n' % msg)
3230 except KeyError:
3230 except KeyError:
3231 pass
3231 pass
3232
3232
3233 # options
3233 # options
3234 if not ui.quiet and entry[1]:
3234 if not ui.quiet and entry[1]:
3235 rst.append('\n%s\n\n' % _("options:"))
3235 rst.append('\n%s\n\n' % _("options:"))
3236 rst.append(help.optrst(entry[1], ui.verbose))
3236 rst.append(help.optrst(entry[1], ui.verbose))
3237
3237
3238 if ui.verbose:
3238 if ui.verbose:
3239 rst.append('\n%s\n\n' % _("global options:"))
3239 rst.append('\n%s\n\n' % _("global options:"))
3240 rst.append(help.optrst(globalopts, ui.verbose))
3240 rst.append(help.optrst(globalopts, ui.verbose))
3241
3241
3242 if not ui.verbose:
3242 if not ui.verbose:
3243 if not full:
3243 if not full:
3244 rst.append(_('\nuse "hg help %s" to show the full help text\n')
3244 rst.append(_('\nuse "hg help %s" to show the full help text\n')
3245 % name)
3245 % name)
3246 elif not ui.quiet:
3246 elif not ui.quiet:
3247 rst.append(_('\nuse "hg -v help %s" to show more info\n')
3247 rst.append(_('\nuse "hg -v help %s" to show more info\n')
3248 % name)
3248 % name)
3249 return rst
3249 return rst
3250
3250
3251
3251
3252 def helplist(select=None):
3252 def helplist(select=None):
3253 # list of commands
3253 # list of commands
3254 if name == "shortlist":
3254 if name == "shortlist":
3255 header = _('basic commands:\n\n')
3255 header = _('basic commands:\n\n')
3256 else:
3256 else:
3257 header = _('list of commands:\n\n')
3257 header = _('list of commands:\n\n')
3258
3258
3259 h = {}
3259 h = {}
3260 cmds = {}
3260 cmds = {}
3261 for c, e in table.iteritems():
3261 for c, e in table.iteritems():
3262 f = c.split("|", 1)[0]
3262 f = c.split("|", 1)[0]
3263 if select and not select(f):
3263 if select and not select(f):
3264 continue
3264 continue
3265 if (not select and name != 'shortlist' and
3265 if (not select and name != 'shortlist' and
3266 e[0].__module__ != __name__):
3266 e[0].__module__ != __name__):
3267 continue
3267 continue
3268 if name == "shortlist" and not f.startswith("^"):
3268 if name == "shortlist" and not f.startswith("^"):
3269 continue
3269 continue
3270 f = f.lstrip("^")
3270 f = f.lstrip("^")
3271 if not ui.debugflag and f.startswith("debug"):
3271 if not ui.debugflag and f.startswith("debug"):
3272 continue
3272 continue
3273 doc = e[0].__doc__
3273 doc = e[0].__doc__
3274 if doc and 'DEPRECATED' in doc and not ui.verbose:
3274 if doc and 'DEPRECATED' in doc and not ui.verbose:
3275 continue
3275 continue
3276 doc = gettext(doc)
3276 doc = gettext(doc)
3277 if not doc:
3277 if not doc:
3278 doc = _("(no help text available)")
3278 doc = _("(no help text available)")
3279 h[f] = doc.splitlines()[0].rstrip()
3279 h[f] = doc.splitlines()[0].rstrip()
3280 cmds[f] = c.lstrip("^")
3280 cmds[f] = c.lstrip("^")
3281
3281
3282 rst = []
3282 rst = []
3283 if not h:
3283 if not h:
3284 if not ui.quiet:
3284 if not ui.quiet:
3285 rst.append(_('no commands defined\n'))
3285 rst.append(_('no commands defined\n'))
3286 return rst
3286 return rst
3287
3287
3288 if not ui.quiet:
3288 if not ui.quiet:
3289 rst.append(header)
3289 rst.append(header)
3290 fns = sorted(h)
3290 fns = sorted(h)
3291 for f in fns:
3291 for f in fns:
3292 if ui.verbose:
3292 if ui.verbose:
3293 commands = cmds[f].replace("|",", ")
3293 commands = cmds[f].replace("|",", ")
3294 rst.append(" :%s: %s\n" % (commands, h[f]))
3294 rst.append(" :%s: %s\n" % (commands, h[f]))
3295 else:
3295 else:
3296 rst.append(' :%s: %s\n' % (f, h[f]))
3296 rst.append(' :%s: %s\n' % (f, h[f]))
3297
3297
3298 if not name:
3298 if not name:
3299 exts = help.listexts(_('enabled extensions:'), extensions.enabled())
3299 exts = help.listexts(_('enabled extensions:'), extensions.enabled())
3300 if exts:
3300 if exts:
3301 rst.append('\n')
3301 rst.append('\n')
3302 rst.extend(exts)
3302 rst.extend(exts)
3303
3303
3304 rst.append(_("\nadditional help topics:\n\n"))
3304 rst.append(_("\nadditional help topics:\n\n"))
3305 topics = []
3305 topics = []
3306 for names, header, doc in help.helptable:
3306 for names, header, doc in help.helptable:
3307 topics.append((names[0], header))
3307 topics.append((names[0], header))
3308 for t, desc in topics:
3308 for t, desc in topics:
3309 rst.append(" :%s: %s\n" % (t, desc))
3309 rst.append(" :%s: %s\n" % (t, desc))
3310
3310
3311 optlist = []
3311 optlist = []
3312 if not ui.quiet:
3312 if not ui.quiet:
3313 if ui.verbose:
3313 if ui.verbose:
3314 optlist.append((_("global options:"), globalopts))
3314 optlist.append((_("global options:"), globalopts))
3315 if name == 'shortlist':
3315 if name == 'shortlist':
3316 optlist.append((_('use "hg help" for the full list '
3316 optlist.append((_('use "hg help" for the full list '
3317 'of commands'), ()))
3317 'of commands'), ()))
3318 else:
3318 else:
3319 if name == 'shortlist':
3319 if name == 'shortlist':
3320 msg = _('use "hg help" for the full list of commands '
3320 msg = _('use "hg help" for the full list of commands '
3321 'or "hg -v" for details')
3321 'or "hg -v" for details')
3322 elif name and not full:
3322 elif name and not full:
3323 msg = _('use "hg help %s" to show the full help '
3323 msg = _('use "hg help %s" to show the full help '
3324 'text') % name
3324 'text') % name
3325 else:
3325 else:
3326 msg = _('use "hg -v help%s" to show builtin aliases and '
3326 msg = _('use "hg -v help%s" to show builtin aliases and '
3327 'global options') % (name and " " + name or "")
3327 'global options') % (name and " " + name or "")
3328 optlist.append((msg, ()))
3328 optlist.append((msg, ()))
3329
3329
3330 if optlist:
3330 if optlist:
3331 for title, options in optlist:
3331 for title, options in optlist:
3332 rst.append('\n%s\n' % title)
3332 rst.append('\n%s\n' % title)
3333 if options:
3333 if options:
3334 rst.append('\n%s\n' % help.optrst(options, ui.verbose))
3334 rst.append('\n%s\n' % help.optrst(options, ui.verbose))
3335 return rst
3335 return rst
3336
3336
3337 def helptopic(name):
3337 def helptopic(name):
3338 for names, header, doc in help.helptable:
3338 for names, header, doc in help.helptable:
3339 if name in names:
3339 if name in names:
3340 break
3340 break
3341 else:
3341 else:
3342 raise error.UnknownCommand(name)
3342 raise error.UnknownCommand(name)
3343
3343
3344 rst = ["%s\n\n" % header]
3344 rst = ["%s\n\n" % header]
3345 # description
3345 # description
3346 if not doc:
3346 if not doc:
3347 rst.append(" %s\n" % _("(no help text available)"))
3347 rst.append(" %s\n" % _("(no help text available)"))
3348 if util.safehasattr(doc, '__call__'):
3348 if util.safehasattr(doc, '__call__'):
3349 rst += [" %s\n" % l for l in doc().splitlines()]
3349 rst += [" %s\n" % l for l in doc().splitlines()]
3350
3350
3351 try:
3351 try:
3352 cmdutil.findcmd(name, table)
3352 cmdutil.findcmd(name, table)
3353 rst.append(_('\nuse "hg help -c %s" to see help for '
3353 rst.append(_('\nuse "hg help -c %s" to see help for '
3354 'the %s command\n') % (name, name))
3354 'the %s command\n') % (name, name))
3355 except error.UnknownCommand:
3355 except error.UnknownCommand:
3356 pass
3356 pass
3357 return rst
3357 return rst
3358
3358
3359 def helpext(name):
3359 def helpext(name):
3360 try:
3360 try:
3361 mod = extensions.find(name)
3361 mod = extensions.find(name)
3362 doc = gettext(mod.__doc__) or _('no help text available')
3362 doc = gettext(mod.__doc__) or _('no help text available')
3363 except KeyError:
3363 except KeyError:
3364 mod = None
3364 mod = None
3365 doc = extensions.disabledext(name)
3365 doc = extensions.disabledext(name)
3366 if not doc:
3366 if not doc:
3367 raise error.UnknownCommand(name)
3367 raise error.UnknownCommand(name)
3368
3368
3369 if '\n' not in doc:
3369 if '\n' not in doc:
3370 head, tail = doc, ""
3370 head, tail = doc, ""
3371 else:
3371 else:
3372 head, tail = doc.split('\n', 1)
3372 head, tail = doc.split('\n', 1)
3373 rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
3373 rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
3374 if tail:
3374 if tail:
3375 rst.extend(tail.splitlines(True))
3375 rst.extend(tail.splitlines(True))
3376 rst.append('\n')
3376 rst.append('\n')
3377
3377
3378 if mod:
3378 if mod:
3379 try:
3379 try:
3380 ct = mod.cmdtable
3380 ct = mod.cmdtable
3381 except AttributeError:
3381 except AttributeError:
3382 ct = {}
3382 ct = {}
3383 modcmds = set([c.split('|', 1)[0] for c in ct])
3383 modcmds = set([c.split('|', 1)[0] for c in ct])
3384 rst.extend(helplist(modcmds.__contains__))
3384 rst.extend(helplist(modcmds.__contains__))
3385 else:
3385 else:
3386 rst.append(_('use "hg help extensions" for information on enabling '
3386 rst.append(_('use "hg help extensions" for information on enabling '
3387 'extensions\n'))
3387 'extensions\n'))
3388 return rst
3388 return rst
3389
3389
3390 def helpextcmd(name):
3390 def helpextcmd(name):
3391 cmd, ext, mod = extensions.disabledcmd(ui, name,
3391 cmd, ext, mod = extensions.disabledcmd(ui, name,
3392 ui.configbool('ui', 'strict'))
3392 ui.configbool('ui', 'strict'))
3393 doc = gettext(mod.__doc__).splitlines()[0]
3393 doc = gettext(mod.__doc__).splitlines()[0]
3394
3394
3395 rst = help.listexts(_("'%s' is provided by the following "
3395 rst = help.listexts(_("'%s' is provided by the following "
3396 "extension:") % cmd, {ext: doc}, indent=4)
3396 "extension:") % cmd, {ext: doc}, indent=4)
3397 rst.append('\n')
3397 rst.append('\n')
3398 rst.append(_('use "hg help extensions" for information on enabling '
3398 rst.append(_('use "hg help extensions" for information on enabling '
3399 'extensions\n'))
3399 'extensions\n'))
3400 return rst
3400 return rst
3401
3401
3402
3402
3403 rst = []
3403 rst = []
3404 kw = opts.get('keyword')
3404 kw = opts.get('keyword')
3405 if kw:
3405 if kw:
3406 matches = help.topicmatch(kw)
3406 matches = help.topicmatch(kw)
3407 for t, title in (('topics', _('Topics')),
3407 for t, title in (('topics', _('Topics')),
3408 ('commands', _('Commands')),
3408 ('commands', _('Commands')),
3409 ('extensions', _('Extensions')),
3409 ('extensions', _('Extensions')),
3410 ('extensioncommands', _('Extension Commands'))):
3410 ('extensioncommands', _('Extension Commands'))):
3411 if matches[t]:
3411 if matches[t]:
3412 rst.append('%s:\n\n' % title)
3412 rst.append('%s:\n\n' % title)
3413 rst.extend(minirst.maketable(sorted(matches[t]), 1))
3413 rst.extend(minirst.maketable(sorted(matches[t]), 1))
3414 rst.append('\n')
3414 rst.append('\n')
3415 elif name and name != 'shortlist':
3415 elif name and name != 'shortlist':
3416 i = None
3416 i = None
3417 if unknowncmd:
3417 if unknowncmd:
3418 queries = (helpextcmd,)
3418 queries = (helpextcmd,)
3419 elif opts.get('extension'):
3419 elif opts.get('extension'):
3420 queries = (helpext,)
3420 queries = (helpext,)
3421 elif opts.get('command'):
3421 elif opts.get('command'):
3422 queries = (helpcmd,)
3422 queries = (helpcmd,)
3423 else:
3423 else:
3424 queries = (helptopic, helpcmd, helpext, helpextcmd)
3424 queries = (helptopic, helpcmd, helpext, helpextcmd)
3425 for f in queries:
3425 for f in queries:
3426 try:
3426 try:
3427 rst = f(name)
3427 rst = f(name)
3428 i = None
3428 i = None
3429 break
3429 break
3430 except error.UnknownCommand, inst:
3430 except error.UnknownCommand, inst:
3431 i = inst
3431 i = inst
3432 if i:
3432 if i:
3433 raise i
3433 raise i
3434 else:
3434 else:
3435 # program name
3435 # program name
3436 if not ui.quiet:
3436 if not ui.quiet:
3437 rst = [_("Mercurial Distributed SCM\n"), '\n']
3437 rst = [_("Mercurial Distributed SCM\n"), '\n']
3438 rst.extend(helplist())
3438 rst.extend(helplist())
3439
3439
3440 keep = ui.verbose and ['verbose'] or []
3440 keep = ui.verbose and ['verbose'] or []
3441 formatted, pruned = minirst.format(''.join(rst), textwidth, keep=keep)
3441 formatted, pruned = minirst.format(''.join(rst), textwidth, keep=keep)
3442 ui.write(formatted)
3442 ui.write(formatted)
3443
3443
3444
3444
3445 @command('identify|id',
3445 @command('identify|id',
3446 [('r', 'rev', '',
3446 [('r', 'rev', '',
3447 _('identify the specified revision'), _('REV')),
3447 _('identify the specified revision'), _('REV')),
3448 ('n', 'num', None, _('show local revision number')),
3448 ('n', 'num', None, _('show local revision number')),
3449 ('i', 'id', None, _('show global revision id')),
3449 ('i', 'id', None, _('show global revision id')),
3450 ('b', 'branch', None, _('show branch')),
3450 ('b', 'branch', None, _('show branch')),
3451 ('t', 'tags', None, _('show tags')),
3451 ('t', 'tags', None, _('show tags')),
3452 ('B', 'bookmarks', None, _('show bookmarks')),
3452 ('B', 'bookmarks', None, _('show bookmarks')),
3453 ] + remoteopts,
3453 ] + remoteopts,
3454 _('[-nibtB] [-r REV] [SOURCE]'))
3454 _('[-nibtB] [-r REV] [SOURCE]'))
3455 def identify(ui, repo, source=None, rev=None,
3455 def identify(ui, repo, source=None, rev=None,
3456 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3456 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3457 """identify the working copy or specified revision
3457 """identify the working copy or specified revision
3458
3458
3459 Print a summary identifying the repository state at REV using one or
3459 Print a summary identifying the repository state at REV using one or
3460 two parent hash identifiers, followed by a "+" if the working
3460 two parent hash identifiers, followed by a "+" if the working
3461 directory has uncommitted changes, the branch name (if not default),
3461 directory has uncommitted changes, the branch name (if not default),
3462 a list of tags, and a list of bookmarks.
3462 a list of tags, and a list of bookmarks.
3463
3463
3464 When REV is not given, print a summary of the current state of the
3464 When REV is not given, print a summary of the current state of the
3465 repository.
3465 repository.
3466
3466
3467 Specifying a path to a repository root or Mercurial bundle will
3467 Specifying a path to a repository root or Mercurial bundle will
3468 cause lookup to operate on that repository/bundle.
3468 cause lookup to operate on that repository/bundle.
3469
3469
3470 .. container:: verbose
3470 .. container:: verbose
3471
3471
3472 Examples:
3472 Examples:
3473
3473
3474 - generate a build identifier for the working directory::
3474 - generate a build identifier for the working directory::
3475
3475
3476 hg id --id > build-id.dat
3476 hg id --id > build-id.dat
3477
3477
3478 - find the revision corresponding to a tag::
3478 - find the revision corresponding to a tag::
3479
3479
3480 hg id -n -r 1.3
3480 hg id -n -r 1.3
3481
3481
3482 - check the most recent revision of a remote repository::
3482 - check the most recent revision of a remote repository::
3483
3483
3484 hg id -r tip http://selenic.com/hg/
3484 hg id -r tip http://selenic.com/hg/
3485
3485
3486 Returns 0 if successful.
3486 Returns 0 if successful.
3487 """
3487 """
3488
3488
3489 if not repo and not source:
3489 if not repo and not source:
3490 raise util.Abort(_("there is no Mercurial repository here "
3490 raise util.Abort(_("there is no Mercurial repository here "
3491 "(.hg not found)"))
3491 "(.hg not found)"))
3492
3492
3493 hexfunc = ui.debugflag and hex or short
3493 hexfunc = ui.debugflag and hex or short
3494 default = not (num or id or branch or tags or bookmarks)
3494 default = not (num or id or branch or tags or bookmarks)
3495 output = []
3495 output = []
3496 revs = []
3496 revs = []
3497
3497
3498 if source:
3498 if source:
3499 source, branches = hg.parseurl(ui.expandpath(source))
3499 source, branches = hg.parseurl(ui.expandpath(source))
3500 peer = hg.peer(ui, opts, source)
3500 peer = hg.peer(ui, opts, source)
3501 repo = peer.local()
3501 repo = peer.local()
3502 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3502 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3503
3503
3504 if not repo:
3504 if not repo:
3505 if num or branch or tags:
3505 if num or branch or tags:
3506 raise util.Abort(
3506 raise util.Abort(
3507 _("can't query remote revision number, branch, or tags"))
3507 _("can't query remote revision number, branch, or tags"))
3508 if not rev and revs:
3508 if not rev and revs:
3509 rev = revs[0]
3509 rev = revs[0]
3510 if not rev:
3510 if not rev:
3511 rev = "tip"
3511 rev = "tip"
3512
3512
3513 remoterev = peer.lookup(rev)
3513 remoterev = peer.lookup(rev)
3514 if default or id:
3514 if default or id:
3515 output = [hexfunc(remoterev)]
3515 output = [hexfunc(remoterev)]
3516
3516
3517 def getbms():
3517 def getbms():
3518 bms = []
3518 bms = []
3519
3519
3520 if 'bookmarks' in peer.listkeys('namespaces'):
3520 if 'bookmarks' in peer.listkeys('namespaces'):
3521 hexremoterev = hex(remoterev)
3521 hexremoterev = hex(remoterev)
3522 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3522 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3523 if bmr == hexremoterev]
3523 if bmr == hexremoterev]
3524
3524
3525 return bms
3525 return bms
3526
3526
3527 if bookmarks:
3527 if bookmarks:
3528 output.extend(getbms())
3528 output.extend(getbms())
3529 elif default and not ui.quiet:
3529 elif default and not ui.quiet:
3530 # multiple bookmarks for a single parent separated by '/'
3530 # multiple bookmarks for a single parent separated by '/'
3531 bm = '/'.join(getbms())
3531 bm = '/'.join(getbms())
3532 if bm:
3532 if bm:
3533 output.append(bm)
3533 output.append(bm)
3534 else:
3534 else:
3535 if not rev:
3535 if not rev:
3536 ctx = repo[None]
3536 ctx = repo[None]
3537 parents = ctx.parents()
3537 parents = ctx.parents()
3538 changed = ""
3538 changed = ""
3539 if default or id or num:
3539 if default or id or num:
3540 if (util.any(repo.status())
3540 if (util.any(repo.status())
3541 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3541 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3542 changed = '+'
3542 changed = '+'
3543 if default or id:
3543 if default or id:
3544 output = ["%s%s" %
3544 output = ["%s%s" %
3545 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3545 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3546 if num:
3546 if num:
3547 output.append("%s%s" %
3547 output.append("%s%s" %
3548 ('+'.join([str(p.rev()) for p in parents]), changed))
3548 ('+'.join([str(p.rev()) for p in parents]), changed))
3549 else:
3549 else:
3550 ctx = scmutil.revsingle(repo, rev)
3550 ctx = scmutil.revsingle(repo, rev)
3551 if default or id:
3551 if default or id:
3552 output = [hexfunc(ctx.node())]
3552 output = [hexfunc(ctx.node())]
3553 if num:
3553 if num:
3554 output.append(str(ctx.rev()))
3554 output.append(str(ctx.rev()))
3555
3555
3556 if default and not ui.quiet:
3556 if default and not ui.quiet:
3557 b = ctx.branch()
3557 b = ctx.branch()
3558 if b != 'default':
3558 if b != 'default':
3559 output.append("(%s)" % b)
3559 output.append("(%s)" % b)
3560
3560
3561 # multiple tags for a single parent separated by '/'
3561 # multiple tags for a single parent separated by '/'
3562 t = '/'.join(ctx.tags())
3562 t = '/'.join(ctx.tags())
3563 if t:
3563 if t:
3564 output.append(t)
3564 output.append(t)
3565
3565
3566 # multiple bookmarks for a single parent separated by '/'
3566 # multiple bookmarks for a single parent separated by '/'
3567 bm = '/'.join(ctx.bookmarks())
3567 bm = '/'.join(ctx.bookmarks())
3568 if bm:
3568 if bm:
3569 output.append(bm)
3569 output.append(bm)
3570 else:
3570 else:
3571 if branch:
3571 if branch:
3572 output.append(ctx.branch())
3572 output.append(ctx.branch())
3573
3573
3574 if tags:
3574 if tags:
3575 output.extend(ctx.tags())
3575 output.extend(ctx.tags())
3576
3576
3577 if bookmarks:
3577 if bookmarks:
3578 output.extend(ctx.bookmarks())
3578 output.extend(ctx.bookmarks())
3579
3579
3580 ui.write("%s\n" % ' '.join(output))
3580 ui.write("%s\n" % ' '.join(output))
3581
3581
3582 @command('import|patch',
3582 @command('import|patch',
3583 [('p', 'strip', 1,
3583 [('p', 'strip', 1,
3584 _('directory strip option for patch. This has the same '
3584 _('directory strip option for patch. This has the same '
3585 'meaning as the corresponding patch option'), _('NUM')),
3585 'meaning as the corresponding patch option'), _('NUM')),
3586 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3586 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3587 ('e', 'edit', False, _('invoke editor on commit messages')),
3587 ('e', 'edit', False, _('invoke editor on commit messages')),
3588 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3588 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3589 ('', 'no-commit', None,
3589 ('', 'no-commit', None,
3590 _("don't commit, just update the working directory")),
3590 _("don't commit, just update the working directory")),
3591 ('', 'bypass', None,
3591 ('', 'bypass', None,
3592 _("apply patch without touching the working directory")),
3592 _("apply patch without touching the working directory")),
3593 ('', 'exact', None,
3593 ('', 'exact', None,
3594 _('apply patch to the nodes from which it was generated')),
3594 _('apply patch to the nodes from which it was generated')),
3595 ('', 'import-branch', None,
3595 ('', 'import-branch', None,
3596 _('use any branch information in patch (implied by --exact)'))] +
3596 _('use any branch information in patch (implied by --exact)'))] +
3597 commitopts + commitopts2 + similarityopts,
3597 commitopts + commitopts2 + similarityopts,
3598 _('[OPTION]... PATCH...'))
3598 _('[OPTION]... PATCH...'))
3599 def import_(ui, repo, patch1=None, *patches, **opts):
3599 def import_(ui, repo, patch1=None, *patches, **opts):
3600 """import an ordered set of patches
3600 """import an ordered set of patches
3601
3601
3602 Import a list of patches and commit them individually (unless
3602 Import a list of patches and commit them individually (unless
3603 --no-commit is specified).
3603 --no-commit is specified).
3604
3604
3605 If there are outstanding changes in the working directory, import
3605 If there are outstanding changes in the working directory, import
3606 will abort unless given the -f/--force flag.
3606 will abort unless given the -f/--force flag.
3607
3607
3608 You can import a patch straight from a mail message. Even patches
3608 You can import a patch straight from a mail message. Even patches
3609 as attachments work (to use the body part, it must have type
3609 as attachments work (to use the body part, it must have type
3610 text/plain or text/x-patch). From and Subject headers of email
3610 text/plain or text/x-patch). From and Subject headers of email
3611 message are used as default committer and commit message. All
3611 message are used as default committer and commit message. All
3612 text/plain body parts before first diff are added to commit
3612 text/plain body parts before first diff are added to commit
3613 message.
3613 message.
3614
3614
3615 If the imported patch was generated by :hg:`export`, user and
3615 If the imported patch was generated by :hg:`export`, user and
3616 description from patch override values from message headers and
3616 description from patch override values from message headers and
3617 body. Values given on command line with -m/--message and -u/--user
3617 body. Values given on command line with -m/--message and -u/--user
3618 override these.
3618 override these.
3619
3619
3620 If --exact is specified, import will set the working directory to
3620 If --exact is specified, import will set the working directory to
3621 the parent of each patch before applying it, and will abort if the
3621 the parent of each patch before applying it, and will abort if the
3622 resulting changeset has a different ID than the one recorded in
3622 resulting changeset has a different ID than the one recorded in
3623 the patch. This may happen due to character set problems or other
3623 the patch. This may happen due to character set problems or other
3624 deficiencies in the text patch format.
3624 deficiencies in the text patch format.
3625
3625
3626 Use --bypass to apply and commit patches directly to the
3626 Use --bypass to apply and commit patches directly to the
3627 repository, not touching the working directory. Without --exact,
3627 repository, not touching the working directory. Without --exact,
3628 patches will be applied on top of the working directory parent
3628 patches will be applied on top of the working directory parent
3629 revision.
3629 revision.
3630
3630
3631 With -s/--similarity, hg will attempt to discover renames and
3631 With -s/--similarity, hg will attempt to discover renames and
3632 copies in the patch in the same way as :hg:`addremove`.
3632 copies in the patch in the same way as :hg:`addremove`.
3633
3633
3634 To read a patch from standard input, use "-" as the patch name. If
3634 To read a patch from standard input, use "-" as the patch name. If
3635 a URL is specified, the patch will be downloaded from it.
3635 a URL is specified, the patch will be downloaded from it.
3636 See :hg:`help dates` for a list of formats valid for -d/--date.
3636 See :hg:`help dates` for a list of formats valid for -d/--date.
3637
3637
3638 .. container:: verbose
3638 .. container:: verbose
3639
3639
3640 Examples:
3640 Examples:
3641
3641
3642 - import a traditional patch from a website and detect renames::
3642 - import a traditional patch from a website and detect renames::
3643
3643
3644 hg import -s 80 http://example.com/bugfix.patch
3644 hg import -s 80 http://example.com/bugfix.patch
3645
3645
3646 - import a changeset from an hgweb server::
3646 - import a changeset from an hgweb server::
3647
3647
3648 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3648 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3649
3649
3650 - import all the patches in an Unix-style mbox::
3650 - import all the patches in an Unix-style mbox::
3651
3651
3652 hg import incoming-patches.mbox
3652 hg import incoming-patches.mbox
3653
3653
3654 - attempt to exactly restore an exported changeset (not always
3654 - attempt to exactly restore an exported changeset (not always
3655 possible)::
3655 possible)::
3656
3656
3657 hg import --exact proposed-fix.patch
3657 hg import --exact proposed-fix.patch
3658
3658
3659 Returns 0 on success.
3659 Returns 0 on success.
3660 """
3660 """
3661
3661
3662 if not patch1:
3662 if not patch1:
3663 raise util.Abort(_('need at least one patch to import'))
3663 raise util.Abort(_('need at least one patch to import'))
3664
3664
3665 patches = (patch1,) + patches
3665 patches = (patch1,) + patches
3666
3666
3667 date = opts.get('date')
3667 date = opts.get('date')
3668 if date:
3668 if date:
3669 opts['date'] = util.parsedate(date)
3669 opts['date'] = util.parsedate(date)
3670
3670
3671 editor = cmdutil.commiteditor
3671 editor = cmdutil.commiteditor
3672 if opts.get('edit'):
3672 if opts.get('edit'):
3673 editor = cmdutil.commitforceeditor
3673 editor = cmdutil.commitforceeditor
3674
3674
3675 update = not opts.get('bypass')
3675 update = not opts.get('bypass')
3676 if not update and opts.get('no_commit'):
3676 if not update and opts.get('no_commit'):
3677 raise util.Abort(_('cannot use --no-commit with --bypass'))
3677 raise util.Abort(_('cannot use --no-commit with --bypass'))
3678 try:
3678 try:
3679 sim = float(opts.get('similarity') or 0)
3679 sim = float(opts.get('similarity') or 0)
3680 except ValueError:
3680 except ValueError:
3681 raise util.Abort(_('similarity must be a number'))
3681 raise util.Abort(_('similarity must be a number'))
3682 if sim < 0 or sim > 100:
3682 if sim < 0 or sim > 100:
3683 raise util.Abort(_('similarity must be between 0 and 100'))
3683 raise util.Abort(_('similarity must be between 0 and 100'))
3684 if sim and not update:
3684 if sim and not update:
3685 raise util.Abort(_('cannot use --similarity with --bypass'))
3685 raise util.Abort(_('cannot use --similarity with --bypass'))
3686
3686
3687 if (opts.get('exact') or not opts.get('force')) and update:
3687 if (opts.get('exact') or not opts.get('force')) and update:
3688 cmdutil.bailifchanged(repo)
3688 cmdutil.bailifchanged(repo)
3689
3689
3690 base = opts["base"]
3690 base = opts["base"]
3691 strip = opts["strip"]
3691 strip = opts["strip"]
3692 wlock = lock = tr = None
3692 wlock = lock = tr = None
3693 msgs = []
3693 msgs = []
3694
3694
3695 def checkexact(repo, n, nodeid):
3695 def checkexact(repo, n, nodeid):
3696 if opts.get('exact') and hex(n) != nodeid:
3696 if opts.get('exact') and hex(n) != nodeid:
3697 repo.rollback()
3697 repo.rollback()
3698 raise util.Abort(_('patch is damaged or loses information'))
3698 raise util.Abort(_('patch is damaged or loses information'))
3699
3699
3700 def tryone(ui, hunk, parents):
3700 def tryone(ui, hunk, parents):
3701 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3701 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3702 patch.extract(ui, hunk)
3702 patch.extract(ui, hunk)
3703
3703
3704 if not tmpname:
3704 if not tmpname:
3705 return (None, None)
3705 return (None, None)
3706 msg = _('applied to working directory')
3706 msg = _('applied to working directory')
3707
3707
3708 try:
3708 try:
3709 cmdline_message = cmdutil.logmessage(ui, opts)
3709 cmdline_message = cmdutil.logmessage(ui, opts)
3710 if cmdline_message:
3710 if cmdline_message:
3711 # pickup the cmdline msg
3711 # pickup the cmdline msg
3712 message = cmdline_message
3712 message = cmdline_message
3713 elif message:
3713 elif message:
3714 # pickup the patch msg
3714 # pickup the patch msg
3715 message = message.strip()
3715 message = message.strip()
3716 else:
3716 else:
3717 # launch the editor
3717 # launch the editor
3718 message = None
3718 message = None
3719 ui.debug('message:\n%s\n' % message)
3719 ui.debug('message:\n%s\n' % message)
3720
3720
3721 if len(parents) == 1:
3721 if len(parents) == 1:
3722 parents.append(repo[nullid])
3722 parents.append(repo[nullid])
3723 if opts.get('exact'):
3723 if opts.get('exact'):
3724 if not nodeid or not p1:
3724 if not nodeid or not p1:
3725 raise util.Abort(_('not a Mercurial patch'))
3725 raise util.Abort(_('not a Mercurial patch'))
3726 p1 = repo[p1]
3726 p1 = repo[p1]
3727 p2 = repo[p2 or nullid]
3727 p2 = repo[p2 or nullid]
3728 elif p2:
3728 elif p2:
3729 try:
3729 try:
3730 p1 = repo[p1]
3730 p1 = repo[p1]
3731 p2 = repo[p2]
3731 p2 = repo[p2]
3732 # Without any options, consider p2 only if the
3732 # Without any options, consider p2 only if the
3733 # patch is being applied on top of the recorded
3733 # patch is being applied on top of the recorded
3734 # first parent.
3734 # first parent.
3735 if p1 != parents[0]:
3735 if p1 != parents[0]:
3736 p1 = parents[0]
3736 p1 = parents[0]
3737 p2 = repo[nullid]
3737 p2 = repo[nullid]
3738 except error.RepoError:
3738 except error.RepoError:
3739 p1, p2 = parents
3739 p1, p2 = parents
3740 else:
3740 else:
3741 p1, p2 = parents
3741 p1, p2 = parents
3742
3742
3743 n = None
3743 n = None
3744 if update:
3744 if update:
3745 if p1 != parents[0]:
3745 if p1 != parents[0]:
3746 hg.clean(repo, p1.node())
3746 hg.clean(repo, p1.node())
3747 if p2 != parents[1]:
3747 if p2 != parents[1]:
3748 repo.setparents(p1.node(), p2.node())
3748 repo.setparents(p1.node(), p2.node())
3749
3749
3750 if opts.get('exact') or opts.get('import_branch'):
3750 if opts.get('exact') or opts.get('import_branch'):
3751 repo.dirstate.setbranch(branch or 'default')
3751 repo.dirstate.setbranch(branch or 'default')
3752
3752
3753 files = set()
3753 files = set()
3754 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3754 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3755 eolmode=None, similarity=sim / 100.0)
3755 eolmode=None, similarity=sim / 100.0)
3756 files = list(files)
3756 files = list(files)
3757 if opts.get('no_commit'):
3757 if opts.get('no_commit'):
3758 if message:
3758 if message:
3759 msgs.append(message)
3759 msgs.append(message)
3760 else:
3760 else:
3761 if opts.get('exact') or p2:
3761 if opts.get('exact') or p2:
3762 # If you got here, you either use --force and know what
3762 # If you got here, you either use --force and know what
3763 # you are doing or used --exact or a merge patch while
3763 # you are doing or used --exact or a merge patch while
3764 # being updated to its first parent.
3764 # being updated to its first parent.
3765 m = None
3765 m = None
3766 else:
3766 else:
3767 m = scmutil.matchfiles(repo, files or [])
3767 m = scmutil.matchfiles(repo, files or [])
3768 n = repo.commit(message, opts.get('user') or user,
3768 n = repo.commit(message, opts.get('user') or user,
3769 opts.get('date') or date, match=m,
3769 opts.get('date') or date, match=m,
3770 editor=editor)
3770 editor=editor)
3771 checkexact(repo, n, nodeid)
3771 checkexact(repo, n, nodeid)
3772 else:
3772 else:
3773 if opts.get('exact') or opts.get('import_branch'):
3773 if opts.get('exact') or opts.get('import_branch'):
3774 branch = branch or 'default'
3774 branch = branch or 'default'
3775 else:
3775 else:
3776 branch = p1.branch()
3776 branch = p1.branch()
3777 store = patch.filestore()
3777 store = patch.filestore()
3778 try:
3778 try:
3779 files = set()
3779 files = set()
3780 try:
3780 try:
3781 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3781 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3782 files, eolmode=None)
3782 files, eolmode=None)
3783 except patch.PatchError, e:
3783 except patch.PatchError, e:
3784 raise util.Abort(str(e))
3784 raise util.Abort(str(e))
3785 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3785 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3786 message,
3786 message,
3787 opts.get('user') or user,
3787 opts.get('user') or user,
3788 opts.get('date') or date,
3788 opts.get('date') or date,
3789 branch, files, store,
3789 branch, files, store,
3790 editor=cmdutil.commiteditor)
3790 editor=cmdutil.commiteditor)
3791 repo.savecommitmessage(memctx.description())
3791 repo.savecommitmessage(memctx.description())
3792 n = memctx.commit()
3792 n = memctx.commit()
3793 checkexact(repo, n, nodeid)
3793 checkexact(repo, n, nodeid)
3794 finally:
3794 finally:
3795 store.close()
3795 store.close()
3796 if n:
3796 if n:
3797 # i18n: refers to a short changeset id
3797 # i18n: refers to a short changeset id
3798 msg = _('created %s') % short(n)
3798 msg = _('created %s') % short(n)
3799 return (msg, n)
3799 return (msg, n)
3800 finally:
3800 finally:
3801 os.unlink(tmpname)
3801 os.unlink(tmpname)
3802
3802
3803 try:
3803 try:
3804 try:
3804 try:
3805 wlock = repo.wlock()
3805 wlock = repo.wlock()
3806 if not opts.get('no_commit'):
3806 if not opts.get('no_commit'):
3807 lock = repo.lock()
3807 lock = repo.lock()
3808 tr = repo.transaction('import')
3808 tr = repo.transaction('import')
3809 parents = repo.parents()
3809 parents = repo.parents()
3810 for patchurl in patches:
3810 for patchurl in patches:
3811 if patchurl == '-':
3811 if patchurl == '-':
3812 ui.status(_('applying patch from stdin\n'))
3812 ui.status(_('applying patch from stdin\n'))
3813 patchfile = ui.fin
3813 patchfile = ui.fin
3814 patchurl = 'stdin' # for error message
3814 patchurl = 'stdin' # for error message
3815 else:
3815 else:
3816 patchurl = os.path.join(base, patchurl)
3816 patchurl = os.path.join(base, patchurl)
3817 ui.status(_('applying %s\n') % patchurl)
3817 ui.status(_('applying %s\n') % patchurl)
3818 patchfile = url.open(ui, patchurl)
3818 patchfile = url.open(ui, patchurl)
3819
3819
3820 haspatch = False
3820 haspatch = False
3821 for hunk in patch.split(patchfile):
3821 for hunk in patch.split(patchfile):
3822 (msg, node) = tryone(ui, hunk, parents)
3822 (msg, node) = tryone(ui, hunk, parents)
3823 if msg:
3823 if msg:
3824 haspatch = True
3824 haspatch = True
3825 ui.note(msg + '\n')
3825 ui.note(msg + '\n')
3826 if update or opts.get('exact'):
3826 if update or opts.get('exact'):
3827 parents = repo.parents()
3827 parents = repo.parents()
3828 else:
3828 else:
3829 parents = [repo[node]]
3829 parents = [repo[node]]
3830
3830
3831 if not haspatch:
3831 if not haspatch:
3832 raise util.Abort(_('%s: no diffs found') % patchurl)
3832 raise util.Abort(_('%s: no diffs found') % patchurl)
3833
3833
3834 if tr:
3834 if tr:
3835 tr.close()
3835 tr.close()
3836 if msgs:
3836 if msgs:
3837 repo.savecommitmessage('\n* * *\n'.join(msgs))
3837 repo.savecommitmessage('\n* * *\n'.join(msgs))
3838 except: # re-raises
3838 except: # re-raises
3839 # wlock.release() indirectly calls dirstate.write(): since
3839 # wlock.release() indirectly calls dirstate.write(): since
3840 # we're crashing, we do not want to change the working dir
3840 # we're crashing, we do not want to change the working dir
3841 # parent after all, so make sure it writes nothing
3841 # parent after all, so make sure it writes nothing
3842 repo.dirstate.invalidate()
3842 repo.dirstate.invalidate()
3843 raise
3843 raise
3844 finally:
3844 finally:
3845 if tr:
3845 if tr:
3846 tr.release()
3846 tr.release()
3847 release(lock, wlock)
3847 release(lock, wlock)
3848
3848
3849 @command('incoming|in',
3849 @command('incoming|in',
3850 [('f', 'force', None,
3850 [('f', 'force', None,
3851 _('run even if remote repository is unrelated')),
3851 _('run even if remote repository is unrelated')),
3852 ('n', 'newest-first', None, _('show newest record first')),
3852 ('n', 'newest-first', None, _('show newest record first')),
3853 ('', 'bundle', '',
3853 ('', 'bundle', '',
3854 _('file to store the bundles into'), _('FILE')),
3854 _('file to store the bundles into'), _('FILE')),
3855 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3855 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3856 ('B', 'bookmarks', False, _("compare bookmarks")),
3856 ('B', 'bookmarks', False, _("compare bookmarks")),
3857 ('b', 'branch', [],
3857 ('b', 'branch', [],
3858 _('a specific branch you would like to pull'), _('BRANCH')),
3858 _('a specific branch you would like to pull'), _('BRANCH')),
3859 ] + logopts + remoteopts + subrepoopts,
3859 ] + logopts + remoteopts + subrepoopts,
3860 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3860 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3861 def incoming(ui, repo, source="default", **opts):
3861 def incoming(ui, repo, source="default", **opts):
3862 """show new changesets found in source
3862 """show new changesets found in source
3863
3863
3864 Show new changesets found in the specified path/URL or the default
3864 Show new changesets found in the specified path/URL or the default
3865 pull location. These are the changesets that would have been pulled
3865 pull location. These are the changesets that would have been pulled
3866 if a pull at the time you issued this command.
3866 if a pull at the time you issued this command.
3867
3867
3868 For remote repository, using --bundle avoids downloading the
3868 For remote repository, using --bundle avoids downloading the
3869 changesets twice if the incoming is followed by a pull.
3869 changesets twice if the incoming is followed by a pull.
3870
3870
3871 See pull for valid source format details.
3871 See pull for valid source format details.
3872
3872
3873 Returns 0 if there are incoming changes, 1 otherwise.
3873 Returns 0 if there are incoming changes, 1 otherwise.
3874 """
3874 """
3875 if opts.get('graph'):
3875 if opts.get('graph'):
3876 cmdutil.checkunsupportedgraphflags([], opts)
3876 cmdutil.checkunsupportedgraphflags([], opts)
3877 def display(other, chlist, displayer):
3877 def display(other, chlist, displayer):
3878 revdag = cmdutil.graphrevs(other, chlist, opts)
3878 revdag = cmdutil.graphrevs(other, chlist, opts)
3879 showparents = [ctx.node() for ctx in repo[None].parents()]
3879 showparents = [ctx.node() for ctx in repo[None].parents()]
3880 cmdutil.displaygraph(ui, revdag, displayer, showparents,
3880 cmdutil.displaygraph(ui, revdag, displayer, showparents,
3881 graphmod.asciiedges)
3881 graphmod.asciiedges)
3882
3882
3883 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3883 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3884 return 0
3884 return 0
3885
3885
3886 if opts.get('bundle') and opts.get('subrepos'):
3886 if opts.get('bundle') and opts.get('subrepos'):
3887 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3887 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3888
3888
3889 if opts.get('bookmarks'):
3889 if opts.get('bookmarks'):
3890 source, branches = hg.parseurl(ui.expandpath(source),
3890 source, branches = hg.parseurl(ui.expandpath(source),
3891 opts.get('branch'))
3891 opts.get('branch'))
3892 other = hg.peer(repo, opts, source)
3892 other = hg.peer(repo, opts, source)
3893 if 'bookmarks' not in other.listkeys('namespaces'):
3893 if 'bookmarks' not in other.listkeys('namespaces'):
3894 ui.warn(_("remote doesn't support bookmarks\n"))
3894 ui.warn(_("remote doesn't support bookmarks\n"))
3895 return 0
3895 return 0
3896 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3896 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3897 return bookmarks.diff(ui, repo, other)
3897 return bookmarks.diff(ui, repo, other)
3898
3898
3899 repo._subtoppath = ui.expandpath(source)
3899 repo._subtoppath = ui.expandpath(source)
3900 try:
3900 try:
3901 return hg.incoming(ui, repo, source, opts)
3901 return hg.incoming(ui, repo, source, opts)
3902 finally:
3902 finally:
3903 del repo._subtoppath
3903 del repo._subtoppath
3904
3904
3905
3905
3906 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3906 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3907 def init(ui, dest=".", **opts):
3907 def init(ui, dest=".", **opts):
3908 """create a new repository in the given directory
3908 """create a new repository in the given directory
3909
3909
3910 Initialize a new repository in the given directory. If the given
3910 Initialize a new repository in the given directory. If the given
3911 directory does not exist, it will be created.
3911 directory does not exist, it will be created.
3912
3912
3913 If no directory is given, the current directory is used.
3913 If no directory is given, the current directory is used.
3914
3914
3915 It is possible to specify an ``ssh://`` URL as the destination.
3915 It is possible to specify an ``ssh://`` URL as the destination.
3916 See :hg:`help urls` for more information.
3916 See :hg:`help urls` for more information.
3917
3917
3918 Returns 0 on success.
3918 Returns 0 on success.
3919 """
3919 """
3920 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3920 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3921
3921
3922 @command('locate',
3922 @command('locate',
3923 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3923 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3924 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3924 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3925 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3925 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3926 ] + walkopts,
3926 ] + walkopts,
3927 _('[OPTION]... [PATTERN]...'))
3927 _('[OPTION]... [PATTERN]...'))
3928 def locate(ui, repo, *pats, **opts):
3928 def locate(ui, repo, *pats, **opts):
3929 """locate files matching specific patterns
3929 """locate files matching specific patterns
3930
3930
3931 Print files under Mercurial control in the working directory whose
3931 Print files under Mercurial control in the working directory whose
3932 names match the given patterns.
3932 names match the given patterns.
3933
3933
3934 By default, this command searches all directories in the working
3934 By default, this command searches all directories in the working
3935 directory. To search just the current directory and its
3935 directory. To search just the current directory and its
3936 subdirectories, use "--include .".
3936 subdirectories, use "--include .".
3937
3937
3938 If no patterns are given to match, this command prints the names
3938 If no patterns are given to match, this command prints the names
3939 of all files under Mercurial control in the working directory.
3939 of all files under Mercurial control in the working directory.
3940
3940
3941 If you want to feed the output of this command into the "xargs"
3941 If you want to feed the output of this command into the "xargs"
3942 command, use the -0 option to both this command and "xargs". This
3942 command, use the -0 option to both this command and "xargs". This
3943 will avoid the problem of "xargs" treating single filenames that
3943 will avoid the problem of "xargs" treating single filenames that
3944 contain whitespace as multiple filenames.
3944 contain whitespace as multiple filenames.
3945
3945
3946 Returns 0 if a match is found, 1 otherwise.
3946 Returns 0 if a match is found, 1 otherwise.
3947 """
3947 """
3948 end = opts.get('print0') and '\0' or '\n'
3948 end = opts.get('print0') and '\0' or '\n'
3949 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3949 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3950
3950
3951 ret = 1
3951 ret = 1
3952 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3952 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3953 m.bad = lambda x, y: False
3953 m.bad = lambda x, y: False
3954 for abs in repo[rev].walk(m):
3954 for abs in repo[rev].walk(m):
3955 if not rev and abs not in repo.dirstate:
3955 if not rev and abs not in repo.dirstate:
3956 continue
3956 continue
3957 if opts.get('fullpath'):
3957 if opts.get('fullpath'):
3958 ui.write(repo.wjoin(abs), end)
3958 ui.write(repo.wjoin(abs), end)
3959 else:
3959 else:
3960 ui.write(((pats and m.rel(abs)) or abs), end)
3960 ui.write(((pats and m.rel(abs)) or abs), end)
3961 ret = 0
3961 ret = 0
3962
3962
3963 return ret
3963 return ret
3964
3964
3965 @command('^log|history',
3965 @command('^log|history',
3966 [('f', 'follow', None,
3966 [('f', 'follow', None,
3967 _('follow changeset history, or file history across copies and renames')),
3967 _('follow changeset history, or file history across copies and renames')),
3968 ('', 'follow-first', None,
3968 ('', 'follow-first', None,
3969 _('only follow the first parent of merge changesets (DEPRECATED)')),
3969 _('only follow the first parent of merge changesets (DEPRECATED)')),
3970 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3970 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3971 ('C', 'copies', None, _('show copied files')),
3971 ('C', 'copies', None, _('show copied files')),
3972 ('k', 'keyword', [],
3972 ('k', 'keyword', [],
3973 _('do case-insensitive search for a given text'), _('TEXT')),
3973 _('do case-insensitive search for a given text'), _('TEXT')),
3974 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3974 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3975 ('', 'removed', None, _('include revisions where files were removed')),
3975 ('', 'removed', None, _('include revisions where files were removed')),
3976 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3976 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3977 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3977 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3978 ('', 'only-branch', [],
3978 ('', 'only-branch', [],
3979 _('show only changesets within the given named branch (DEPRECATED)'),
3979 _('show only changesets within the given named branch (DEPRECATED)'),
3980 _('BRANCH')),
3980 _('BRANCH')),
3981 ('b', 'branch', [],
3981 ('b', 'branch', [],
3982 _('show changesets within the given named branch'), _('BRANCH')),
3982 _('show changesets within the given named branch'), _('BRANCH')),
3983 ('P', 'prune', [],
3983 ('P', 'prune', [],
3984 _('do not display revision or any of its ancestors'), _('REV')),
3984 _('do not display revision or any of its ancestors'), _('REV')),
3985 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
3985 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
3986 ] + logopts + walkopts,
3986 ] + logopts + walkopts,
3987 _('[OPTION]... [FILE]'))
3987 _('[OPTION]... [FILE]'))
3988 def log(ui, repo, *pats, **opts):
3988 def log(ui, repo, *pats, **opts):
3989 """show revision history of entire repository or files
3989 """show revision history of entire repository or files
3990
3990
3991 Print the revision history of the specified files or the entire
3991 Print the revision history of the specified files or the entire
3992 project.
3992 project.
3993
3993
3994 If no revision range is specified, the default is ``tip:0`` unless
3994 If no revision range is specified, the default is ``tip:0`` unless
3995 --follow is set, in which case the working directory parent is
3995 --follow is set, in which case the working directory parent is
3996 used as the starting revision.
3996 used as the starting revision.
3997
3997
3998 File history is shown without following rename or copy history of
3998 File history is shown without following rename or copy history of
3999 files. Use -f/--follow with a filename to follow history across
3999 files. Use -f/--follow with a filename to follow history across
4000 renames and copies. --follow without a filename will only show
4000 renames and copies. --follow without a filename will only show
4001 ancestors or descendants of the starting revision.
4001 ancestors or descendants of the starting revision.
4002
4002
4003 By default this command prints revision number and changeset id,
4003 By default this command prints revision number and changeset id,
4004 tags, non-trivial parents, user, date and time, and a summary for
4004 tags, non-trivial parents, user, date and time, and a summary for
4005 each commit. When the -v/--verbose switch is used, the list of
4005 each commit. When the -v/--verbose switch is used, the list of
4006 changed files and full commit message are shown.
4006 changed files and full commit message are shown.
4007
4007
4008 .. note::
4008 .. note::
4009 log -p/--patch may generate unexpected diff output for merge
4009 log -p/--patch may generate unexpected diff output for merge
4010 changesets, as it will only compare the merge changeset against
4010 changesets, as it will only compare the merge changeset against
4011 its first parent. Also, only files different from BOTH parents
4011 its first parent. Also, only files different from BOTH parents
4012 will appear in files:.
4012 will appear in files:.
4013
4013
4014 .. note::
4014 .. note::
4015 for performance reasons, log FILE may omit duplicate changes
4015 for performance reasons, log FILE may omit duplicate changes
4016 made on branches and will not show deletions. To see all
4016 made on branches and will not show deletions. To see all
4017 changes including duplicates and deletions, use the --removed
4017 changes including duplicates and deletions, use the --removed
4018 switch.
4018 switch.
4019
4019
4020 .. container:: verbose
4020 .. container:: verbose
4021
4021
4022 Some examples:
4022 Some examples:
4023
4023
4024 - changesets with full descriptions and file lists::
4024 - changesets with full descriptions and file lists::
4025
4025
4026 hg log -v
4026 hg log -v
4027
4027
4028 - changesets ancestral to the working directory::
4028 - changesets ancestral to the working directory::
4029
4029
4030 hg log -f
4030 hg log -f
4031
4031
4032 - last 10 commits on the current branch::
4032 - last 10 commits on the current branch::
4033
4033
4034 hg log -l 10 -b .
4034 hg log -l 10 -b .
4035
4035
4036 - changesets showing all modifications of a file, including removals::
4036 - changesets showing all modifications of a file, including removals::
4037
4037
4038 hg log --removed file.c
4038 hg log --removed file.c
4039
4039
4040 - all changesets that touch a directory, with diffs, excluding merges::
4040 - all changesets that touch a directory, with diffs, excluding merges::
4041
4041
4042 hg log -Mp lib/
4042 hg log -Mp lib/
4043
4043
4044 - all revision numbers that match a keyword::
4044 - all revision numbers that match a keyword::
4045
4045
4046 hg log -k bug --template "{rev}\\n"
4046 hg log -k bug --template "{rev}\\n"
4047
4047
4048 - check if a given changeset is included is a tagged release::
4048 - check if a given changeset is included is a tagged release::
4049
4049
4050 hg log -r "a21ccf and ancestor(1.9)"
4050 hg log -r "a21ccf and ancestor(1.9)"
4051
4051
4052 - find all changesets by some user in a date range::
4052 - find all changesets by some user in a date range::
4053
4053
4054 hg log -k alice -d "may 2008 to jul 2008"
4054 hg log -k alice -d "may 2008 to jul 2008"
4055
4055
4056 - summary of all changesets after the last tag::
4056 - summary of all changesets after the last tag::
4057
4057
4058 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4058 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4059
4059
4060 See :hg:`help dates` for a list of formats valid for -d/--date.
4060 See :hg:`help dates` for a list of formats valid for -d/--date.
4061
4061
4062 See :hg:`help revisions` and :hg:`help revsets` for more about
4062 See :hg:`help revisions` and :hg:`help revsets` for more about
4063 specifying revisions.
4063 specifying revisions.
4064
4064
4065 See :hg:`help templates` for more about pre-packaged styles and
4065 See :hg:`help templates` for more about pre-packaged styles and
4066 specifying custom templates.
4066 specifying custom templates.
4067
4067
4068 Returns 0 on success.
4068 Returns 0 on success.
4069 """
4069 """
4070 if opts.get('graph'):
4070 if opts.get('graph'):
4071 return cmdutil.graphlog(ui, repo, *pats, **opts)
4071 return cmdutil.graphlog(ui, repo, *pats, **opts)
4072
4072
4073 matchfn = scmutil.match(repo[None], pats, opts)
4073 matchfn = scmutil.match(repo[None], pats, opts)
4074 limit = cmdutil.loglimit(opts)
4074 limit = cmdutil.loglimit(opts)
4075 count = 0
4075 count = 0
4076
4076
4077 getrenamed, endrev = None, None
4077 getrenamed, endrev = None, None
4078 if opts.get('copies'):
4078 if opts.get('copies'):
4079 if opts.get('rev'):
4079 if opts.get('rev'):
4080 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
4080 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
4081 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4081 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4082
4082
4083 df = False
4083 df = False
4084 if opts.get("date"):
4084 if opts.get("date"):
4085 df = util.matchdate(opts["date"])
4085 df = util.matchdate(opts["date"])
4086
4086
4087 branches = opts.get('branch', []) + opts.get('only_branch', [])
4087 branches = opts.get('branch', []) + opts.get('only_branch', [])
4088 opts['branch'] = [repo.lookupbranch(b) for b in branches]
4088 opts['branch'] = [repo.lookupbranch(b) for b in branches]
4089
4089
4090 displayer = cmdutil.show_changeset(ui, repo, opts, True)
4090 displayer = cmdutil.show_changeset(ui, repo, opts, True)
4091 def prep(ctx, fns):
4091 def prep(ctx, fns):
4092 rev = ctx.rev()
4092 rev = ctx.rev()
4093 parents = [p for p in repo.changelog.parentrevs(rev)
4093 parents = [p for p in repo.changelog.parentrevs(rev)
4094 if p != nullrev]
4094 if p != nullrev]
4095 if opts.get('no_merges') and len(parents) == 2:
4095 if opts.get('no_merges') and len(parents) == 2:
4096 return
4096 return
4097 if opts.get('only_merges') and len(parents) != 2:
4097 if opts.get('only_merges') and len(parents) != 2:
4098 return
4098 return
4099 if opts.get('branch') and ctx.branch() not in opts['branch']:
4099 if opts.get('branch') and ctx.branch() not in opts['branch']:
4100 return
4100 return
4101 if not opts.get('hidden') and ctx.hidden():
4101 if not opts.get('hidden') and ctx.hidden():
4102 return
4102 return
4103 if df and not df(ctx.date()[0]):
4103 if df and not df(ctx.date()[0]):
4104 return
4104 return
4105
4105
4106 lower = encoding.lower
4106 lower = encoding.lower
4107 if opts.get('user'):
4107 if opts.get('user'):
4108 luser = lower(ctx.user())
4108 luser = lower(ctx.user())
4109 for k in [lower(x) for x in opts['user']]:
4109 for k in [lower(x) for x in opts['user']]:
4110 if (k in luser):
4110 if (k in luser):
4111 break
4111 break
4112 else:
4112 else:
4113 return
4113 return
4114 if opts.get('keyword'):
4114 if opts.get('keyword'):
4115 luser = lower(ctx.user())
4115 luser = lower(ctx.user())
4116 ldesc = lower(ctx.description())
4116 ldesc = lower(ctx.description())
4117 lfiles = lower(" ".join(ctx.files()))
4117 lfiles = lower(" ".join(ctx.files()))
4118 for k in [lower(x) for x in opts['keyword']]:
4118 for k in [lower(x) for x in opts['keyword']]:
4119 if (k in luser or k in ldesc or k in lfiles):
4119 if (k in luser or k in ldesc or k in lfiles):
4120 break
4120 break
4121 else:
4121 else:
4122 return
4122 return
4123
4123
4124 copies = None
4124 copies = None
4125 if getrenamed is not None and rev:
4125 if getrenamed is not None and rev:
4126 copies = []
4126 copies = []
4127 for fn in ctx.files():
4127 for fn in ctx.files():
4128 rename = getrenamed(fn, rev)
4128 rename = getrenamed(fn, rev)
4129 if rename:
4129 if rename:
4130 copies.append((fn, rename[0]))
4130 copies.append((fn, rename[0]))
4131
4131
4132 revmatchfn = None
4132 revmatchfn = None
4133 if opts.get('patch') or opts.get('stat'):
4133 if opts.get('patch') or opts.get('stat'):
4134 if opts.get('follow') or opts.get('follow_first'):
4134 if opts.get('follow') or opts.get('follow_first'):
4135 # note: this might be wrong when following through merges
4135 # note: this might be wrong when following through merges
4136 revmatchfn = scmutil.match(repo[None], fns, default='path')
4136 revmatchfn = scmutil.match(repo[None], fns, default='path')
4137 else:
4137 else:
4138 revmatchfn = matchfn
4138 revmatchfn = matchfn
4139
4139
4140 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4140 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4141
4141
4142 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4142 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4143 if count == limit:
4143 if count == limit:
4144 break
4144 break
4145 if displayer.flush(ctx.rev()):
4145 if displayer.flush(ctx.rev()):
4146 count += 1
4146 count += 1
4147 displayer.close()
4147 displayer.close()
4148
4148
4149 @command('manifest',
4149 @command('manifest',
4150 [('r', 'rev', '', _('revision to display'), _('REV')),
4150 [('r', 'rev', '', _('revision to display'), _('REV')),
4151 ('', 'all', False, _("list files from all revisions"))],
4151 ('', 'all', False, _("list files from all revisions"))],
4152 _('[-r REV]'))
4152 _('[-r REV]'))
4153 def manifest(ui, repo, node=None, rev=None, **opts):
4153 def manifest(ui, repo, node=None, rev=None, **opts):
4154 """output the current or given revision of the project manifest
4154 """output the current or given revision of the project manifest
4155
4155
4156 Print a list of version controlled files for the given revision.
4156 Print a list of version controlled files for the given revision.
4157 If no revision is given, the first parent of the working directory
4157 If no revision is given, the first parent of the working directory
4158 is used, or the null revision if no revision is checked out.
4158 is used, or the null revision if no revision is checked out.
4159
4159
4160 With -v, print file permissions, symlink and executable bits.
4160 With -v, print file permissions, symlink and executable bits.
4161 With --debug, print file revision hashes.
4161 With --debug, print file revision hashes.
4162
4162
4163 If option --all is specified, the list of all files from all revisions
4163 If option --all is specified, the list of all files from all revisions
4164 is printed. This includes deleted and renamed files.
4164 is printed. This includes deleted and renamed files.
4165
4165
4166 Returns 0 on success.
4166 Returns 0 on success.
4167 """
4167 """
4168 if opts.get('all'):
4168 if opts.get('all'):
4169 if rev or node:
4169 if rev or node:
4170 raise util.Abort(_("can't specify a revision with --all"))
4170 raise util.Abort(_("can't specify a revision with --all"))
4171
4171
4172 res = []
4172 res = []
4173 prefix = "data/"
4173 prefix = "data/"
4174 suffix = ".i"
4174 suffix = ".i"
4175 plen = len(prefix)
4175 plen = len(prefix)
4176 slen = len(suffix)
4176 slen = len(suffix)
4177 lock = repo.lock()
4177 lock = repo.lock()
4178 try:
4178 try:
4179 for fn, b, size in repo.store.datafiles():
4179 for fn, b, size in repo.store.datafiles():
4180 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4180 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4181 res.append(fn[plen:-slen])
4181 res.append(fn[plen:-slen])
4182 finally:
4182 finally:
4183 lock.release()
4183 lock.release()
4184 for f in res:
4184 for f in res:
4185 ui.write("%s\n" % f)
4185 ui.write("%s\n" % f)
4186 return
4186 return
4187
4187
4188 if rev and node:
4188 if rev and node:
4189 raise util.Abort(_("please specify just one revision"))
4189 raise util.Abort(_("please specify just one revision"))
4190
4190
4191 if not node:
4191 if not node:
4192 node = rev
4192 node = rev
4193
4193
4194 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
4194 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
4195 ctx = scmutil.revsingle(repo, node)
4195 ctx = scmutil.revsingle(repo, node)
4196 for f in ctx:
4196 for f in ctx:
4197 if ui.debugflag:
4197 if ui.debugflag:
4198 ui.write("%40s " % hex(ctx.manifest()[f]))
4198 ui.write("%40s " % hex(ctx.manifest()[f]))
4199 if ui.verbose:
4199 if ui.verbose:
4200 ui.write(decor[ctx.flags(f)])
4200 ui.write(decor[ctx.flags(f)])
4201 ui.write("%s\n" % f)
4201 ui.write("%s\n" % f)
4202
4202
4203 @command('^merge',
4203 @command('^merge',
4204 [('f', 'force', None, _('force a merge with outstanding changes')),
4204 [('f', 'force', None, _('force a merge with outstanding changes')),
4205 ('r', 'rev', '', _('revision to merge'), _('REV')),
4205 ('r', 'rev', '', _('revision to merge'), _('REV')),
4206 ('P', 'preview', None,
4206 ('P', 'preview', None,
4207 _('review revisions to merge (no merge is performed)'))
4207 _('review revisions to merge (no merge is performed)'))
4208 ] + mergetoolopts,
4208 ] + mergetoolopts,
4209 _('[-P] [-f] [[-r] REV]'))
4209 _('[-P] [-f] [[-r] REV]'))
4210 def merge(ui, repo, node=None, **opts):
4210 def merge(ui, repo, node=None, **opts):
4211 """merge working directory with another revision
4211 """merge working directory with another revision
4212
4212
4213 The current working directory is updated with all changes made in
4213 The current working directory is updated with all changes made in
4214 the requested revision since the last common predecessor revision.
4214 the requested revision since the last common predecessor revision.
4215
4215
4216 Files that changed between either parent are marked as changed for
4216 Files that changed between either parent are marked as changed for
4217 the next commit and a commit must be performed before any further
4217 the next commit and a commit must be performed before any further
4218 updates to the repository are allowed. The next commit will have
4218 updates to the repository are allowed. The next commit will have
4219 two parents.
4219 two parents.
4220
4220
4221 ``--tool`` can be used to specify the merge tool used for file
4221 ``--tool`` can be used to specify the merge tool used for file
4222 merges. It overrides the HGMERGE environment variable and your
4222 merges. It overrides the HGMERGE environment variable and your
4223 configuration files. See :hg:`help merge-tools` for options.
4223 configuration files. See :hg:`help merge-tools` for options.
4224
4224
4225 If no revision is specified, the working directory's parent is a
4225 If no revision is specified, the working directory's parent is a
4226 head revision, and the current branch contains exactly one other
4226 head revision, and the current branch contains exactly one other
4227 head, the other head is merged with by default. Otherwise, an
4227 head, the other head is merged with by default. Otherwise, an
4228 explicit revision with which to merge with must be provided.
4228 explicit revision with which to merge with must be provided.
4229
4229
4230 :hg:`resolve` must be used to resolve unresolved files.
4230 :hg:`resolve` must be used to resolve unresolved files.
4231
4231
4232 To undo an uncommitted merge, use :hg:`update --clean .` which
4232 To undo an uncommitted merge, use :hg:`update --clean .` which
4233 will check out a clean copy of the original merge parent, losing
4233 will check out a clean copy of the original merge parent, losing
4234 all changes.
4234 all changes.
4235
4235
4236 Returns 0 on success, 1 if there are unresolved files.
4236 Returns 0 on success, 1 if there are unresolved files.
4237 """
4237 """
4238
4238
4239 if opts.get('rev') and node:
4239 if opts.get('rev') and node:
4240 raise util.Abort(_("please specify just one revision"))
4240 raise util.Abort(_("please specify just one revision"))
4241 if not node:
4241 if not node:
4242 node = opts.get('rev')
4242 node = opts.get('rev')
4243
4243
4244 if node:
4244 if node:
4245 node = scmutil.revsingle(repo, node).node()
4245 node = scmutil.revsingle(repo, node).node()
4246
4246
4247 if not node and repo._bookmarkcurrent:
4247 if not node and repo._bookmarkcurrent:
4248 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4248 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4249 curhead = repo[repo._bookmarkcurrent]
4249 curhead = repo[repo._bookmarkcurrent]
4250 if len(bmheads) == 2:
4250 if len(bmheads) == 2:
4251 if curhead == bmheads[0]:
4251 if curhead == bmheads[0]:
4252 node = bmheads[1]
4252 node = bmheads[1]
4253 else:
4253 else:
4254 node = bmheads[0]
4254 node = bmheads[0]
4255 elif len(bmheads) > 2:
4255 elif len(bmheads) > 2:
4256 raise util.Abort(_("multiple matching bookmarks to merge - "
4256 raise util.Abort(_("multiple matching bookmarks to merge - "
4257 "please merge with an explicit rev or bookmark"),
4257 "please merge with an explicit rev or bookmark"),
4258 hint=_("run 'hg heads' to see all heads"))
4258 hint=_("run 'hg heads' to see all heads"))
4259 elif len(bmheads) <= 1:
4259 elif len(bmheads) <= 1:
4260 raise util.Abort(_("no matching bookmark to merge - "
4260 raise util.Abort(_("no matching bookmark to merge - "
4261 "please merge with an explicit rev or bookmark"),
4261 "please merge with an explicit rev or bookmark"),
4262 hint=_("run 'hg heads' to see all heads"))
4262 hint=_("run 'hg heads' to see all heads"))
4263
4263
4264 if not node and not repo._bookmarkcurrent:
4264 if not node and not repo._bookmarkcurrent:
4265 branch = repo[None].branch()
4265 branch = repo[None].branch()
4266 bheads = repo.branchheads(branch)
4266 bheads = repo.branchheads(branch)
4267 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4267 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4268
4268
4269 if len(nbhs) > 2:
4269 if len(nbhs) > 2:
4270 raise util.Abort(_("branch '%s' has %d heads - "
4270 raise util.Abort(_("branch '%s' has %d heads - "
4271 "please merge with an explicit rev")
4271 "please merge with an explicit rev")
4272 % (branch, len(bheads)),
4272 % (branch, len(bheads)),
4273 hint=_("run 'hg heads .' to see heads"))
4273 hint=_("run 'hg heads .' to see heads"))
4274
4274
4275 parent = repo.dirstate.p1()
4275 parent = repo.dirstate.p1()
4276 if len(nbhs) <= 1:
4276 if len(nbhs) <= 1:
4277 if len(bheads) > 1:
4277 if len(bheads) > 1:
4278 raise util.Abort(_("heads are bookmarked - "
4278 raise util.Abort(_("heads are bookmarked - "
4279 "please merge with an explicit rev"),
4279 "please merge with an explicit rev"),
4280 hint=_("run 'hg heads' to see all heads"))
4280 hint=_("run 'hg heads' to see all heads"))
4281 if len(repo.heads()) > 1:
4281 if len(repo.heads()) > 1:
4282 raise util.Abort(_("branch '%s' has one head - "
4282 raise util.Abort(_("branch '%s' has one head - "
4283 "please merge with an explicit rev")
4283 "please merge with an explicit rev")
4284 % branch,
4284 % branch,
4285 hint=_("run 'hg heads' to see all heads"))
4285 hint=_("run 'hg heads' to see all heads"))
4286 msg, hint = _('nothing to merge'), None
4286 msg, hint = _('nothing to merge'), None
4287 if parent != repo.lookup(branch):
4287 if parent != repo.lookup(branch):
4288 hint = _("use 'hg update' instead")
4288 hint = _("use 'hg update' instead")
4289 raise util.Abort(msg, hint=hint)
4289 raise util.Abort(msg, hint=hint)
4290
4290
4291 if parent not in bheads:
4291 if parent not in bheads:
4292 raise util.Abort(_('working directory not at a head revision'),
4292 raise util.Abort(_('working directory not at a head revision'),
4293 hint=_("use 'hg update' or merge with an "
4293 hint=_("use 'hg update' or merge with an "
4294 "explicit revision"))
4294 "explicit revision"))
4295 if parent == nbhs[0]:
4295 if parent == nbhs[0]:
4296 node = nbhs[-1]
4296 node = nbhs[-1]
4297 else:
4297 else:
4298 node = nbhs[0]
4298 node = nbhs[0]
4299
4299
4300 if opts.get('preview'):
4300 if opts.get('preview'):
4301 # find nodes that are ancestors of p2 but not of p1
4301 # find nodes that are ancestors of p2 but not of p1
4302 p1 = repo.lookup('.')
4302 p1 = repo.lookup('.')
4303 p2 = repo.lookup(node)
4303 p2 = repo.lookup(node)
4304 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4304 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4305
4305
4306 displayer = cmdutil.show_changeset(ui, repo, opts)
4306 displayer = cmdutil.show_changeset(ui, repo, opts)
4307 for node in nodes:
4307 for node in nodes:
4308 displayer.show(repo[node])
4308 displayer.show(repo[node])
4309 displayer.close()
4309 displayer.close()
4310 return 0
4310 return 0
4311
4311
4312 try:
4312 try:
4313 # ui.forcemerge is an internal variable, do not document
4313 # ui.forcemerge is an internal variable, do not document
4314 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4314 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4315 return hg.merge(repo, node, force=opts.get('force'))
4315 return hg.merge(repo, node, force=opts.get('force'))
4316 finally:
4316 finally:
4317 ui.setconfig('ui', 'forcemerge', '')
4317 ui.setconfig('ui', 'forcemerge', '')
4318
4318
4319 @command('outgoing|out',
4319 @command('outgoing|out',
4320 [('f', 'force', None, _('run even when the destination is unrelated')),
4320 [('f', 'force', None, _('run even when the destination is unrelated')),
4321 ('r', 'rev', [],
4321 ('r', 'rev', [],
4322 _('a changeset intended to be included in the destination'), _('REV')),
4322 _('a changeset intended to be included in the destination'), _('REV')),
4323 ('n', 'newest-first', None, _('show newest record first')),
4323 ('n', 'newest-first', None, _('show newest record first')),
4324 ('B', 'bookmarks', False, _('compare bookmarks')),
4324 ('B', 'bookmarks', False, _('compare bookmarks')),
4325 ('b', 'branch', [], _('a specific branch you would like to push'),
4325 ('b', 'branch', [], _('a specific branch you would like to push'),
4326 _('BRANCH')),
4326 _('BRANCH')),
4327 ] + logopts + remoteopts + subrepoopts,
4327 ] + logopts + remoteopts + subrepoopts,
4328 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4328 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4329 def outgoing(ui, repo, dest=None, **opts):
4329 def outgoing(ui, repo, dest=None, **opts):
4330 """show changesets not found in the destination
4330 """show changesets not found in the destination
4331
4331
4332 Show changesets not found in the specified destination repository
4332 Show changesets not found in the specified destination repository
4333 or the default push location. These are the changesets that would
4333 or the default push location. These are the changesets that would
4334 be pushed if a push was requested.
4334 be pushed if a push was requested.
4335
4335
4336 See pull for details of valid destination formats.
4336 See pull for details of valid destination formats.
4337
4337
4338 Returns 0 if there are outgoing changes, 1 otherwise.
4338 Returns 0 if there are outgoing changes, 1 otherwise.
4339 """
4339 """
4340 if opts.get('graph'):
4340 if opts.get('graph'):
4341 cmdutil.checkunsupportedgraphflags([], opts)
4341 cmdutil.checkunsupportedgraphflags([], opts)
4342 o = hg._outgoing(ui, repo, dest, opts)
4342 o = hg._outgoing(ui, repo, dest, opts)
4343 if o is None:
4343 if o is None:
4344 return
4344 return
4345
4345
4346 revdag = cmdutil.graphrevs(repo, o, opts)
4346 revdag = cmdutil.graphrevs(repo, o, opts)
4347 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4347 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4348 showparents = [ctx.node() for ctx in repo[None].parents()]
4348 showparents = [ctx.node() for ctx in repo[None].parents()]
4349 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4349 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4350 graphmod.asciiedges)
4350 graphmod.asciiedges)
4351 return 0
4351 return 0
4352
4352
4353 if opts.get('bookmarks'):
4353 if opts.get('bookmarks'):
4354 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4354 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4355 dest, branches = hg.parseurl(dest, opts.get('branch'))
4355 dest, branches = hg.parseurl(dest, opts.get('branch'))
4356 other = hg.peer(repo, opts, dest)
4356 other = hg.peer(repo, opts, dest)
4357 if 'bookmarks' not in other.listkeys('namespaces'):
4357 if 'bookmarks' not in other.listkeys('namespaces'):
4358 ui.warn(_("remote doesn't support bookmarks\n"))
4358 ui.warn(_("remote doesn't support bookmarks\n"))
4359 return 0
4359 return 0
4360 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4360 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4361 return bookmarks.diff(ui, other, repo)
4361 return bookmarks.diff(ui, other, repo)
4362
4362
4363 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4363 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4364 try:
4364 try:
4365 return hg.outgoing(ui, repo, dest, opts)
4365 return hg.outgoing(ui, repo, dest, opts)
4366 finally:
4366 finally:
4367 del repo._subtoppath
4367 del repo._subtoppath
4368
4368
4369 @command('parents',
4369 @command('parents',
4370 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4370 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4371 ] + templateopts,
4371 ] + templateopts,
4372 _('[-r REV] [FILE]'))
4372 _('[-r REV] [FILE]'))
4373 def parents(ui, repo, file_=None, **opts):
4373 def parents(ui, repo, file_=None, **opts):
4374 """show the parents of the working directory or revision
4374 """show the parents of the working directory or revision
4375
4375
4376 Print the working directory's parent revisions. If a revision is
4376 Print the working directory's parent revisions. If a revision is
4377 given via -r/--rev, the parent of that revision will be printed.
4377 given via -r/--rev, the parent of that revision will be printed.
4378 If a file argument is given, the revision in which the file was
4378 If a file argument is given, the revision in which the file was
4379 last changed (before the working directory revision or the
4379 last changed (before the working directory revision or the
4380 argument to --rev if given) is printed.
4380 argument to --rev if given) is printed.
4381
4381
4382 Returns 0 on success.
4382 Returns 0 on success.
4383 """
4383 """
4384
4384
4385 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4385 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4386
4386
4387 if file_:
4387 if file_:
4388 m = scmutil.match(ctx, (file_,), opts)
4388 m = scmutil.match(ctx, (file_,), opts)
4389 if m.anypats() or len(m.files()) != 1:
4389 if m.anypats() or len(m.files()) != 1:
4390 raise util.Abort(_('can only specify an explicit filename'))
4390 raise util.Abort(_('can only specify an explicit filename'))
4391 file_ = m.files()[0]
4391 file_ = m.files()[0]
4392 filenodes = []
4392 filenodes = []
4393 for cp in ctx.parents():
4393 for cp in ctx.parents():
4394 if not cp:
4394 if not cp:
4395 continue
4395 continue
4396 try:
4396 try:
4397 filenodes.append(cp.filenode(file_))
4397 filenodes.append(cp.filenode(file_))
4398 except error.LookupError:
4398 except error.LookupError:
4399 pass
4399 pass
4400 if not filenodes:
4400 if not filenodes:
4401 raise util.Abort(_("'%s' not found in manifest!") % file_)
4401 raise util.Abort(_("'%s' not found in manifest!") % file_)
4402 fl = repo.file(file_)
4402 fl = repo.file(file_)
4403 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4403 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4404 else:
4404 else:
4405 p = [cp.node() for cp in ctx.parents()]
4405 p = [cp.node() for cp in ctx.parents()]
4406
4406
4407 displayer = cmdutil.show_changeset(ui, repo, opts)
4407 displayer = cmdutil.show_changeset(ui, repo, opts)
4408 for n in p:
4408 for n in p:
4409 if n != nullid:
4409 if n != nullid:
4410 displayer.show(repo[n])
4410 displayer.show(repo[n])
4411 displayer.close()
4411 displayer.close()
4412
4412
4413 @command('paths', [], _('[NAME]'))
4413 @command('paths', [], _('[NAME]'))
4414 def paths(ui, repo, search=None):
4414 def paths(ui, repo, search=None):
4415 """show aliases for remote repositories
4415 """show aliases for remote repositories
4416
4416
4417 Show definition of symbolic path name NAME. If no name is given,
4417 Show definition of symbolic path name NAME. If no name is given,
4418 show definition of all available names.
4418 show definition of all available names.
4419
4419
4420 Option -q/--quiet suppresses all output when searching for NAME
4420 Option -q/--quiet suppresses all output when searching for NAME
4421 and shows only the path names when listing all definitions.
4421 and shows only the path names when listing all definitions.
4422
4422
4423 Path names are defined in the [paths] section of your
4423 Path names are defined in the [paths] section of your
4424 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4424 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4425 repository, ``.hg/hgrc`` is used, too.
4425 repository, ``.hg/hgrc`` is used, too.
4426
4426
4427 The path names ``default`` and ``default-push`` have a special
4427 The path names ``default`` and ``default-push`` have a special
4428 meaning. When performing a push or pull operation, they are used
4428 meaning. When performing a push or pull operation, they are used
4429 as fallbacks if no location is specified on the command-line.
4429 as fallbacks if no location is specified on the command-line.
4430 When ``default-push`` is set, it will be used for push and
4430 When ``default-push`` is set, it will be used for push and
4431 ``default`` will be used for pull; otherwise ``default`` is used
4431 ``default`` will be used for pull; otherwise ``default`` is used
4432 as the fallback for both. When cloning a repository, the clone
4432 as the fallback for both. When cloning a repository, the clone
4433 source is written as ``default`` in ``.hg/hgrc``. Note that
4433 source is written as ``default`` in ``.hg/hgrc``. Note that
4434 ``default`` and ``default-push`` apply to all inbound (e.g.
4434 ``default`` and ``default-push`` apply to all inbound (e.g.
4435 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4435 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4436 :hg:`bundle`) operations.
4436 :hg:`bundle`) operations.
4437
4437
4438 See :hg:`help urls` for more information.
4438 See :hg:`help urls` for more information.
4439
4439
4440 Returns 0 on success.
4440 Returns 0 on success.
4441 """
4441 """
4442 if search:
4442 if search:
4443 for name, path in ui.configitems("paths"):
4443 for name, path in ui.configitems("paths"):
4444 if name == search:
4444 if name == search:
4445 ui.status("%s\n" % util.hidepassword(path))
4445 ui.status("%s\n" % util.hidepassword(path))
4446 return
4446 return
4447 if not ui.quiet:
4447 if not ui.quiet:
4448 ui.warn(_("not found!\n"))
4448 ui.warn(_("not found!\n"))
4449 return 1
4449 return 1
4450 else:
4450 else:
4451 for name, path in ui.configitems("paths"):
4451 for name, path in ui.configitems("paths"):
4452 if ui.quiet:
4452 if ui.quiet:
4453 ui.write("%s\n" % name)
4453 ui.write("%s\n" % name)
4454 else:
4454 else:
4455 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4455 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4456
4456
4457 @command('^phase',
4457 @command('^phase',
4458 [('p', 'public', False, _('set changeset phase to public')),
4458 [('p', 'public', False, _('set changeset phase to public')),
4459 ('d', 'draft', False, _('set changeset phase to draft')),
4459 ('d', 'draft', False, _('set changeset phase to draft')),
4460 ('s', 'secret', False, _('set changeset phase to secret')),
4460 ('s', 'secret', False, _('set changeset phase to secret')),
4461 ('f', 'force', False, _('allow to move boundary backward')),
4461 ('f', 'force', False, _('allow to move boundary backward')),
4462 ('r', 'rev', [], _('target revision'), _('REV')),
4462 ('r', 'rev', [], _('target revision'), _('REV')),
4463 ],
4463 ],
4464 _('[-p|-d|-s] [-f] [-r] REV...'))
4464 _('[-p|-d|-s] [-f] [-r] REV...'))
4465 def phase(ui, repo, *revs, **opts):
4465 def phase(ui, repo, *revs, **opts):
4466 """set or show the current phase name
4466 """set or show the current phase name
4467
4467
4468 With no argument, show the phase name of specified revisions.
4468 With no argument, show the phase name of specified revisions.
4469
4469
4470 With one of -p/--public, -d/--draft or -s/--secret, change the
4470 With one of -p/--public, -d/--draft or -s/--secret, change the
4471 phase value of the specified revisions.
4471 phase value of the specified revisions.
4472
4472
4473 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4473 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4474 lower phase to an higher phase. Phases are ordered as follows::
4474 lower phase to an higher phase. Phases are ordered as follows::
4475
4475
4476 public < draft < secret
4476 public < draft < secret
4477
4477
4478 Return 0 on success, 1 if no phases were changed or some could not
4478 Return 0 on success, 1 if no phases were changed or some could not
4479 be changed.
4479 be changed.
4480 """
4480 """
4481 # search for a unique phase argument
4481 # search for a unique phase argument
4482 targetphase = None
4482 targetphase = None
4483 for idx, name in enumerate(phases.phasenames):
4483 for idx, name in enumerate(phases.phasenames):
4484 if opts[name]:
4484 if opts[name]:
4485 if targetphase is not None:
4485 if targetphase is not None:
4486 raise util.Abort(_('only one phase can be specified'))
4486 raise util.Abort(_('only one phase can be specified'))
4487 targetphase = idx
4487 targetphase = idx
4488
4488
4489 # look for specified revision
4489 # look for specified revision
4490 revs = list(revs)
4490 revs = list(revs)
4491 revs.extend(opts['rev'])
4491 revs.extend(opts['rev'])
4492 if not revs:
4492 if not revs:
4493 raise util.Abort(_('no revisions specified'))
4493 raise util.Abort(_('no revisions specified'))
4494
4494
4495 revs = scmutil.revrange(repo, revs)
4495 revs = scmutil.revrange(repo, revs)
4496
4496
4497 lock = None
4497 lock = None
4498 ret = 0
4498 ret = 0
4499 if targetphase is None:
4499 if targetphase is None:
4500 # display
4500 # display
4501 for r in revs:
4501 for r in revs:
4502 ctx = repo[r]
4502 ctx = repo[r]
4503 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4503 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4504 else:
4504 else:
4505 lock = repo.lock()
4505 lock = repo.lock()
4506 try:
4506 try:
4507 # set phase
4507 # set phase
4508 if not revs:
4508 if not revs:
4509 raise util.Abort(_('empty revision set'))
4509 raise util.Abort(_('empty revision set'))
4510 nodes = [repo[r].node() for r in revs]
4510 nodes = [repo[r].node() for r in revs]
4511 olddata = repo._phasecache.getphaserevs(repo)[:]
4511 olddata = repo._phasecache.getphaserevs(repo)[:]
4512 phases.advanceboundary(repo, targetphase, nodes)
4512 phases.advanceboundary(repo, targetphase, nodes)
4513 if opts['force']:
4513 if opts['force']:
4514 phases.retractboundary(repo, targetphase, nodes)
4514 phases.retractboundary(repo, targetphase, nodes)
4515 finally:
4515 finally:
4516 lock.release()
4516 lock.release()
4517 newdata = repo._phasecache.getphaserevs(repo)
4517 newdata = repo._phasecache.getphaserevs(repo)
4518 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4518 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4519 rejected = [n for n in nodes
4519 rejected = [n for n in nodes
4520 if newdata[repo[n].rev()] < targetphase]
4520 if newdata[repo[n].rev()] < targetphase]
4521 if rejected:
4521 if rejected:
4522 ui.warn(_('cannot move %i changesets to a more permissive '
4522 ui.warn(_('cannot move %i changesets to a more permissive '
4523 'phase, use --force\n') % len(rejected))
4523 'phase, use --force\n') % len(rejected))
4524 ret = 1
4524 ret = 1
4525 if changes:
4525 if changes:
4526 msg = _('phase changed for %i changesets\n') % changes
4526 msg = _('phase changed for %i changesets\n') % changes
4527 if ret:
4527 if ret:
4528 ui.status(msg)
4528 ui.status(msg)
4529 else:
4529 else:
4530 ui.note(msg)
4530 ui.note(msg)
4531 else:
4531 else:
4532 ui.warn(_('no phases changed\n'))
4532 ui.warn(_('no phases changed\n'))
4533 ret = 1
4533 ret = 1
4534 return ret
4534 return ret
4535
4535
4536 def postincoming(ui, repo, modheads, optupdate, checkout):
4536 def postincoming(ui, repo, modheads, optupdate, checkout):
4537 if modheads == 0:
4537 if modheads == 0:
4538 return
4538 return
4539 if optupdate:
4539 if optupdate:
4540 movemarkfrom = repo['.'].node()
4540 movemarkfrom = repo['.'].node()
4541 try:
4541 try:
4542 ret = hg.update(repo, checkout)
4542 ret = hg.update(repo, checkout)
4543 except util.Abort, inst:
4543 except util.Abort, inst:
4544 ui.warn(_("not updating: %s\n") % str(inst))
4544 ui.warn(_("not updating: %s\n") % str(inst))
4545 return 0
4545 return 0
4546 if not ret and not checkout:
4546 if not ret and not checkout:
4547 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4547 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4548 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4548 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4549 return ret
4549 return ret
4550 if modheads > 1:
4550 if modheads > 1:
4551 currentbranchheads = len(repo.branchheads())
4551 currentbranchheads = len(repo.branchheads())
4552 if currentbranchheads == modheads:
4552 if currentbranchheads == modheads:
4553 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4553 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4554 elif currentbranchheads > 1:
4554 elif currentbranchheads > 1:
4555 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4555 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4556 "merge)\n"))
4556 "merge)\n"))
4557 else:
4557 else:
4558 ui.status(_("(run 'hg heads' to see heads)\n"))
4558 ui.status(_("(run 'hg heads' to see heads)\n"))
4559 else:
4559 else:
4560 ui.status(_("(run 'hg update' to get a working copy)\n"))
4560 ui.status(_("(run 'hg update' to get a working copy)\n"))
4561
4561
4562 @command('^pull',
4562 @command('^pull',
4563 [('u', 'update', None,
4563 [('u', 'update', None,
4564 _('update to new branch head if changesets were pulled')),
4564 _('update to new branch head if changesets were pulled')),
4565 ('f', 'force', None, _('run even when remote repository is unrelated')),
4565 ('f', 'force', None, _('run even when remote repository is unrelated')),
4566 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4566 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4567 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4567 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4568 ('b', 'branch', [], _('a specific branch you would like to pull'),
4568 ('b', 'branch', [], _('a specific branch you would like to pull'),
4569 _('BRANCH')),
4569 _('BRANCH')),
4570 ] + remoteopts,
4570 ] + remoteopts,
4571 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4571 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4572 def pull(ui, repo, source="default", **opts):
4572 def pull(ui, repo, source="default", **opts):
4573 """pull changes from the specified source
4573 """pull changes from the specified source
4574
4574
4575 Pull changes from a remote repository to a local one.
4575 Pull changes from a remote repository to a local one.
4576
4576
4577 This finds all changes from the repository at the specified path
4577 This finds all changes from the repository at the specified path
4578 or URL and adds them to a local repository (the current one unless
4578 or URL and adds them to a local repository (the current one unless
4579 -R is specified). By default, this does not update the copy of the
4579 -R is specified). By default, this does not update the copy of the
4580 project in the working directory.
4580 project in the working directory.
4581
4581
4582 Use :hg:`incoming` if you want to see what would have been added
4582 Use :hg:`incoming` if you want to see what would have been added
4583 by a pull at the time you issued this command. If you then decide
4583 by a pull at the time you issued this command. If you then decide
4584 to add those changes to the repository, you should use :hg:`pull
4584 to add those changes to the repository, you should use :hg:`pull
4585 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4585 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4586
4586
4587 If SOURCE is omitted, the 'default' path will be used.
4587 If SOURCE is omitted, the 'default' path will be used.
4588 See :hg:`help urls` for more information.
4588 See :hg:`help urls` for more information.
4589
4589
4590 Returns 0 on success, 1 if an update had unresolved files.
4590 Returns 0 on success, 1 if an update had unresolved files.
4591 """
4591 """
4592 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4592 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4593 other = hg.peer(repo, opts, source)
4593 other = hg.peer(repo, opts, source)
4594 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4594 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4595 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4595 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4596
4596
4597 if opts.get('bookmark'):
4597 if opts.get('bookmark'):
4598 if not revs:
4598 if not revs:
4599 revs = []
4599 revs = []
4600 rb = other.listkeys('bookmarks')
4600 rb = other.listkeys('bookmarks')
4601 for b in opts['bookmark']:
4601 for b in opts['bookmark']:
4602 if b not in rb:
4602 if b not in rb:
4603 raise util.Abort(_('remote bookmark %s not found!') % b)
4603 raise util.Abort(_('remote bookmark %s not found!') % b)
4604 revs.append(rb[b])
4604 revs.append(rb[b])
4605
4605
4606 if revs:
4606 if revs:
4607 try:
4607 try:
4608 revs = [other.lookup(rev) for rev in revs]
4608 revs = [other.lookup(rev) for rev in revs]
4609 except error.CapabilityError:
4609 except error.CapabilityError:
4610 err = _("other repository doesn't support revision lookup, "
4610 err = _("other repository doesn't support revision lookup, "
4611 "so a rev cannot be specified.")
4611 "so a rev cannot be specified.")
4612 raise util.Abort(err)
4612 raise util.Abort(err)
4613
4613
4614 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4614 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4615 bookmarks.updatefromremote(ui, repo, other, source)
4615 bookmarks.updatefromremote(ui, repo, other, source)
4616 if checkout:
4616 if checkout:
4617 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4617 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4618 repo._subtoppath = source
4618 repo._subtoppath = source
4619 try:
4619 try:
4620 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4620 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4621
4621
4622 finally:
4622 finally:
4623 del repo._subtoppath
4623 del repo._subtoppath
4624
4624
4625 # update specified bookmarks
4625 # update specified bookmarks
4626 if opts.get('bookmark'):
4626 if opts.get('bookmark'):
4627 for b in opts['bookmark']:
4627 for b in opts['bookmark']:
4628 # explicit pull overrides local bookmark if any
4628 # explicit pull overrides local bookmark if any
4629 ui.status(_("importing bookmark %s\n") % b)
4629 ui.status(_("importing bookmark %s\n") % b)
4630 repo._bookmarks[b] = repo[rb[b]].node()
4630 repo._bookmarks[b] = repo[rb[b]].node()
4631 bookmarks.write(repo)
4631 bookmarks.write(repo)
4632
4632
4633 return ret
4633 return ret
4634
4634
4635 @command('^push',
4635 @command('^push',
4636 [('f', 'force', None, _('force push')),
4636 [('f', 'force', None, _('force push')),
4637 ('r', 'rev', [],
4637 ('r', 'rev', [],
4638 _('a changeset intended to be included in the destination'),
4638 _('a changeset intended to be included in the destination'),
4639 _('REV')),
4639 _('REV')),
4640 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4640 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4641 ('b', 'branch', [],
4641 ('b', 'branch', [],
4642 _('a specific branch you would like to push'), _('BRANCH')),
4642 _('a specific branch you would like to push'), _('BRANCH')),
4643 ('', 'new-branch', False, _('allow pushing a new branch')),
4643 ('', 'new-branch', False, _('allow pushing a new branch')),
4644 ] + remoteopts,
4644 ] + remoteopts,
4645 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4645 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4646 def push(ui, repo, dest=None, **opts):
4646 def push(ui, repo, dest=None, **opts):
4647 """push changes to the specified destination
4647 """push changes to the specified destination
4648
4648
4649 Push changesets from the local repository to the specified
4649 Push changesets from the local repository to the specified
4650 destination.
4650 destination.
4651
4651
4652 This operation is symmetrical to pull: it is identical to a pull
4652 This operation is symmetrical to pull: it is identical to a pull
4653 in the destination repository from the current one.
4653 in the destination repository from the current one.
4654
4654
4655 By default, push will not allow creation of new heads at the
4655 By default, push will not allow creation of new heads at the
4656 destination, since multiple heads would make it unclear which head
4656 destination, since multiple heads would make it unclear which head
4657 to use. In this situation, it is recommended to pull and merge
4657 to use. In this situation, it is recommended to pull and merge
4658 before pushing.
4658 before pushing.
4659
4659
4660 Use --new-branch if you want to allow push to create a new named
4660 Use --new-branch if you want to allow push to create a new named
4661 branch that is not present at the destination. This allows you to
4661 branch that is not present at the destination. This allows you to
4662 only create a new branch without forcing other changes.
4662 only create a new branch without forcing other changes.
4663
4663
4664 Use -f/--force to override the default behavior and push all
4664 Use -f/--force to override the default behavior and push all
4665 changesets on all branches.
4665 changesets on all branches.
4666
4666
4667 If -r/--rev is used, the specified revision and all its ancestors
4667 If -r/--rev is used, the specified revision and all its ancestors
4668 will be pushed to the remote repository.
4668 will be pushed to the remote repository.
4669
4669
4670 If -B/--bookmark is used, the specified bookmarked revision, its
4670 If -B/--bookmark is used, the specified bookmarked revision, its
4671 ancestors, and the bookmark will be pushed to the remote
4671 ancestors, and the bookmark will be pushed to the remote
4672 repository.
4672 repository.
4673
4673
4674 Please see :hg:`help urls` for important details about ``ssh://``
4674 Please see :hg:`help urls` for important details about ``ssh://``
4675 URLs. If DESTINATION is omitted, a default path will be used.
4675 URLs. If DESTINATION is omitted, a default path will be used.
4676
4676
4677 Returns 0 if push was successful, 1 if nothing to push.
4677 Returns 0 if push was successful, 1 if nothing to push.
4678 """
4678 """
4679
4679
4680 if opts.get('bookmark'):
4680 if opts.get('bookmark'):
4681 for b in opts['bookmark']:
4681 for b in opts['bookmark']:
4682 # translate -B options to -r so changesets get pushed
4682 # translate -B options to -r so changesets get pushed
4683 if b in repo._bookmarks:
4683 if b in repo._bookmarks:
4684 opts.setdefault('rev', []).append(b)
4684 opts.setdefault('rev', []).append(b)
4685 else:
4685 else:
4686 # if we try to push a deleted bookmark, translate it to null
4686 # if we try to push a deleted bookmark, translate it to null
4687 # this lets simultaneous -r, -b options continue working
4687 # this lets simultaneous -r, -b options continue working
4688 opts.setdefault('rev', []).append("null")
4688 opts.setdefault('rev', []).append("null")
4689
4689
4690 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4690 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4691 dest, branches = hg.parseurl(dest, opts.get('branch'))
4691 dest, branches = hg.parseurl(dest, opts.get('branch'))
4692 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4692 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4693 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4693 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4694 other = hg.peer(repo, opts, dest)
4694 other = hg.peer(repo, opts, dest)
4695 if revs:
4695 if revs:
4696 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4696 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4697
4697
4698 repo._subtoppath = dest
4698 repo._subtoppath = dest
4699 try:
4699 try:
4700 # push subrepos depth-first for coherent ordering
4700 # push subrepos depth-first for coherent ordering
4701 c = repo['']
4701 c = repo['']
4702 subs = c.substate # only repos that are committed
4702 subs = c.substate # only repos that are committed
4703 for s in sorted(subs):
4703 for s in sorted(subs):
4704 if c.sub(s).push(opts) == 0:
4704 if c.sub(s).push(opts) == 0:
4705 return False
4705 return False
4706 finally:
4706 finally:
4707 del repo._subtoppath
4707 del repo._subtoppath
4708 result = repo.push(other, opts.get('force'), revs=revs,
4708 result = repo.push(other, opts.get('force'), revs=revs,
4709 newbranch=opts.get('new_branch'))
4709 newbranch=opts.get('new_branch'))
4710
4710
4711 result = not result
4711 result = not result
4712
4712
4713 if opts.get('bookmark'):
4713 if opts.get('bookmark'):
4714 rb = other.listkeys('bookmarks')
4714 rb = other.listkeys('bookmarks')
4715 for b in opts['bookmark']:
4715 for b in opts['bookmark']:
4716 # explicit push overrides remote bookmark if any
4716 # explicit push overrides remote bookmark if any
4717 if b in repo._bookmarks:
4717 if b in repo._bookmarks:
4718 ui.status(_("exporting bookmark %s\n") % b)
4718 ui.status(_("exporting bookmark %s\n") % b)
4719 new = repo[b].hex()
4719 new = repo[b].hex()
4720 elif b in rb:
4720 elif b in rb:
4721 ui.status(_("deleting remote bookmark %s\n") % b)
4721 ui.status(_("deleting remote bookmark %s\n") % b)
4722 new = '' # delete
4722 new = '' # delete
4723 else:
4723 else:
4724 ui.warn(_('bookmark %s does not exist on the local '
4724 ui.warn(_('bookmark %s does not exist on the local '
4725 'or remote repository!\n') % b)
4725 'or remote repository!\n') % b)
4726 return 2
4726 return 2
4727 old = rb.get(b, '')
4727 old = rb.get(b, '')
4728 r = other.pushkey('bookmarks', b, old, new)
4728 r = other.pushkey('bookmarks', b, old, new)
4729 if not r:
4729 if not r:
4730 ui.warn(_('updating bookmark %s failed!\n') % b)
4730 ui.warn(_('updating bookmark %s failed!\n') % b)
4731 if not result:
4731 if not result:
4732 result = 2
4732 result = 2
4733
4733
4734 return result
4734 return result
4735
4735
4736 @command('recover', [])
4736 @command('recover', [])
4737 def recover(ui, repo):
4737 def recover(ui, repo):
4738 """roll back an interrupted transaction
4738 """roll back an interrupted transaction
4739
4739
4740 Recover from an interrupted commit or pull.
4740 Recover from an interrupted commit or pull.
4741
4741
4742 This command tries to fix the repository status after an
4742 This command tries to fix the repository status after an
4743 interrupted operation. It should only be necessary when Mercurial
4743 interrupted operation. It should only be necessary when Mercurial
4744 suggests it.
4744 suggests it.
4745
4745
4746 Returns 0 if successful, 1 if nothing to recover or verify fails.
4746 Returns 0 if successful, 1 if nothing to recover or verify fails.
4747 """
4747 """
4748 if repo.recover():
4748 if repo.recover():
4749 return hg.verify(repo)
4749 return hg.verify(repo)
4750 return 1
4750 return 1
4751
4751
4752 @command('^remove|rm',
4752 @command('^remove|rm',
4753 [('A', 'after', None, _('record delete for missing files')),
4753 [('A', 'after', None, _('record delete for missing files')),
4754 ('f', 'force', None,
4754 ('f', 'force', None,
4755 _('remove (and delete) file even if added or modified')),
4755 _('remove (and delete) file even if added or modified')),
4756 ] + walkopts,
4756 ] + walkopts,
4757 _('[OPTION]... FILE...'))
4757 _('[OPTION]... FILE...'))
4758 def remove(ui, repo, *pats, **opts):
4758 def remove(ui, repo, *pats, **opts):
4759 """remove the specified files on the next commit
4759 """remove the specified files on the next commit
4760
4760
4761 Schedule the indicated files for removal from the current branch.
4761 Schedule the indicated files for removal from the current branch.
4762
4762
4763 This command schedules the files to be removed at the next commit.
4763 This command schedules the files to be removed at the next commit.
4764 To undo a remove before that, see :hg:`revert`. To undo added
4764 To undo a remove before that, see :hg:`revert`. To undo added
4765 files, see :hg:`forget`.
4765 files, see :hg:`forget`.
4766
4766
4767 .. container:: verbose
4767 .. container:: verbose
4768
4768
4769 -A/--after can be used to remove only files that have already
4769 -A/--after can be used to remove only files that have already
4770 been deleted, -f/--force can be used to force deletion, and -Af
4770 been deleted, -f/--force can be used to force deletion, and -Af
4771 can be used to remove files from the next revision without
4771 can be used to remove files from the next revision without
4772 deleting them from the working directory.
4772 deleting them from the working directory.
4773
4773
4774 The following table details the behavior of remove for different
4774 The following table details the behavior of remove for different
4775 file states (columns) and option combinations (rows). The file
4775 file states (columns) and option combinations (rows). The file
4776 states are Added [A], Clean [C], Modified [M] and Missing [!]
4776 states are Added [A], Clean [C], Modified [M] and Missing [!]
4777 (as reported by :hg:`status`). The actions are Warn, Remove
4777 (as reported by :hg:`status`). The actions are Warn, Remove
4778 (from branch) and Delete (from disk):
4778 (from branch) and Delete (from disk):
4779
4779
4780 ======= == == == ==
4780 ======= == == == ==
4781 A C M !
4781 A C M !
4782 ======= == == == ==
4782 ======= == == == ==
4783 none W RD W R
4783 none W RD W R
4784 -f R RD RD R
4784 -f R RD RD R
4785 -A W W W R
4785 -A W W W R
4786 -Af R R R R
4786 -Af R R R R
4787 ======= == == == ==
4787 ======= == == == ==
4788
4788
4789 Note that remove never deletes files in Added [A] state from the
4789 Note that remove never deletes files in Added [A] state from the
4790 working directory, not even if option --force is specified.
4790 working directory, not even if option --force is specified.
4791
4791
4792 Returns 0 on success, 1 if any warnings encountered.
4792 Returns 0 on success, 1 if any warnings encountered.
4793 """
4793 """
4794
4794
4795 ret = 0
4795 ret = 0
4796 after, force = opts.get('after'), opts.get('force')
4796 after, force = opts.get('after'), opts.get('force')
4797 if not pats and not after:
4797 if not pats and not after:
4798 raise util.Abort(_('no files specified'))
4798 raise util.Abort(_('no files specified'))
4799
4799
4800 m = scmutil.match(repo[None], pats, opts)
4800 m = scmutil.match(repo[None], pats, opts)
4801 s = repo.status(match=m, clean=True)
4801 s = repo.status(match=m, clean=True)
4802 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4802 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4803
4803
4804 for f in m.files():
4804 for f in m.files():
4805 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
4805 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
4806 if os.path.exists(m.rel(f)):
4806 if os.path.exists(m.rel(f)):
4807 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4807 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4808 ret = 1
4808 ret = 1
4809
4809
4810 if force:
4810 if force:
4811 list = modified + deleted + clean + added
4811 list = modified + deleted + clean + added
4812 elif after:
4812 elif after:
4813 list = deleted
4813 list = deleted
4814 for f in modified + added + clean:
4814 for f in modified + added + clean:
4815 ui.warn(_('not removing %s: file still exists (use -f'
4815 ui.warn(_('not removing %s: file still exists (use -f'
4816 ' to force removal)\n') % m.rel(f))
4816 ' to force removal)\n') % m.rel(f))
4817 ret = 1
4817 ret = 1
4818 else:
4818 else:
4819 list = deleted + clean
4819 list = deleted + clean
4820 for f in modified:
4820 for f in modified:
4821 ui.warn(_('not removing %s: file is modified (use -f'
4821 ui.warn(_('not removing %s: file is modified (use -f'
4822 ' to force removal)\n') % m.rel(f))
4822 ' to force removal)\n') % m.rel(f))
4823 ret = 1
4823 ret = 1
4824 for f in added:
4824 for f in added:
4825 ui.warn(_('not removing %s: file has been marked for add'
4825 ui.warn(_('not removing %s: file has been marked for add'
4826 ' (use forget to undo)\n') % m.rel(f))
4826 ' (use forget to undo)\n') % m.rel(f))
4827 ret = 1
4827 ret = 1
4828
4828
4829 for f in sorted(list):
4829 for f in sorted(list):
4830 if ui.verbose or not m.exact(f):
4830 if ui.verbose or not m.exact(f):
4831 ui.status(_('removing %s\n') % m.rel(f))
4831 ui.status(_('removing %s\n') % m.rel(f))
4832
4832
4833 wlock = repo.wlock()
4833 wlock = repo.wlock()
4834 try:
4834 try:
4835 if not after:
4835 if not after:
4836 for f in list:
4836 for f in list:
4837 if f in added:
4837 if f in added:
4838 continue # we never unlink added files on remove
4838 continue # we never unlink added files on remove
4839 try:
4839 try:
4840 util.unlinkpath(repo.wjoin(f))
4840 util.unlinkpath(repo.wjoin(f))
4841 except OSError, inst:
4841 except OSError, inst:
4842 if inst.errno != errno.ENOENT:
4842 if inst.errno != errno.ENOENT:
4843 raise
4843 raise
4844 repo[None].forget(list)
4844 repo[None].forget(list)
4845 finally:
4845 finally:
4846 wlock.release()
4846 wlock.release()
4847
4847
4848 return ret
4848 return ret
4849
4849
4850 @command('rename|move|mv',
4850 @command('rename|move|mv',
4851 [('A', 'after', None, _('record a rename that has already occurred')),
4851 [('A', 'after', None, _('record a rename that has already occurred')),
4852 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4852 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4853 ] + walkopts + dryrunopts,
4853 ] + walkopts + dryrunopts,
4854 _('[OPTION]... SOURCE... DEST'))
4854 _('[OPTION]... SOURCE... DEST'))
4855 def rename(ui, repo, *pats, **opts):
4855 def rename(ui, repo, *pats, **opts):
4856 """rename files; equivalent of copy + remove
4856 """rename files; equivalent of copy + remove
4857
4857
4858 Mark dest as copies of sources; mark sources for deletion. If dest
4858 Mark dest as copies of sources; mark sources for deletion. If dest
4859 is a directory, copies are put in that directory. If dest is a
4859 is a directory, copies are put in that directory. If dest is a
4860 file, there can only be one source.
4860 file, there can only be one source.
4861
4861
4862 By default, this command copies the contents of files as they
4862 By default, this command copies the contents of files as they
4863 exist in the working directory. If invoked with -A/--after, the
4863 exist in the working directory. If invoked with -A/--after, the
4864 operation is recorded, but no copying is performed.
4864 operation is recorded, but no copying is performed.
4865
4865
4866 This command takes effect at the next commit. To undo a rename
4866 This command takes effect at the next commit. To undo a rename
4867 before that, see :hg:`revert`.
4867 before that, see :hg:`revert`.
4868
4868
4869 Returns 0 on success, 1 if errors are encountered.
4869 Returns 0 on success, 1 if errors are encountered.
4870 """
4870 """
4871 wlock = repo.wlock(False)
4871 wlock = repo.wlock(False)
4872 try:
4872 try:
4873 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4873 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4874 finally:
4874 finally:
4875 wlock.release()
4875 wlock.release()
4876
4876
4877 @command('resolve',
4877 @command('resolve',
4878 [('a', 'all', None, _('select all unresolved files')),
4878 [('a', 'all', None, _('select all unresolved files')),
4879 ('l', 'list', None, _('list state of files needing merge')),
4879 ('l', 'list', None, _('list state of files needing merge')),
4880 ('m', 'mark', None, _('mark files as resolved')),
4880 ('m', 'mark', None, _('mark files as resolved')),
4881 ('u', 'unmark', None, _('mark files as unresolved')),
4881 ('u', 'unmark', None, _('mark files as unresolved')),
4882 ('n', 'no-status', None, _('hide status prefix'))]
4882 ('n', 'no-status', None, _('hide status prefix'))]
4883 + mergetoolopts + walkopts,
4883 + mergetoolopts + walkopts,
4884 _('[OPTION]... [FILE]...'))
4884 _('[OPTION]... [FILE]...'))
4885 def resolve(ui, repo, *pats, **opts):
4885 def resolve(ui, repo, *pats, **opts):
4886 """redo merges or set/view the merge status of files
4886 """redo merges or set/view the merge status of files
4887
4887
4888 Merges with unresolved conflicts are often the result of
4888 Merges with unresolved conflicts are often the result of
4889 non-interactive merging using the ``internal:merge`` configuration
4889 non-interactive merging using the ``internal:merge`` configuration
4890 setting, or a command-line merge tool like ``diff3``. The resolve
4890 setting, or a command-line merge tool like ``diff3``. The resolve
4891 command is used to manage the files involved in a merge, after
4891 command is used to manage the files involved in a merge, after
4892 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4892 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4893 working directory must have two parents). See :hg:`help
4893 working directory must have two parents). See :hg:`help
4894 merge-tools` for information on configuring merge tools.
4894 merge-tools` for information on configuring merge tools.
4895
4895
4896 The resolve command can be used in the following ways:
4896 The resolve command can be used in the following ways:
4897
4897
4898 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4898 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4899 files, discarding any previous merge attempts. Re-merging is not
4899 files, discarding any previous merge attempts. Re-merging is not
4900 performed for files already marked as resolved. Use ``--all/-a``
4900 performed for files already marked as resolved. Use ``--all/-a``
4901 to select all unresolved files. ``--tool`` can be used to specify
4901 to select all unresolved files. ``--tool`` can be used to specify
4902 the merge tool used for the given files. It overrides the HGMERGE
4902 the merge tool used for the given files. It overrides the HGMERGE
4903 environment variable and your configuration files. Previous file
4903 environment variable and your configuration files. Previous file
4904 contents are saved with a ``.orig`` suffix.
4904 contents are saved with a ``.orig`` suffix.
4905
4905
4906 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4906 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4907 (e.g. after having manually fixed-up the files). The default is
4907 (e.g. after having manually fixed-up the files). The default is
4908 to mark all unresolved files.
4908 to mark all unresolved files.
4909
4909
4910 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4910 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4911 default is to mark all resolved files.
4911 default is to mark all resolved files.
4912
4912
4913 - :hg:`resolve -l`: list files which had or still have conflicts.
4913 - :hg:`resolve -l`: list files which had or still have conflicts.
4914 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4914 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4915
4915
4916 Note that Mercurial will not let you commit files with unresolved
4916 Note that Mercurial will not let you commit files with unresolved
4917 merge conflicts. You must use :hg:`resolve -m ...` before you can
4917 merge conflicts. You must use :hg:`resolve -m ...` before you can
4918 commit after a conflicting merge.
4918 commit after a conflicting merge.
4919
4919
4920 Returns 0 on success, 1 if any files fail a resolve attempt.
4920 Returns 0 on success, 1 if any files fail a resolve attempt.
4921 """
4921 """
4922
4922
4923 all, mark, unmark, show, nostatus = \
4923 all, mark, unmark, show, nostatus = \
4924 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4924 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4925
4925
4926 if (show and (mark or unmark)) or (mark and unmark):
4926 if (show and (mark or unmark)) or (mark and unmark):
4927 raise util.Abort(_("too many options specified"))
4927 raise util.Abort(_("too many options specified"))
4928 if pats and all:
4928 if pats and all:
4929 raise util.Abort(_("can't specify --all and patterns"))
4929 raise util.Abort(_("can't specify --all and patterns"))
4930 if not (all or pats or show or mark or unmark):
4930 if not (all or pats or show or mark or unmark):
4931 raise util.Abort(_('no files or directories specified; '
4931 raise util.Abort(_('no files or directories specified; '
4932 'use --all to remerge all files'))
4932 'use --all to remerge all files'))
4933
4933
4934 ms = mergemod.mergestate(repo)
4934 ms = mergemod.mergestate(repo)
4935 m = scmutil.match(repo[None], pats, opts)
4935 m = scmutil.match(repo[None], pats, opts)
4936 ret = 0
4936 ret = 0
4937
4937
4938 for f in ms:
4938 for f in ms:
4939 if m(f):
4939 if m(f):
4940 if show:
4940 if show:
4941 if nostatus:
4941 if nostatus:
4942 ui.write("%s\n" % f)
4942 ui.write("%s\n" % f)
4943 else:
4943 else:
4944 ui.write("%s %s\n" % (ms[f].upper(), f),
4944 ui.write("%s %s\n" % (ms[f].upper(), f),
4945 label='resolve.' +
4945 label='resolve.' +
4946 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4946 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4947 elif mark:
4947 elif mark:
4948 ms.mark(f, "r")
4948 ms.mark(f, "r")
4949 elif unmark:
4949 elif unmark:
4950 ms.mark(f, "u")
4950 ms.mark(f, "u")
4951 else:
4951 else:
4952 wctx = repo[None]
4952 wctx = repo[None]
4953 mctx = wctx.parents()[-1]
4953 mctx = wctx.parents()[-1]
4954
4954
4955 # backup pre-resolve (merge uses .orig for its own purposes)
4955 # backup pre-resolve (merge uses .orig for its own purposes)
4956 a = repo.wjoin(f)
4956 a = repo.wjoin(f)
4957 util.copyfile(a, a + ".resolve")
4957 util.copyfile(a, a + ".resolve")
4958
4958
4959 try:
4959 try:
4960 # resolve file
4960 # resolve file
4961 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4961 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4962 if ms.resolve(f, wctx, mctx):
4962 if ms.resolve(f, wctx, mctx):
4963 ret = 1
4963 ret = 1
4964 finally:
4964 finally:
4965 ui.setconfig('ui', 'forcemerge', '')
4965 ui.setconfig('ui', 'forcemerge', '')
4966
4966
4967 # replace filemerge's .orig file with our resolve file
4967 # replace filemerge's .orig file with our resolve file
4968 util.rename(a + ".resolve", a + ".orig")
4968 util.rename(a + ".resolve", a + ".orig")
4969
4969
4970 ms.commit()
4970 ms.commit()
4971 return ret
4971 return ret
4972
4972
4973 @command('revert',
4973 @command('revert',
4974 [('a', 'all', None, _('revert all changes when no arguments given')),
4974 [('a', 'all', None, _('revert all changes when no arguments given')),
4975 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4975 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4976 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4976 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4977 ('C', 'no-backup', None, _('do not save backup copies of files')),
4977 ('C', 'no-backup', None, _('do not save backup copies of files')),
4978 ] + walkopts + dryrunopts,
4978 ] + walkopts + dryrunopts,
4979 _('[OPTION]... [-r REV] [NAME]...'))
4979 _('[OPTION]... [-r REV] [NAME]...'))
4980 def revert(ui, repo, *pats, **opts):
4980 def revert(ui, repo, *pats, **opts):
4981 """restore files to their checkout state
4981 """restore files to their checkout state
4982
4982
4983 .. note::
4983 .. note::
4984
4984
4985 To check out earlier revisions, you should use :hg:`update REV`.
4985 To check out earlier revisions, you should use :hg:`update REV`.
4986 To cancel an uncommitted merge (and lose your changes), use
4986 To cancel an uncommitted merge (and lose your changes), use
4987 :hg:`update --clean .`.
4987 :hg:`update --clean .`.
4988
4988
4989 With no revision specified, revert the specified files or directories
4989 With no revision specified, revert the specified files or directories
4990 to the contents they had in the parent of the working directory.
4990 to the contents they had in the parent of the working directory.
4991 This restores the contents of files to an unmodified
4991 This restores the contents of files to an unmodified
4992 state and unschedules adds, removes, copies, and renames. If the
4992 state and unschedules adds, removes, copies, and renames. If the
4993 working directory has two parents, you must explicitly specify a
4993 working directory has two parents, you must explicitly specify a
4994 revision.
4994 revision.
4995
4995
4996 Using the -r/--rev or -d/--date options, revert the given files or
4996 Using the -r/--rev or -d/--date options, revert the given files or
4997 directories to their states as of a specific revision. Because
4997 directories to their states as of a specific revision. Because
4998 revert does not change the working directory parents, this will
4998 revert does not change the working directory parents, this will
4999 cause these files to appear modified. This can be helpful to "back
4999 cause these files to appear modified. This can be helpful to "back
5000 out" some or all of an earlier change. See :hg:`backout` for a
5000 out" some or all of an earlier change. See :hg:`backout` for a
5001 related method.
5001 related method.
5002
5002
5003 Modified files are saved with a .orig suffix before reverting.
5003 Modified files are saved with a .orig suffix before reverting.
5004 To disable these backups, use --no-backup.
5004 To disable these backups, use --no-backup.
5005
5005
5006 See :hg:`help dates` for a list of formats valid for -d/--date.
5006 See :hg:`help dates` for a list of formats valid for -d/--date.
5007
5007
5008 Returns 0 on success.
5008 Returns 0 on success.
5009 """
5009 """
5010
5010
5011 if opts.get("date"):
5011 if opts.get("date"):
5012 if opts.get("rev"):
5012 if opts.get("rev"):
5013 raise util.Abort(_("you can't specify a revision and a date"))
5013 raise util.Abort(_("you can't specify a revision and a date"))
5014 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5014 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5015
5015
5016 parent, p2 = repo.dirstate.parents()
5016 parent, p2 = repo.dirstate.parents()
5017 if not opts.get('rev') and p2 != nullid:
5017 if not opts.get('rev') and p2 != nullid:
5018 # revert after merge is a trap for new users (issue2915)
5018 # revert after merge is a trap for new users (issue2915)
5019 raise util.Abort(_('uncommitted merge with no revision specified'),
5019 raise util.Abort(_('uncommitted merge with no revision specified'),
5020 hint=_('use "hg update" or see "hg help revert"'))
5020 hint=_('use "hg update" or see "hg help revert"'))
5021
5021
5022 ctx = scmutil.revsingle(repo, opts.get('rev'))
5022 ctx = scmutil.revsingle(repo, opts.get('rev'))
5023
5023
5024 if not pats and not opts.get('all'):
5024 if not pats and not opts.get('all'):
5025 msg = _("no files or directories specified")
5025 msg = _("no files or directories specified")
5026 if p2 != nullid:
5026 if p2 != nullid:
5027 hint = _("uncommitted merge, use --all to discard all changes,"
5027 hint = _("uncommitted merge, use --all to discard all changes,"
5028 " or 'hg update -C .' to abort the merge")
5028 " or 'hg update -C .' to abort the merge")
5029 raise util.Abort(msg, hint=hint)
5029 raise util.Abort(msg, hint=hint)
5030 dirty = util.any(repo.status())
5030 dirty = util.any(repo.status())
5031 node = ctx.node()
5031 node = ctx.node()
5032 if node != parent:
5032 if node != parent:
5033 if dirty:
5033 if dirty:
5034 hint = _("uncommitted changes, use --all to discard all"
5034 hint = _("uncommitted changes, use --all to discard all"
5035 " changes, or 'hg update %s' to update") % ctx.rev()
5035 " changes, or 'hg update %s' to update") % ctx.rev()
5036 else:
5036 else:
5037 hint = _("use --all to revert all files,"
5037 hint = _("use --all to revert all files,"
5038 " or 'hg update %s' to update") % ctx.rev()
5038 " or 'hg update %s' to update") % ctx.rev()
5039 elif dirty:
5039 elif dirty:
5040 hint = _("uncommitted changes, use --all to discard all changes")
5040 hint = _("uncommitted changes, use --all to discard all changes")
5041 else:
5041 else:
5042 hint = _("use --all to revert all files")
5042 hint = _("use --all to revert all files")
5043 raise util.Abort(msg, hint=hint)
5043 raise util.Abort(msg, hint=hint)
5044
5044
5045 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5045 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5046
5046
5047 @command('rollback', dryrunopts +
5047 @command('rollback', dryrunopts +
5048 [('f', 'force', False, _('ignore safety measures'))])
5048 [('f', 'force', False, _('ignore safety measures'))])
5049 def rollback(ui, repo, **opts):
5049 def rollback(ui, repo, **opts):
5050 """roll back the last transaction (dangerous)
5050 """roll back the last transaction (dangerous)
5051
5051
5052 This command should be used with care. There is only one level of
5052 This command should be used with care. There is only one level of
5053 rollback, and there is no way to undo a rollback. It will also
5053 rollback, and there is no way to undo a rollback. It will also
5054 restore the dirstate at the time of the last transaction, losing
5054 restore the dirstate at the time of the last transaction, losing
5055 any dirstate changes since that time. This command does not alter
5055 any dirstate changes since that time. This command does not alter
5056 the working directory.
5056 the working directory.
5057
5057
5058 Transactions are used to encapsulate the effects of all commands
5058 Transactions are used to encapsulate the effects of all commands
5059 that create new changesets or propagate existing changesets into a
5059 that create new changesets or propagate existing changesets into a
5060 repository.
5060 repository.
5061
5061
5062 .. container:: verbose
5062 .. container:: verbose
5063
5063
5064 For example, the following commands are transactional, and their
5064 For example, the following commands are transactional, and their
5065 effects can be rolled back:
5065 effects can be rolled back:
5066
5066
5067 - commit
5067 - commit
5068 - import
5068 - import
5069 - pull
5069 - pull
5070 - push (with this repository as the destination)
5070 - push (with this repository as the destination)
5071 - unbundle
5071 - unbundle
5072
5072
5073 To avoid permanent data loss, rollback will refuse to rollback a
5073 To avoid permanent data loss, rollback will refuse to rollback a
5074 commit transaction if it isn't checked out. Use --force to
5074 commit transaction if it isn't checked out. Use --force to
5075 override this protection.
5075 override this protection.
5076
5076
5077 This command is not intended for use on public repositories. Once
5077 This command is not intended for use on public repositories. Once
5078 changes are visible for pull by other users, rolling a transaction
5078 changes are visible for pull by other users, rolling a transaction
5079 back locally is ineffective (someone else may already have pulled
5079 back locally is ineffective (someone else may already have pulled
5080 the changes). Furthermore, a race is possible with readers of the
5080 the changes). Furthermore, a race is possible with readers of the
5081 repository; for example an in-progress pull from the repository
5081 repository; for example an in-progress pull from the repository
5082 may fail if a rollback is performed.
5082 may fail if a rollback is performed.
5083
5083
5084 Returns 0 on success, 1 if no rollback data is available.
5084 Returns 0 on success, 1 if no rollback data is available.
5085 """
5085 """
5086 return repo.rollback(dryrun=opts.get('dry_run'),
5086 return repo.rollback(dryrun=opts.get('dry_run'),
5087 force=opts.get('force'))
5087 force=opts.get('force'))
5088
5088
5089 @command('root', [])
5089 @command('root', [])
5090 def root(ui, repo):
5090 def root(ui, repo):
5091 """print the root (top) of the current working directory
5091 """print the root (top) of the current working directory
5092
5092
5093 Print the root directory of the current repository.
5093 Print the root directory of the current repository.
5094
5094
5095 Returns 0 on success.
5095 Returns 0 on success.
5096 """
5096 """
5097 ui.write(repo.root + "\n")
5097 ui.write(repo.root + "\n")
5098
5098
5099 @command('^serve',
5099 @command('^serve',
5100 [('A', 'accesslog', '', _('name of access log file to write to'),
5100 [('A', 'accesslog', '', _('name of access log file to write to'),
5101 _('FILE')),
5101 _('FILE')),
5102 ('d', 'daemon', None, _('run server in background')),
5102 ('d', 'daemon', None, _('run server in background')),
5103 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
5103 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
5104 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5104 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5105 # use string type, then we can check if something was passed
5105 # use string type, then we can check if something was passed
5106 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5106 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5107 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5107 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5108 _('ADDR')),
5108 _('ADDR')),
5109 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5109 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5110 _('PREFIX')),
5110 _('PREFIX')),
5111 ('n', 'name', '',
5111 ('n', 'name', '',
5112 _('name to show in web pages (default: working directory)'), _('NAME')),
5112 _('name to show in web pages (default: working directory)'), _('NAME')),
5113 ('', 'web-conf', '',
5113 ('', 'web-conf', '',
5114 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5114 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5115 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5115 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5116 _('FILE')),
5116 _('FILE')),
5117 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5117 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5118 ('', 'stdio', None, _('for remote clients')),
5118 ('', 'stdio', None, _('for remote clients')),
5119 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5119 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5120 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5120 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5121 ('', 'style', '', _('template style to use'), _('STYLE')),
5121 ('', 'style', '', _('template style to use'), _('STYLE')),
5122 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5122 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5123 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5123 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5124 _('[OPTION]...'))
5124 _('[OPTION]...'))
5125 def serve(ui, repo, **opts):
5125 def serve(ui, repo, **opts):
5126 """start stand-alone webserver
5126 """start stand-alone webserver
5127
5127
5128 Start a local HTTP repository browser and pull server. You can use
5128 Start a local HTTP repository browser and pull server. You can use
5129 this for ad-hoc sharing and browsing of repositories. It is
5129 this for ad-hoc sharing and browsing of repositories. It is
5130 recommended to use a real web server to serve a repository for
5130 recommended to use a real web server to serve a repository for
5131 longer periods of time.
5131 longer periods of time.
5132
5132
5133 Please note that the server does not implement access control.
5133 Please note that the server does not implement access control.
5134 This means that, by default, anybody can read from the server and
5134 This means that, by default, anybody can read from the server and
5135 nobody can write to it by default. Set the ``web.allow_push``
5135 nobody can write to it by default. Set the ``web.allow_push``
5136 option to ``*`` to allow everybody to push to the server. You
5136 option to ``*`` to allow everybody to push to the server. You
5137 should use a real web server if you need to authenticate users.
5137 should use a real web server if you need to authenticate users.
5138
5138
5139 By default, the server logs accesses to stdout and errors to
5139 By default, the server logs accesses to stdout and errors to
5140 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5140 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5141 files.
5141 files.
5142
5142
5143 To have the server choose a free port number to listen on, specify
5143 To have the server choose a free port number to listen on, specify
5144 a port number of 0; in this case, the server will print the port
5144 a port number of 0; in this case, the server will print the port
5145 number it uses.
5145 number it uses.
5146
5146
5147 Returns 0 on success.
5147 Returns 0 on success.
5148 """
5148 """
5149
5149
5150 if opts["stdio"] and opts["cmdserver"]:
5150 if opts["stdio"] and opts["cmdserver"]:
5151 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5151 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5152
5152
5153 def checkrepo():
5153 def checkrepo():
5154 if repo is None:
5154 if repo is None:
5155 raise error.RepoError(_("there is no Mercurial repository here"
5155 raise error.RepoError(_("there is no Mercurial repository here"
5156 " (.hg not found)"))
5156 " (.hg not found)"))
5157
5157
5158 if opts["stdio"]:
5158 if opts["stdio"]:
5159 checkrepo()
5159 checkrepo()
5160 s = sshserver.sshserver(ui, repo)
5160 s = sshserver.sshserver(ui, repo)
5161 s.serve_forever()
5161 s.serve_forever()
5162
5162
5163 if opts["cmdserver"]:
5163 if opts["cmdserver"]:
5164 checkrepo()
5164 checkrepo()
5165 s = commandserver.server(ui, repo, opts["cmdserver"])
5165 s = commandserver.server(ui, repo, opts["cmdserver"])
5166 return s.serve()
5166 return s.serve()
5167
5167
5168 # this way we can check if something was given in the command-line
5168 # this way we can check if something was given in the command-line
5169 if opts.get('port'):
5169 if opts.get('port'):
5170 opts['port'] = util.getport(opts.get('port'))
5170 opts['port'] = util.getport(opts.get('port'))
5171
5171
5172 baseui = repo and repo.baseui or ui
5172 baseui = repo and repo.baseui or ui
5173 optlist = ("name templates style address port prefix ipv6"
5173 optlist = ("name templates style address port prefix ipv6"
5174 " accesslog errorlog certificate encoding")
5174 " accesslog errorlog certificate encoding")
5175 for o in optlist.split():
5175 for o in optlist.split():
5176 val = opts.get(o, '')
5176 val = opts.get(o, '')
5177 if val in (None, ''): # should check against default options instead
5177 if val in (None, ''): # should check against default options instead
5178 continue
5178 continue
5179 baseui.setconfig("web", o, val)
5179 baseui.setconfig("web", o, val)
5180 if repo and repo.ui != baseui:
5180 if repo and repo.ui != baseui:
5181 repo.ui.setconfig("web", o, val)
5181 repo.ui.setconfig("web", o, val)
5182
5182
5183 o = opts.get('web_conf') or opts.get('webdir_conf')
5183 o = opts.get('web_conf') or opts.get('webdir_conf')
5184 if not o:
5184 if not o:
5185 if not repo:
5185 if not repo:
5186 raise error.RepoError(_("there is no Mercurial repository"
5186 raise error.RepoError(_("there is no Mercurial repository"
5187 " here (.hg not found)"))
5187 " here (.hg not found)"))
5188 o = repo.root
5188 o = repo.root
5189
5189
5190 app = hgweb.hgweb(o, baseui=ui)
5190 app = hgweb.hgweb(o, baseui=ui)
5191
5191
5192 class service(object):
5192 class service(object):
5193 def init(self):
5193 def init(self):
5194 util.setsignalhandler()
5194 util.setsignalhandler()
5195 self.httpd = hgweb.server.create_server(ui, app)
5195 self.httpd = hgweb.server.create_server(ui, app)
5196
5196
5197 if opts['port'] and not ui.verbose:
5197 if opts['port'] and not ui.verbose:
5198 return
5198 return
5199
5199
5200 if self.httpd.prefix:
5200 if self.httpd.prefix:
5201 prefix = self.httpd.prefix.strip('/') + '/'
5201 prefix = self.httpd.prefix.strip('/') + '/'
5202 else:
5202 else:
5203 prefix = ''
5203 prefix = ''
5204
5204
5205 port = ':%d' % self.httpd.port
5205 port = ':%d' % self.httpd.port
5206 if port == ':80':
5206 if port == ':80':
5207 port = ''
5207 port = ''
5208
5208
5209 bindaddr = self.httpd.addr
5209 bindaddr = self.httpd.addr
5210 if bindaddr == '0.0.0.0':
5210 if bindaddr == '0.0.0.0':
5211 bindaddr = '*'
5211 bindaddr = '*'
5212 elif ':' in bindaddr: # IPv6
5212 elif ':' in bindaddr: # IPv6
5213 bindaddr = '[%s]' % bindaddr
5213 bindaddr = '[%s]' % bindaddr
5214
5214
5215 fqaddr = self.httpd.fqaddr
5215 fqaddr = self.httpd.fqaddr
5216 if ':' in fqaddr:
5216 if ':' in fqaddr:
5217 fqaddr = '[%s]' % fqaddr
5217 fqaddr = '[%s]' % fqaddr
5218 if opts['port']:
5218 if opts['port']:
5219 write = ui.status
5219 write = ui.status
5220 else:
5220 else:
5221 write = ui.write
5221 write = ui.write
5222 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5222 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5223 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5223 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5224
5224
5225 def run(self):
5225 def run(self):
5226 self.httpd.serve_forever()
5226 self.httpd.serve_forever()
5227
5227
5228 service = service()
5228 service = service()
5229
5229
5230 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5230 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5231
5231
5232 @command('showconfig|debugconfig',
5232 @command('showconfig|debugconfig',
5233 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5233 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5234 _('[-u] [NAME]...'))
5234 _('[-u] [NAME]...'))
5235 def showconfig(ui, repo, *values, **opts):
5235 def showconfig(ui, repo, *values, **opts):
5236 """show combined config settings from all hgrc files
5236 """show combined config settings from all hgrc files
5237
5237
5238 With no arguments, print names and values of all config items.
5238 With no arguments, print names and values of all config items.
5239
5239
5240 With one argument of the form section.name, print just the value
5240 With one argument of the form section.name, print just the value
5241 of that config item.
5241 of that config item.
5242
5242
5243 With multiple arguments, print names and values of all config
5243 With multiple arguments, print names and values of all config
5244 items with matching section names.
5244 items with matching section names.
5245
5245
5246 With --debug, the source (filename and line number) is printed
5246 With --debug, the source (filename and line number) is printed
5247 for each config item.
5247 for each config item.
5248
5248
5249 Returns 0 on success.
5249 Returns 0 on success.
5250 """
5250 """
5251
5251
5252 for f in scmutil.rcpath():
5252 for f in scmutil.rcpath():
5253 ui.debug('read config from: %s\n' % f)
5253 ui.debug('read config from: %s\n' % f)
5254 untrusted = bool(opts.get('untrusted'))
5254 untrusted = bool(opts.get('untrusted'))
5255 if values:
5255 if values:
5256 sections = [v for v in values if '.' not in v]
5256 sections = [v for v in values if '.' not in v]
5257 items = [v for v in values if '.' in v]
5257 items = [v for v in values if '.' in v]
5258 if len(items) > 1 or items and sections:
5258 if len(items) > 1 or items and sections:
5259 raise util.Abort(_('only one config item permitted'))
5259 raise util.Abort(_('only one config item permitted'))
5260 for section, name, value in ui.walkconfig(untrusted=untrusted):
5260 for section, name, value in ui.walkconfig(untrusted=untrusted):
5261 value = str(value).replace('\n', '\\n')
5261 value = str(value).replace('\n', '\\n')
5262 sectname = section + '.' + name
5262 sectname = section + '.' + name
5263 if values:
5263 if values:
5264 for v in values:
5264 for v in values:
5265 if v == section:
5265 if v == section:
5266 ui.debug('%s: ' %
5266 ui.debug('%s: ' %
5267 ui.configsource(section, name, untrusted))
5267 ui.configsource(section, name, untrusted))
5268 ui.write('%s=%s\n' % (sectname, value))
5268 ui.write('%s=%s\n' % (sectname, value))
5269 elif v == sectname:
5269 elif v == sectname:
5270 ui.debug('%s: ' %
5270 ui.debug('%s: ' %
5271 ui.configsource(section, name, untrusted))
5271 ui.configsource(section, name, untrusted))
5272 ui.write(value, '\n')
5272 ui.write(value, '\n')
5273 else:
5273 else:
5274 ui.debug('%s: ' %
5274 ui.debug('%s: ' %
5275 ui.configsource(section, name, untrusted))
5275 ui.configsource(section, name, untrusted))
5276 ui.write('%s=%s\n' % (sectname, value))
5276 ui.write('%s=%s\n' % (sectname, value))
5277
5277
5278 @command('^status|st',
5278 @command('^status|st',
5279 [('A', 'all', None, _('show status of all files')),
5279 [('A', 'all', None, _('show status of all files')),
5280 ('m', 'modified', None, _('show only modified files')),
5280 ('m', 'modified', None, _('show only modified files')),
5281 ('a', 'added', None, _('show only added files')),
5281 ('a', 'added', None, _('show only added files')),
5282 ('r', 'removed', None, _('show only removed files')),
5282 ('r', 'removed', None, _('show only removed files')),
5283 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5283 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5284 ('c', 'clean', None, _('show only files without changes')),
5284 ('c', 'clean', None, _('show only files without changes')),
5285 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5285 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5286 ('i', 'ignored', None, _('show only ignored files')),
5286 ('i', 'ignored', None, _('show only ignored files')),
5287 ('n', 'no-status', None, _('hide status prefix')),
5287 ('n', 'no-status', None, _('hide status prefix')),
5288 ('C', 'copies', None, _('show source of copied files')),
5288 ('C', 'copies', None, _('show source of copied files')),
5289 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5289 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5290 ('', 'rev', [], _('show difference from revision'), _('REV')),
5290 ('', 'rev', [], _('show difference from revision'), _('REV')),
5291 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5291 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5292 ] + walkopts + subrepoopts,
5292 ] + walkopts + subrepoopts,
5293 _('[OPTION]... [FILE]...'))
5293 _('[OPTION]... [FILE]...'))
5294 def status(ui, repo, *pats, **opts):
5294 def status(ui, repo, *pats, **opts):
5295 """show changed files in the working directory
5295 """show changed files in the working directory
5296
5296
5297 Show status of files in the repository. If names are given, only
5297 Show status of files in the repository. If names are given, only
5298 files that match are shown. Files that are clean or ignored or
5298 files that match are shown. Files that are clean or ignored or
5299 the source of a copy/move operation, are not listed unless
5299 the source of a copy/move operation, are not listed unless
5300 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5300 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5301 Unless options described with "show only ..." are given, the
5301 Unless options described with "show only ..." are given, the
5302 options -mardu are used.
5302 options -mardu are used.
5303
5303
5304 Option -q/--quiet hides untracked (unknown and ignored) files
5304 Option -q/--quiet hides untracked (unknown and ignored) files
5305 unless explicitly requested with -u/--unknown or -i/--ignored.
5305 unless explicitly requested with -u/--unknown or -i/--ignored.
5306
5306
5307 .. note::
5307 .. note::
5308 status may appear to disagree with diff if permissions have
5308 status may appear to disagree with diff if permissions have
5309 changed or a merge has occurred. The standard diff format does
5309 changed or a merge has occurred. The standard diff format does
5310 not report permission changes and diff only reports changes
5310 not report permission changes and diff only reports changes
5311 relative to one merge parent.
5311 relative to one merge parent.
5312
5312
5313 If one revision is given, it is used as the base revision.
5313 If one revision is given, it is used as the base revision.
5314 If two revisions are given, the differences between them are
5314 If two revisions are given, the differences between them are
5315 shown. The --change option can also be used as a shortcut to list
5315 shown. The --change option can also be used as a shortcut to list
5316 the changed files of a revision from its first parent.
5316 the changed files of a revision from its first parent.
5317
5317
5318 The codes used to show the status of files are::
5318 The codes used to show the status of files are::
5319
5319
5320 M = modified
5320 M = modified
5321 A = added
5321 A = added
5322 R = removed
5322 R = removed
5323 C = clean
5323 C = clean
5324 ! = missing (deleted by non-hg command, but still tracked)
5324 ! = missing (deleted by non-hg command, but still tracked)
5325 ? = not tracked
5325 ? = not tracked
5326 I = ignored
5326 I = ignored
5327 = origin of the previous file listed as A (added)
5327 = origin of the previous file listed as A (added)
5328
5328
5329 .. container:: verbose
5329 .. container:: verbose
5330
5330
5331 Examples:
5331 Examples:
5332
5332
5333 - show changes in the working directory relative to a
5333 - show changes in the working directory relative to a
5334 changeset::
5334 changeset::
5335
5335
5336 hg status --rev 9353
5336 hg status --rev 9353
5337
5337
5338 - show all changes including copies in an existing changeset::
5338 - show all changes including copies in an existing changeset::
5339
5339
5340 hg status --copies --change 9353
5340 hg status --copies --change 9353
5341
5341
5342 - get a NUL separated list of added files, suitable for xargs::
5342 - get a NUL separated list of added files, suitable for xargs::
5343
5343
5344 hg status -an0
5344 hg status -an0
5345
5345
5346 Returns 0 on success.
5346 Returns 0 on success.
5347 """
5347 """
5348
5348
5349 revs = opts.get('rev')
5349 revs = opts.get('rev')
5350 change = opts.get('change')
5350 change = opts.get('change')
5351
5351
5352 if revs and change:
5352 if revs and change:
5353 msg = _('cannot specify --rev and --change at the same time')
5353 msg = _('cannot specify --rev and --change at the same time')
5354 raise util.Abort(msg)
5354 raise util.Abort(msg)
5355 elif change:
5355 elif change:
5356 node2 = scmutil.revsingle(repo, change, None).node()
5356 node2 = scmutil.revsingle(repo, change, None).node()
5357 node1 = repo[node2].p1().node()
5357 node1 = repo[node2].p1().node()
5358 else:
5358 else:
5359 node1, node2 = scmutil.revpair(repo, revs)
5359 node1, node2 = scmutil.revpair(repo, revs)
5360
5360
5361 cwd = (pats and repo.getcwd()) or ''
5361 cwd = (pats and repo.getcwd()) or ''
5362 end = opts.get('print0') and '\0' or '\n'
5362 end = opts.get('print0') and '\0' or '\n'
5363 copy = {}
5363 copy = {}
5364 states = 'modified added removed deleted unknown ignored clean'.split()
5364 states = 'modified added removed deleted unknown ignored clean'.split()
5365 show = [k for k in states if opts.get(k)]
5365 show = [k for k in states if opts.get(k)]
5366 if opts.get('all'):
5366 if opts.get('all'):
5367 show += ui.quiet and (states[:4] + ['clean']) or states
5367 show += ui.quiet and (states[:4] + ['clean']) or states
5368 if not show:
5368 if not show:
5369 show = ui.quiet and states[:4] or states[:5]
5369 show = ui.quiet and states[:4] or states[:5]
5370
5370
5371 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5371 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5372 'ignored' in show, 'clean' in show, 'unknown' in show,
5372 'ignored' in show, 'clean' in show, 'unknown' in show,
5373 opts.get('subrepos'))
5373 opts.get('subrepos'))
5374 changestates = zip(states, 'MAR!?IC', stat)
5374 changestates = zip(states, 'MAR!?IC', stat)
5375
5375
5376 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5376 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5377 copy = copies.pathcopies(repo[node1], repo[node2])
5377 copy = copies.pathcopies(repo[node1], repo[node2])
5378
5378
5379 fm = ui.formatter('status', opts)
5379 fm = ui.formatter('status', opts)
5380 format = '%s %s' + end
5380 format = '%s %s' + end
5381 if opts.get('no_status'):
5381 if opts.get('no_status'):
5382 format = '%.0s%s' + end
5382 format = '%.0s%s' + end
5383
5383
5384 for state, char, files in changestates:
5384 for state, char, files in changestates:
5385 if state in show:
5385 if state in show:
5386 label = 'status.' + state
5386 label = 'status.' + state
5387 for f in files:
5387 for f in files:
5388 fm.startitem()
5388 fm.startitem()
5389 fm.write("status path", format, char,
5389 fm.write("status path", format, char,
5390 repo.pathto(f, cwd), label=label)
5390 repo.pathto(f, cwd), label=label)
5391 if f in copy:
5391 if f in copy:
5392 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5392 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5393 label='status.copied')
5393 label='status.copied')
5394 fm.end()
5394 fm.end()
5395
5395
5396 @command('^summary|sum',
5396 @command('^summary|sum',
5397 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5397 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5398 def summary(ui, repo, **opts):
5398 def summary(ui, repo, **opts):
5399 """summarize working directory state
5399 """summarize working directory state
5400
5400
5401 This generates a brief summary of the working directory state,
5401 This generates a brief summary of the working directory state,
5402 including parents, branch, commit status, and available updates.
5402 including parents, branch, commit status, and available updates.
5403
5403
5404 With the --remote option, this will check the default paths for
5404 With the --remote option, this will check the default paths for
5405 incoming and outgoing changes. This can be time-consuming.
5405 incoming and outgoing changes. This can be time-consuming.
5406
5406
5407 Returns 0 on success.
5407 Returns 0 on success.
5408 """
5408 """
5409
5409
5410 ctx = repo[None]
5410 ctx = repo[None]
5411 parents = ctx.parents()
5411 parents = ctx.parents()
5412 pnode = parents[0].node()
5412 pnode = parents[0].node()
5413 marks = []
5413 marks = []
5414
5414
5415 for p in parents:
5415 for p in parents:
5416 # label with log.changeset (instead of log.parent) since this
5416 # label with log.changeset (instead of log.parent) since this
5417 # shows a working directory parent *changeset*:
5417 # shows a working directory parent *changeset*:
5418 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5418 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5419 label='log.changeset')
5419 label='log.changeset')
5420 ui.write(' '.join(p.tags()), label='log.tag')
5420 ui.write(' '.join(p.tags()), label='log.tag')
5421 if p.bookmarks():
5421 if p.bookmarks():
5422 marks.extend(p.bookmarks())
5422 marks.extend(p.bookmarks())
5423 if p.rev() == -1:
5423 if p.rev() == -1:
5424 if not len(repo):
5424 if not len(repo):
5425 ui.write(_(' (empty repository)'))
5425 ui.write(_(' (empty repository)'))
5426 else:
5426 else:
5427 ui.write(_(' (no revision checked out)'))
5427 ui.write(_(' (no revision checked out)'))
5428 ui.write('\n')
5428 ui.write('\n')
5429 if p.description():
5429 if p.description():
5430 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5430 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5431 label='log.summary')
5431 label='log.summary')
5432
5432
5433 branch = ctx.branch()
5433 branch = ctx.branch()
5434 bheads = repo.branchheads(branch)
5434 bheads = repo.branchheads(branch)
5435 m = _('branch: %s\n') % branch
5435 m = _('branch: %s\n') % branch
5436 if branch != 'default':
5436 if branch != 'default':
5437 ui.write(m, label='log.branch')
5437 ui.write(m, label='log.branch')
5438 else:
5438 else:
5439 ui.status(m, label='log.branch')
5439 ui.status(m, label='log.branch')
5440
5440
5441 if marks:
5441 if marks:
5442 current = repo._bookmarkcurrent
5442 current = repo._bookmarkcurrent
5443 ui.write(_('bookmarks:'), label='log.bookmark')
5443 ui.write(_('bookmarks:'), label='log.bookmark')
5444 if current is not None:
5444 if current is not None:
5445 try:
5445 try:
5446 marks.remove(current)
5446 marks.remove(current)
5447 ui.write(' *' + current, label='bookmarks.current')
5447 ui.write(' *' + current, label='bookmarks.current')
5448 except ValueError:
5448 except ValueError:
5449 # current bookmark not in parent ctx marks
5449 # current bookmark not in parent ctx marks
5450 pass
5450 pass
5451 for m in marks:
5451 for m in marks:
5452 ui.write(' ' + m, label='log.bookmark')
5452 ui.write(' ' + m, label='log.bookmark')
5453 ui.write('\n', label='log.bookmark')
5453 ui.write('\n', label='log.bookmark')
5454
5454
5455 st = list(repo.status(unknown=True))[:6]
5455 st = list(repo.status(unknown=True))[:6]
5456
5456
5457 c = repo.dirstate.copies()
5457 c = repo.dirstate.copies()
5458 copied, renamed = [], []
5458 copied, renamed = [], []
5459 for d, s in c.iteritems():
5459 for d, s in c.iteritems():
5460 if s in st[2]:
5460 if s in st[2]:
5461 st[2].remove(s)
5461 st[2].remove(s)
5462 renamed.append(d)
5462 renamed.append(d)
5463 else:
5463 else:
5464 copied.append(d)
5464 copied.append(d)
5465 if d in st[1]:
5465 if d in st[1]:
5466 st[1].remove(d)
5466 st[1].remove(d)
5467 st.insert(3, renamed)
5467 st.insert(3, renamed)
5468 st.insert(4, copied)
5468 st.insert(4, copied)
5469
5469
5470 ms = mergemod.mergestate(repo)
5470 ms = mergemod.mergestate(repo)
5471 st.append([f for f in ms if ms[f] == 'u'])
5471 st.append([f for f in ms if ms[f] == 'u'])
5472
5472
5473 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5473 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5474 st.append(subs)
5474 st.append(subs)
5475
5475
5476 labels = [ui.label(_('%d modified'), 'status.modified'),
5476 labels = [ui.label(_('%d modified'), 'status.modified'),
5477 ui.label(_('%d added'), 'status.added'),
5477 ui.label(_('%d added'), 'status.added'),
5478 ui.label(_('%d removed'), 'status.removed'),
5478 ui.label(_('%d removed'), 'status.removed'),
5479 ui.label(_('%d renamed'), 'status.copied'),
5479 ui.label(_('%d renamed'), 'status.copied'),
5480 ui.label(_('%d copied'), 'status.copied'),
5480 ui.label(_('%d copied'), 'status.copied'),
5481 ui.label(_('%d deleted'), 'status.deleted'),
5481 ui.label(_('%d deleted'), 'status.deleted'),
5482 ui.label(_('%d unknown'), 'status.unknown'),
5482 ui.label(_('%d unknown'), 'status.unknown'),
5483 ui.label(_('%d ignored'), 'status.ignored'),
5483 ui.label(_('%d ignored'), 'status.ignored'),
5484 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5484 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5485 ui.label(_('%d subrepos'), 'status.modified')]
5485 ui.label(_('%d subrepos'), 'status.modified')]
5486 t = []
5486 t = []
5487 for s, l in zip(st, labels):
5487 for s, l in zip(st, labels):
5488 if s:
5488 if s:
5489 t.append(l % len(s))
5489 t.append(l % len(s))
5490
5490
5491 t = ', '.join(t)
5491 t = ', '.join(t)
5492 cleanworkdir = False
5492 cleanworkdir = False
5493
5493
5494 if len(parents) > 1:
5494 if len(parents) > 1:
5495 t += _(' (merge)')
5495 t += _(' (merge)')
5496 elif branch != parents[0].branch():
5496 elif branch != parents[0].branch():
5497 t += _(' (new branch)')
5497 t += _(' (new branch)')
5498 elif (parents[0].closesbranch() and
5498 elif (parents[0].closesbranch() and
5499 pnode in repo.branchheads(branch, closed=True)):
5499 pnode in repo.branchheads(branch, closed=True)):
5500 t += _(' (head closed)')
5500 t += _(' (head closed)')
5501 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5501 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5502 t += _(' (clean)')
5502 t += _(' (clean)')
5503 cleanworkdir = True
5503 cleanworkdir = True
5504 elif pnode not in bheads:
5504 elif pnode not in bheads:
5505 t += _(' (new branch head)')
5505 t += _(' (new branch head)')
5506
5506
5507 if cleanworkdir:
5507 if cleanworkdir:
5508 ui.status(_('commit: %s\n') % t.strip())
5508 ui.status(_('commit: %s\n') % t.strip())
5509 else:
5509 else:
5510 ui.write(_('commit: %s\n') % t.strip())
5510 ui.write(_('commit: %s\n') % t.strip())
5511
5511
5512 # all ancestors of branch heads - all ancestors of parent = new csets
5512 # all ancestors of branch heads - all ancestors of parent = new csets
5513 new = [0] * len(repo)
5513 new = [0] * len(repo)
5514 cl = repo.changelog
5514 cl = repo.changelog
5515 for a in [cl.rev(n) for n in bheads]:
5515 for a in [cl.rev(n) for n in bheads]:
5516 new[a] = 1
5516 new[a] = 1
5517 for a in cl.ancestors([cl.rev(n) for n in bheads]):
5517 for a in cl.ancestors([cl.rev(n) for n in bheads]):
5518 new[a] = 1
5518 new[a] = 1
5519 for a in [p.rev() for p in parents]:
5519 for a in [p.rev() for p in parents]:
5520 if a >= 0:
5520 if a >= 0:
5521 new[a] = 0
5521 new[a] = 0
5522 for a in cl.ancestors([p.rev() for p in parents]):
5522 for a in cl.ancestors([p.rev() for p in parents]):
5523 new[a] = 0
5523 new[a] = 0
5524 new = sum(new)
5524 new = sum(new)
5525
5525
5526 if new == 0:
5526 if new == 0:
5527 ui.status(_('update: (current)\n'))
5527 ui.status(_('update: (current)\n'))
5528 elif pnode not in bheads:
5528 elif pnode not in bheads:
5529 ui.write(_('update: %d new changesets (update)\n') % new)
5529 ui.write(_('update: %d new changesets (update)\n') % new)
5530 else:
5530 else:
5531 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5531 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5532 (new, len(bheads)))
5532 (new, len(bheads)))
5533
5533
5534 if opts.get('remote'):
5534 if opts.get('remote'):
5535 t = []
5535 t = []
5536 source, branches = hg.parseurl(ui.expandpath('default'))
5536 source, branches = hg.parseurl(ui.expandpath('default'))
5537 other = hg.peer(repo, {}, source)
5537 other = hg.peer(repo, {}, source)
5538 revs, checkout = hg.addbranchrevs(repo, other, branches,
5538 revs, checkout = hg.addbranchrevs(repo, other, branches,
5539 opts.get('rev'))
5539 opts.get('rev'))
5540 ui.debug('comparing with %s\n' % util.hidepassword(source))
5540 ui.debug('comparing with %s\n' % util.hidepassword(source))
5541 repo.ui.pushbuffer()
5541 repo.ui.pushbuffer()
5542 commoninc = discovery.findcommonincoming(repo, other)
5542 commoninc = discovery.findcommonincoming(repo, other)
5543 _common, incoming, _rheads = commoninc
5543 _common, incoming, _rheads = commoninc
5544 repo.ui.popbuffer()
5544 repo.ui.popbuffer()
5545 if incoming:
5545 if incoming:
5546 t.append(_('1 or more incoming'))
5546 t.append(_('1 or more incoming'))
5547
5547
5548 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5548 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5549 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5549 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5550 if source != dest:
5550 if source != dest:
5551 other = hg.peer(repo, {}, dest)
5551 other = hg.peer(repo, {}, dest)
5552 commoninc = None
5552 commoninc = None
5553 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5553 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5554 repo.ui.pushbuffer()
5554 repo.ui.pushbuffer()
5555 outgoing = discovery.findcommonoutgoing(repo, other,
5555 outgoing = discovery.findcommonoutgoing(repo, other,
5556 commoninc=commoninc)
5556 commoninc=commoninc)
5557 repo.ui.popbuffer()
5557 repo.ui.popbuffer()
5558 o = outgoing.missing
5558 o = outgoing.missing
5559 if o:
5559 if o:
5560 t.append(_('%d outgoing') % len(o))
5560 t.append(_('%d outgoing') % len(o))
5561 if 'bookmarks' in other.listkeys('namespaces'):
5561 if 'bookmarks' in other.listkeys('namespaces'):
5562 lmarks = repo.listkeys('bookmarks')
5562 lmarks = repo.listkeys('bookmarks')
5563 rmarks = other.listkeys('bookmarks')
5563 rmarks = other.listkeys('bookmarks')
5564 diff = set(rmarks) - set(lmarks)
5564 diff = set(rmarks) - set(lmarks)
5565 if len(diff) > 0:
5565 if len(diff) > 0:
5566 t.append(_('%d incoming bookmarks') % len(diff))
5566 t.append(_('%d incoming bookmarks') % len(diff))
5567 diff = set(lmarks) - set(rmarks)
5567 diff = set(lmarks) - set(rmarks)
5568 if len(diff) > 0:
5568 if len(diff) > 0:
5569 t.append(_('%d outgoing bookmarks') % len(diff))
5569 t.append(_('%d outgoing bookmarks') % len(diff))
5570
5570
5571 if t:
5571 if t:
5572 ui.write(_('remote: %s\n') % (', '.join(t)))
5572 ui.write(_('remote: %s\n') % (', '.join(t)))
5573 else:
5573 else:
5574 ui.status(_('remote: (synced)\n'))
5574 ui.status(_('remote: (synced)\n'))
5575
5575
5576 @command('tag',
5576 @command('tag',
5577 [('f', 'force', None, _('force tag')),
5577 [('f', 'force', None, _('force tag')),
5578 ('l', 'local', None, _('make the tag local')),
5578 ('l', 'local', None, _('make the tag local')),
5579 ('r', 'rev', '', _('revision to tag'), _('REV')),
5579 ('r', 'rev', '', _('revision to tag'), _('REV')),
5580 ('', 'remove', None, _('remove a tag')),
5580 ('', 'remove', None, _('remove a tag')),
5581 # -l/--local is already there, commitopts cannot be used
5581 # -l/--local is already there, commitopts cannot be used
5582 ('e', 'edit', None, _('edit commit message')),
5582 ('e', 'edit', None, _('edit commit message')),
5583 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5583 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5584 ] + commitopts2,
5584 ] + commitopts2,
5585 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5585 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5586 def tag(ui, repo, name1, *names, **opts):
5586 def tag(ui, repo, name1, *names, **opts):
5587 """add one or more tags for the current or given revision
5587 """add one or more tags for the current or given revision
5588
5588
5589 Name a particular revision using <name>.
5589 Name a particular revision using <name>.
5590
5590
5591 Tags are used to name particular revisions of the repository and are
5591 Tags are used to name particular revisions of the repository and are
5592 very useful to compare different revisions, to go back to significant
5592 very useful to compare different revisions, to go back to significant
5593 earlier versions or to mark branch points as releases, etc. Changing
5593 earlier versions or to mark branch points as releases, etc. Changing
5594 an existing tag is normally disallowed; use -f/--force to override.
5594 an existing tag is normally disallowed; use -f/--force to override.
5595
5595
5596 If no revision is given, the parent of the working directory is
5596 If no revision is given, the parent of the working directory is
5597 used, or tip if no revision is checked out.
5597 used, or tip if no revision is checked out.
5598
5598
5599 To facilitate version control, distribution, and merging of tags,
5599 To facilitate version control, distribution, and merging of tags,
5600 they are stored as a file named ".hgtags" which is managed similarly
5600 they are stored as a file named ".hgtags" which is managed similarly
5601 to other project files and can be hand-edited if necessary. This
5601 to other project files and can be hand-edited if necessary. This
5602 also means that tagging creates a new commit. The file
5602 also means that tagging creates a new commit. The file
5603 ".hg/localtags" is used for local tags (not shared among
5603 ".hg/localtags" is used for local tags (not shared among
5604 repositories).
5604 repositories).
5605
5605
5606 Tag commits are usually made at the head of a branch. If the parent
5606 Tag commits are usually made at the head of a branch. If the parent
5607 of the working directory is not a branch head, :hg:`tag` aborts; use
5607 of the working directory is not a branch head, :hg:`tag` aborts; use
5608 -f/--force to force the tag commit to be based on a non-head
5608 -f/--force to force the tag commit to be based on a non-head
5609 changeset.
5609 changeset.
5610
5610
5611 See :hg:`help dates` for a list of formats valid for -d/--date.
5611 See :hg:`help dates` for a list of formats valid for -d/--date.
5612
5612
5613 Since tag names have priority over branch names during revision
5613 Since tag names have priority over branch names during revision
5614 lookup, using an existing branch name as a tag name is discouraged.
5614 lookup, using an existing branch name as a tag name is discouraged.
5615
5615
5616 Returns 0 on success.
5616 Returns 0 on success.
5617 """
5617 """
5618 wlock = lock = None
5618 wlock = lock = None
5619 try:
5619 try:
5620 wlock = repo.wlock()
5620 wlock = repo.wlock()
5621 lock = repo.lock()
5621 lock = repo.lock()
5622 rev_ = "."
5622 rev_ = "."
5623 names = [t.strip() for t in (name1,) + names]
5623 names = [t.strip() for t in (name1,) + names]
5624 if len(names) != len(set(names)):
5624 if len(names) != len(set(names)):
5625 raise util.Abort(_('tag names must be unique'))
5625 raise util.Abort(_('tag names must be unique'))
5626 for n in names:
5626 for n in names:
5627 if n in ['tip', '.', 'null']:
5627 if n in ['tip', '.', 'null']:
5628 raise util.Abort(_("the name '%s' is reserved") % n)
5628 raise util.Abort(_("the name '%s' is reserved") % n)
5629 if not n:
5629 if not n:
5630 raise util.Abort(_('tag names cannot consist entirely of '
5630 raise util.Abort(_('tag names cannot consist entirely of '
5631 'whitespace'))
5631 'whitespace'))
5632 if opts.get('rev') and opts.get('remove'):
5632 if opts.get('rev') and opts.get('remove'):
5633 raise util.Abort(_("--rev and --remove are incompatible"))
5633 raise util.Abort(_("--rev and --remove are incompatible"))
5634 if opts.get('rev'):
5634 if opts.get('rev'):
5635 rev_ = opts['rev']
5635 rev_ = opts['rev']
5636 message = opts.get('message')
5636 message = opts.get('message')
5637 if opts.get('remove'):
5637 if opts.get('remove'):
5638 expectedtype = opts.get('local') and 'local' or 'global'
5638 expectedtype = opts.get('local') and 'local' or 'global'
5639 for n in names:
5639 for n in names:
5640 if not repo.tagtype(n):
5640 if not repo.tagtype(n):
5641 raise util.Abort(_("tag '%s' does not exist") % n)
5641 raise util.Abort(_("tag '%s' does not exist") % n)
5642 if repo.tagtype(n) != expectedtype:
5642 if repo.tagtype(n) != expectedtype:
5643 if expectedtype == 'global':
5643 if expectedtype == 'global':
5644 raise util.Abort(_("tag '%s' is not a global tag") % n)
5644 raise util.Abort(_("tag '%s' is not a global tag") % n)
5645 else:
5645 else:
5646 raise util.Abort(_("tag '%s' is not a local tag") % n)
5646 raise util.Abort(_("tag '%s' is not a local tag") % n)
5647 rev_ = nullid
5647 rev_ = nullid
5648 if not message:
5648 if not message:
5649 # we don't translate commit messages
5649 # we don't translate commit messages
5650 message = 'Removed tag %s' % ', '.join(names)
5650 message = 'Removed tag %s' % ', '.join(names)
5651 elif not opts.get('force'):
5651 elif not opts.get('force'):
5652 for n in names:
5652 for n in names:
5653 if n in repo.tags():
5653 if n in repo.tags():
5654 raise util.Abort(_("tag '%s' already exists "
5654 raise util.Abort(_("tag '%s' already exists "
5655 "(use -f to force)") % n)
5655 "(use -f to force)") % n)
5656 if not opts.get('local'):
5656 if not opts.get('local'):
5657 p1, p2 = repo.dirstate.parents()
5657 p1, p2 = repo.dirstate.parents()
5658 if p2 != nullid:
5658 if p2 != nullid:
5659 raise util.Abort(_('uncommitted merge'))
5659 raise util.Abort(_('uncommitted merge'))
5660 bheads = repo.branchheads()
5660 bheads = repo.branchheads()
5661 if not opts.get('force') and bheads and p1 not in bheads:
5661 if not opts.get('force') and bheads and p1 not in bheads:
5662 raise util.Abort(_('not at a branch head (use -f to force)'))
5662 raise util.Abort(_('not at a branch head (use -f to force)'))
5663 r = scmutil.revsingle(repo, rev_).node()
5663 r = scmutil.revsingle(repo, rev_).node()
5664
5664
5665 if not message:
5665 if not message:
5666 # we don't translate commit messages
5666 # we don't translate commit messages
5667 message = ('Added tag %s for changeset %s' %
5667 message = ('Added tag %s for changeset %s' %
5668 (', '.join(names), short(r)))
5668 (', '.join(names), short(r)))
5669
5669
5670 date = opts.get('date')
5670 date = opts.get('date')
5671 if date:
5671 if date:
5672 date = util.parsedate(date)
5672 date = util.parsedate(date)
5673
5673
5674 if opts.get('edit'):
5674 if opts.get('edit'):
5675 message = ui.edit(message, ui.username())
5675 message = ui.edit(message, ui.username())
5676
5676
5677 # don't allow tagging the null rev
5677 # don't allow tagging the null rev
5678 if (not opts.get('remove') and
5678 if (not opts.get('remove') and
5679 scmutil.revsingle(repo, rev_).rev() == nullrev):
5679 scmutil.revsingle(repo, rev_).rev() == nullrev):
5680 raise util.Abort(_("null revision specified"))
5680 raise util.Abort(_("null revision specified"))
5681
5681
5682 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5682 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5683 finally:
5683 finally:
5684 release(lock, wlock)
5684 release(lock, wlock)
5685
5685
5686 @command('tags', [], '')
5686 @command('tags', [], '')
5687 def tags(ui, repo):
5687 def tags(ui, repo):
5688 """list repository tags
5688 """list repository tags
5689
5689
5690 This lists both regular and local tags. When the -v/--verbose
5690 This lists both regular and local tags. When the -v/--verbose
5691 switch is used, a third column "local" is printed for local tags.
5691 switch is used, a third column "local" is printed for local tags.
5692
5692
5693 Returns 0 on success.
5693 Returns 0 on success.
5694 """
5694 """
5695
5695
5696 hexfunc = ui.debugflag and hex or short
5696 hexfunc = ui.debugflag and hex or short
5697 tagtype = ""
5697 tagtype = ""
5698
5698
5699 for t, n in reversed(repo.tagslist()):
5699 for t, n in reversed(repo.tagslist()):
5700 if ui.quiet:
5700 if ui.quiet:
5701 ui.write("%s\n" % t, label='tags.normal')
5701 ui.write("%s\n" % t, label='tags.normal')
5702 continue
5702 continue
5703
5703
5704 hn = hexfunc(n)
5704 hn = hexfunc(n)
5705 r = "%5d:%s" % (repo.changelog.rev(n), hn)
5705 r = "%5d:%s" % (repo.changelog.rev(n), hn)
5706 rev = ui.label(r, 'log.changeset')
5706 rev = ui.label(r, 'log.changeset')
5707 spaces = " " * (30 - encoding.colwidth(t))
5707 spaces = " " * (30 - encoding.colwidth(t))
5708
5708
5709 tag = ui.label(t, 'tags.normal')
5709 tag = ui.label(t, 'tags.normal')
5710 if ui.verbose:
5710 if ui.verbose:
5711 if repo.tagtype(t) == 'local':
5711 if repo.tagtype(t) == 'local':
5712 tagtype = " local"
5712 tagtype = " local"
5713 tag = ui.label(t, 'tags.local')
5713 tag = ui.label(t, 'tags.local')
5714 else:
5714 else:
5715 tagtype = ""
5715 tagtype = ""
5716 ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
5716 ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
5717
5717
5718 @command('tip',
5718 @command('tip',
5719 [('p', 'patch', None, _('show patch')),
5719 [('p', 'patch', None, _('show patch')),
5720 ('g', 'git', None, _('use git extended diff format')),
5720 ('g', 'git', None, _('use git extended diff format')),
5721 ] + templateopts,
5721 ] + templateopts,
5722 _('[-p] [-g]'))
5722 _('[-p] [-g]'))
5723 def tip(ui, repo, **opts):
5723 def tip(ui, repo, **opts):
5724 """show the tip revision
5724 """show the tip revision
5725
5725
5726 The tip revision (usually just called the tip) is the changeset
5726 The tip revision (usually just called the tip) is the changeset
5727 most recently added to the repository (and therefore the most
5727 most recently added to the repository (and therefore the most
5728 recently changed head).
5728 recently changed head).
5729
5729
5730 If you have just made a commit, that commit will be the tip. If
5730 If you have just made a commit, that commit will be the tip. If
5731 you have just pulled changes from another repository, the tip of
5731 you have just pulled changes from another repository, the tip of
5732 that repository becomes the current tip. The "tip" tag is special
5732 that repository becomes the current tip. The "tip" tag is special
5733 and cannot be renamed or assigned to a different changeset.
5733 and cannot be renamed or assigned to a different changeset.
5734
5734
5735 Returns 0 on success.
5735 Returns 0 on success.
5736 """
5736 """
5737 displayer = cmdutil.show_changeset(ui, repo, opts)
5737 displayer = cmdutil.show_changeset(ui, repo, opts)
5738 displayer.show(repo[len(repo) - 1])
5738 displayer.show(repo[len(repo) - 1])
5739 displayer.close()
5739 displayer.close()
5740
5740
5741 @command('unbundle',
5741 @command('unbundle',
5742 [('u', 'update', None,
5742 [('u', 'update', None,
5743 _('update to new branch head if changesets were unbundled'))],
5743 _('update to new branch head if changesets were unbundled'))],
5744 _('[-u] FILE...'))
5744 _('[-u] FILE...'))
5745 def unbundle(ui, repo, fname1, *fnames, **opts):
5745 def unbundle(ui, repo, fname1, *fnames, **opts):
5746 """apply one or more changegroup files
5746 """apply one or more changegroup files
5747
5747
5748 Apply one or more compressed changegroup files generated by the
5748 Apply one or more compressed changegroup files generated by the
5749 bundle command.
5749 bundle command.
5750
5750
5751 Returns 0 on success, 1 if an update has unresolved files.
5751 Returns 0 on success, 1 if an update has unresolved files.
5752 """
5752 """
5753 fnames = (fname1,) + fnames
5753 fnames = (fname1,) + fnames
5754
5754
5755 lock = repo.lock()
5755 lock = repo.lock()
5756 wc = repo['.']
5756 wc = repo['.']
5757 try:
5757 try:
5758 for fname in fnames:
5758 for fname in fnames:
5759 f = url.open(ui, fname)
5759 f = url.open(ui, fname)
5760 gen = changegroup.readbundle(f, fname)
5760 gen = changegroup.readbundle(f, fname)
5761 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5761 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5762 finally:
5762 finally:
5763 lock.release()
5763 lock.release()
5764 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5764 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5765 return postincoming(ui, repo, modheads, opts.get('update'), None)
5765 return postincoming(ui, repo, modheads, opts.get('update'), None)
5766
5766
5767 @command('^update|up|checkout|co',
5767 @command('^update|up|checkout|co',
5768 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5768 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5769 ('c', 'check', None,
5769 ('c', 'check', None,
5770 _('update across branches if no uncommitted changes')),
5770 _('update across branches if no uncommitted changes')),
5771 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5771 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5772 ('r', 'rev', '', _('revision'), _('REV'))],
5772 ('r', 'rev', '', _('revision'), _('REV'))],
5773 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5773 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5774 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5774 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5775 """update working directory (or switch revisions)
5775 """update working directory (or switch revisions)
5776
5776
5777 Update the repository's working directory to the specified
5777 Update the repository's working directory to the specified
5778 changeset. If no changeset is specified, update to the tip of the
5778 changeset. If no changeset is specified, update to the tip of the
5779 current named branch and move the current bookmark (see :hg:`help
5779 current named branch and move the current bookmark (see :hg:`help
5780 bookmarks`).
5780 bookmarks`).
5781
5781
5782 Update sets the working directory's parent revision to the specified
5782 Update sets the working directory's parent revision to the specified
5783 changeset (see :hg:`help parents`).
5783 changeset (see :hg:`help parents`).
5784
5784
5785 If the changeset is not a descendant or ancestor of the working
5785 If the changeset is not a descendant or ancestor of the working
5786 directory's parent, the update is aborted. With the -c/--check
5786 directory's parent, the update is aborted. With the -c/--check
5787 option, the working directory is checked for uncommitted changes; if
5787 option, the working directory is checked for uncommitted changes; if
5788 none are found, the working directory is updated to the specified
5788 none are found, the working directory is updated to the specified
5789 changeset.
5789 changeset.
5790
5790
5791 .. container:: verbose
5791 .. container:: verbose
5792
5792
5793 The following rules apply when the working directory contains
5793 The following rules apply when the working directory contains
5794 uncommitted changes:
5794 uncommitted changes:
5795
5795
5796 1. If neither -c/--check nor -C/--clean is specified, and if
5796 1. If neither -c/--check nor -C/--clean is specified, and if
5797 the requested changeset is an ancestor or descendant of
5797 the requested changeset is an ancestor or descendant of
5798 the working directory's parent, the uncommitted changes
5798 the working directory's parent, the uncommitted changes
5799 are merged into the requested changeset and the merged
5799 are merged into the requested changeset and the merged
5800 result is left uncommitted. If the requested changeset is
5800 result is left uncommitted. If the requested changeset is
5801 not an ancestor or descendant (that is, it is on another
5801 not an ancestor or descendant (that is, it is on another
5802 branch), the update is aborted and the uncommitted changes
5802 branch), the update is aborted and the uncommitted changes
5803 are preserved.
5803 are preserved.
5804
5804
5805 2. With the -c/--check option, the update is aborted and the
5805 2. With the -c/--check option, the update is aborted and the
5806 uncommitted changes are preserved.
5806 uncommitted changes are preserved.
5807
5807
5808 3. With the -C/--clean option, uncommitted changes are discarded and
5808 3. With the -C/--clean option, uncommitted changes are discarded and
5809 the working directory is updated to the requested changeset.
5809 the working directory is updated to the requested changeset.
5810
5810
5811 To cancel an uncommitted merge (and lose your changes), use
5811 To cancel an uncommitted merge (and lose your changes), use
5812 :hg:`update --clean .`.
5812 :hg:`update --clean .`.
5813
5813
5814 Use null as the changeset to remove the working directory (like
5814 Use null as the changeset to remove the working directory (like
5815 :hg:`clone -U`).
5815 :hg:`clone -U`).
5816
5816
5817 If you want to revert just one file to an older revision, use
5817 If you want to revert just one file to an older revision, use
5818 :hg:`revert [-r REV] NAME`.
5818 :hg:`revert [-r REV] NAME`.
5819
5819
5820 See :hg:`help dates` for a list of formats valid for -d/--date.
5820 See :hg:`help dates` for a list of formats valid for -d/--date.
5821
5821
5822 Returns 0 on success, 1 if there are unresolved files.
5822 Returns 0 on success, 1 if there are unresolved files.
5823 """
5823 """
5824 if rev and node:
5824 if rev and node:
5825 raise util.Abort(_("please specify just one revision"))
5825 raise util.Abort(_("please specify just one revision"))
5826
5826
5827 if rev is None or rev == '':
5827 if rev is None or rev == '':
5828 rev = node
5828 rev = node
5829
5829
5830 # with no argument, we also move the current bookmark, if any
5830 # with no argument, we also move the current bookmark, if any
5831 movemarkfrom = None
5831 movemarkfrom = None
5832 if rev is None or node == '':
5832 if rev is None or node == '':
5833 movemarkfrom = repo['.'].node()
5833 movemarkfrom = repo['.'].node()
5834
5834
5835 # if we defined a bookmark, we have to remember the original bookmark name
5835 # if we defined a bookmark, we have to remember the original bookmark name
5836 brev = rev
5836 brev = rev
5837 rev = scmutil.revsingle(repo, rev, rev).rev()
5837 rev = scmutil.revsingle(repo, rev, rev).rev()
5838
5838
5839 if check and clean:
5839 if check and clean:
5840 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5840 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5841
5841
5842 if date:
5842 if date:
5843 if rev is not None:
5843 if rev is not None:
5844 raise util.Abort(_("you can't specify a revision and a date"))
5844 raise util.Abort(_("you can't specify a revision and a date"))
5845 rev = cmdutil.finddate(ui, repo, date)
5845 rev = cmdutil.finddate(ui, repo, date)
5846
5846
5847 if check:
5847 if check:
5848 c = repo[None]
5848 c = repo[None]
5849 if c.dirty(merge=False, branch=False):
5849 if c.dirty(merge=False, branch=False):
5850 raise util.Abort(_("uncommitted local changes"))
5850 raise util.Abort(_("uncommitted local changes"))
5851 if rev is None:
5851 if rev is None:
5852 rev = repo[repo[None].branch()].rev()
5852 rev = repo[repo[None].branch()].rev()
5853 mergemod._checkunknown(repo, repo[None], repo[rev])
5853 mergemod._checkunknown(repo, repo[None], repo[rev])
5854
5854
5855 if clean:
5855 if clean:
5856 ret = hg.clean(repo, rev)
5856 ret = hg.clean(repo, rev)
5857 else:
5857 else:
5858 ret = hg.update(repo, rev)
5858 ret = hg.update(repo, rev)
5859
5859
5860 if not ret and movemarkfrom:
5860 if not ret and movemarkfrom:
5861 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5861 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5862 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5862 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5863 elif brev in repo._bookmarks:
5863 elif brev in repo._bookmarks:
5864 bookmarks.setcurrent(repo, brev)
5864 bookmarks.setcurrent(repo, brev)
5865 elif brev:
5865 elif brev:
5866 bookmarks.unsetcurrent(repo)
5866 bookmarks.unsetcurrent(repo)
5867
5867
5868 return ret
5868 return ret
5869
5869
5870 @command('verify', [])
5870 @command('verify', [])
5871 def verify(ui, repo):
5871 def verify(ui, repo):
5872 """verify the integrity of the repository
5872 """verify the integrity of the repository
5873
5873
5874 Verify the integrity of the current repository.
5874 Verify the integrity of the current repository.
5875
5875
5876 This will perform an extensive check of the repository's
5876 This will perform an extensive check of the repository's
5877 integrity, validating the hashes and checksums of each entry in
5877 integrity, validating the hashes and checksums of each entry in
5878 the changelog, manifest, and tracked files, as well as the
5878 the changelog, manifest, and tracked files, as well as the
5879 integrity of their crosslinks and indices.
5879 integrity of their crosslinks and indices.
5880
5880
5881 Returns 0 on success, 1 if errors are encountered.
5881 Returns 0 on success, 1 if errors are encountered.
5882 """
5882 """
5883 return hg.verify(repo)
5883 return hg.verify(repo)
5884
5884
5885 @command('version', [])
5885 @command('version', [])
5886 def version_(ui):
5886 def version_(ui):
5887 """output version and copyright information"""
5887 """output version and copyright information"""
5888 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5888 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5889 % util.version())
5889 % util.version())
5890 ui.status(_(
5890 ui.status(_(
5891 "(see http://mercurial.selenic.com for more information)\n"
5891 "(see http://mercurial.selenic.com for more information)\n"
5892 "\nCopyright (C) 2005-2012 Matt Mackall and others\n"
5892 "\nCopyright (C) 2005-2012 Matt Mackall and others\n"
5893 "This is free software; see the source for copying conditions. "
5893 "This is free software; see the source for copying conditions. "
5894 "There is NO\nwarranty; "
5894 "There is NO\nwarranty; "
5895 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5895 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5896 ))
5896 ))
5897
5897
5898 norepo = ("clone init version help debugcommands debugcomplete"
5898 norepo = ("clone init version help debugcommands debugcomplete"
5899 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5899 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5900 " debugknown debuggetbundle debugbundle")
5900 " debugknown debuggetbundle debugbundle")
5901 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5901 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5902 " debugdata debugindex debugindexdot debugrevlog")
5902 " debugdata debugindex debugindexdot debugrevlog")
@@ -1,238 +1,238 b''
1 # commandserver.py - communicate with Mercurial's API over a pipe
1 # commandserver.py - communicate with Mercurial's API over a pipe
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import struct
9 import struct
10 import sys, os
10 import sys, os
11 import dispatch, encoding, util
11 import dispatch, encoding, util
12
12
13 logfile = None
13 logfile = None
14
14
15 def log(*args):
15 def log(*args):
16 if not logfile:
16 if not logfile:
17 return
17 return
18
18
19 for a in args:
19 for a in args:
20 logfile.write(str(a))
20 logfile.write(str(a))
21
21
22 logfile.flush()
22 logfile.flush()
23
23
24 class channeledoutput(object):
24 class channeledoutput(object):
25 """
25 """
26 Write data from in_ to out in the following format:
26 Write data from in_ to out in the following format:
27
27
28 data length (unsigned int),
28 data length (unsigned int),
29 data
29 data
30 """
30 """
31 def __init__(self, in_, out, channel):
31 def __init__(self, in_, out, channel):
32 self.in_ = in_
32 self.in_ = in_
33 self.out = out
33 self.out = out
34 self.channel = channel
34 self.channel = channel
35
35
36 def write(self, data):
36 def write(self, data):
37 if not data:
37 if not data:
38 return
38 return
39 self.out.write(struct.pack('>cI', self.channel, len(data)))
39 self.out.write(struct.pack('>cI', self.channel, len(data)))
40 self.out.write(data)
40 self.out.write(data)
41 self.out.flush()
41 self.out.flush()
42
42
43 def __getattr__(self, attr):
43 def __getattr__(self, attr):
44 if attr in ('isatty', 'fileno'):
44 if attr in ('isatty', 'fileno'):
45 raise AttributeError, attr
45 raise AttributeError, attr
46 return getattr(self.in_, attr)
46 return getattr(self.in_, attr)
47
47
48 class channeledinput(object):
48 class channeledinput(object):
49 """
49 """
50 Read data from in_.
50 Read data from in_.
51
51
52 Requests for input are written to out in the following format:
52 Requests for input are written to out in the following format:
53 channel identifier - 'I' for plain input, 'L' line based (1 byte)
53 channel identifier - 'I' for plain input, 'L' line based (1 byte)
54 how many bytes to send at most (unsigned int),
54 how many bytes to send at most (unsigned int),
55
55
56 The client replies with:
56 The client replies with:
57 data length (unsigned int), 0 meaning EOF
57 data length (unsigned int), 0 meaning EOF
58 data
58 data
59 """
59 """
60
60
61 maxchunksize = 4 * 1024
61 maxchunksize = 4 * 1024
62
62
63 def __init__(self, in_, out, channel):
63 def __init__(self, in_, out, channel):
64 self.in_ = in_
64 self.in_ = in_
65 self.out = out
65 self.out = out
66 self.channel = channel
66 self.channel = channel
67
67
68 def read(self, size=-1):
68 def read(self, size=-1):
69 if size < 0:
69 if size < 0:
70 # if we need to consume all the clients input, ask for 4k chunks
70 # if we need to consume all the clients input, ask for 4k chunks
71 # so the pipe doesn't fill up risking a deadlock
71 # so the pipe doesn't fill up risking a deadlock
72 size = self.maxchunksize
72 size = self.maxchunksize
73 s = self._read(size, self.channel)
73 s = self._read(size, self.channel)
74 buf = s
74 buf = s
75 while s:
75 while s:
76 s = self._read(size, self.channel)
76 s = self._read(size, self.channel)
77 buf += s
77 buf += s
78
78
79 return buf
79 return buf
80 else:
80 else:
81 return self._read(size, self.channel)
81 return self._read(size, self.channel)
82
82
83 def _read(self, size, channel):
83 def _read(self, size, channel):
84 if not size:
84 if not size:
85 return ''
85 return ''
86 assert size > 0
86 assert size > 0
87
87
88 # tell the client we need at most size bytes
88 # tell the client we need at most size bytes
89 self.out.write(struct.pack('>cI', channel, size))
89 self.out.write(struct.pack('>cI', channel, size))
90 self.out.flush()
90 self.out.flush()
91
91
92 length = self.in_.read(4)
92 length = self.in_.read(4)
93 length = struct.unpack('>I', length)[0]
93 length = struct.unpack('>I', length)[0]
94 if not length:
94 if not length:
95 return ''
95 return ''
96 else:
96 else:
97 return self.in_.read(length)
97 return self.in_.read(length)
98
98
99 def readline(self, size=-1):
99 def readline(self, size=-1):
100 if size < 0:
100 if size < 0:
101 size = self.maxchunksize
101 size = self.maxchunksize
102 s = self._read(size, 'L')
102 s = self._read(size, 'L')
103 buf = s
103 buf = s
104 # keep asking for more until there's either no more or
104 # keep asking for more until there's either no more or
105 # we got a full line
105 # we got a full line
106 while s and s[-1] != '\n':
106 while s and s[-1] != '\n':
107 s = self._read(size, 'L')
107 s = self._read(size, 'L')
108 buf += s
108 buf += s
109
109
110 return buf
110 return buf
111 else:
111 else:
112 return self._read(size, 'L')
112 return self._read(size, 'L')
113
113
114 def __iter__(self):
114 def __iter__(self):
115 return self
115 return self
116
116
117 def next(self):
117 def next(self):
118 l = self.readline()
118 l = self.readline()
119 if not l:
119 if not l:
120 raise StopIteration
120 raise StopIteration
121 return l
121 return l
122
122
123 def __getattr__(self, attr):
123 def __getattr__(self, attr):
124 if attr in ('isatty', 'fileno'):
124 if attr in ('isatty', 'fileno'):
125 raise AttributeError, attr
125 raise AttributeError, attr
126 return getattr(self.in_, attr)
126 return getattr(self.in_, attr)
127
127
128 class server(object):
128 class server(object):
129 """
129 """
130 Listens for commands on stdin, runs them and writes the output on a channel
130 Listens for commands on stdin, runs them and writes the output on a channel
131 based stream to stdout.
131 based stream to stdout.
132 """
132 """
133 def __init__(self, ui, repo, mode):
133 def __init__(self, ui, repo, mode):
134 self.cwd = os.getcwd()
134 self.cwd = os.getcwd()
135
135
136 logpath = ui.config("cmdserver", "log", None)
136 logpath = ui.config("cmdserver", "log", None)
137 if logpath:
137 if logpath:
138 global logfile
138 global logfile
139 if logpath == '-':
139 if logpath == '-':
140 # write log on a special 'd'ebug channel
140 # write log on a special 'd' (debug) channel
141 logfile = channeledoutput(sys.stdout, sys.stdout, 'd')
141 logfile = channeledoutput(sys.stdout, sys.stdout, 'd')
142 else:
142 else:
143 logfile = open(logpath, 'a')
143 logfile = open(logpath, 'a')
144
144
145 # the ui here is really the repo ui so take its baseui so we don't end
145 # the ui here is really the repo ui so take its baseui so we don't end
146 # up with its local configuration
146 # up with its local configuration
147 self.ui = repo.baseui
147 self.ui = repo.baseui
148 self.repo = repo
148 self.repo = repo
149 self.repoui = repo.ui
149 self.repoui = repo.ui
150
150
151 if mode == 'pipe':
151 if mode == 'pipe':
152 self.cerr = channeledoutput(sys.stderr, sys.stdout, 'e')
152 self.cerr = channeledoutput(sys.stderr, sys.stdout, 'e')
153 self.cout = channeledoutput(sys.stdout, sys.stdout, 'o')
153 self.cout = channeledoutput(sys.stdout, sys.stdout, 'o')
154 self.cin = channeledinput(sys.stdin, sys.stdout, 'I')
154 self.cin = channeledinput(sys.stdin, sys.stdout, 'I')
155 self.cresult = channeledoutput(sys.stdout, sys.stdout, 'r')
155 self.cresult = channeledoutput(sys.stdout, sys.stdout, 'r')
156
156
157 self.client = sys.stdin
157 self.client = sys.stdin
158 else:
158 else:
159 raise util.Abort(_('unknown mode %s') % mode)
159 raise util.Abort(_('unknown mode %s') % mode)
160
160
161 def _read(self, size):
161 def _read(self, size):
162 if not size:
162 if not size:
163 return ''
163 return ''
164
164
165 data = self.client.read(size)
165 data = self.client.read(size)
166
166
167 # is the other end closed?
167 # is the other end closed?
168 if not data:
168 if not data:
169 raise EOFError
169 raise EOFError
170
170
171 return data
171 return data
172
172
173 def runcommand(self):
173 def runcommand(self):
174 """ reads a list of \0 terminated arguments, executes
174 """ reads a list of \0 terminated arguments, executes
175 and writes the return code to the result channel """
175 and writes the return code to the result channel """
176
176
177 length = struct.unpack('>I', self._read(4))[0]
177 length = struct.unpack('>I', self._read(4))[0]
178 if not length:
178 if not length:
179 args = []
179 args = []
180 else:
180 else:
181 args = self._read(length).split('\0')
181 args = self._read(length).split('\0')
182
182
183 # copy the uis so changes (e.g. --config or --verbose) don't
183 # copy the uis so changes (e.g. --config or --verbose) don't
184 # persist between requests
184 # persist between requests
185 copiedui = self.ui.copy()
185 copiedui = self.ui.copy()
186 self.repo.baseui = copiedui
186 self.repo.baseui = copiedui
187 self.repo.ui = self.repo.dirstate._ui = self.repoui.copy()
187 self.repo.ui = self.repo.dirstate._ui = self.repoui.copy()
188 self.repo.invalidate()
188 self.repo.invalidate()
189 self.repo.invalidatedirstate()
189 self.repo.invalidatedirstate()
190
190
191 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
191 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
192 self.cout, self.cerr)
192 self.cout, self.cerr)
193
193
194 ret = dispatch.dispatch(req) or 0 # might return None
194 ret = dispatch.dispatch(req) or 0 # might return None
195
195
196 # restore old cwd
196 # restore old cwd
197 if '--cwd' in args:
197 if '--cwd' in args:
198 os.chdir(self.cwd)
198 os.chdir(self.cwd)
199
199
200 self.cresult.write(struct.pack('>i', int(ret)))
200 self.cresult.write(struct.pack('>i', int(ret)))
201
201
202 def getencoding(self):
202 def getencoding(self):
203 """ writes the current encoding to the result channel """
203 """ writes the current encoding to the result channel """
204 self.cresult.write(encoding.encoding)
204 self.cresult.write(encoding.encoding)
205
205
206 def serveone(self):
206 def serveone(self):
207 cmd = self.client.readline()[:-1]
207 cmd = self.client.readline()[:-1]
208 if cmd:
208 if cmd:
209 handler = self.capabilities.get(cmd)
209 handler = self.capabilities.get(cmd)
210 if handler:
210 if handler:
211 handler(self)
211 handler(self)
212 else:
212 else:
213 # clients are expected to check what commands are supported by
213 # clients are expected to check what commands are supported by
214 # looking at the servers capabilities
214 # looking at the servers capabilities
215 raise util.Abort(_('unknown command %s') % cmd)
215 raise util.Abort(_('unknown command %s') % cmd)
216
216
217 return cmd != ''
217 return cmd != ''
218
218
219 capabilities = {'runcommand' : runcommand,
219 capabilities = {'runcommand' : runcommand,
220 'getencoding' : getencoding}
220 'getencoding' : getencoding}
221
221
222 def serve(self):
222 def serve(self):
223 hellomsg = 'capabilities: ' + ' '.join(self.capabilities.keys())
223 hellomsg = 'capabilities: ' + ' '.join(self.capabilities.keys())
224 hellomsg += '\n'
224 hellomsg += '\n'
225 hellomsg += 'encoding: ' + encoding.encoding
225 hellomsg += 'encoding: ' + encoding.encoding
226
226
227 # write the hello msg in -one- chunk
227 # write the hello msg in -one- chunk
228 self.cout.write(hellomsg)
228 self.cout.write(hellomsg)
229
229
230 try:
230 try:
231 while self.serveone():
231 while self.serveone():
232 pass
232 pass
233 except EOFError:
233 except EOFError:
234 # we'll get here if the client disconnected while we were reading
234 # we'll get here if the client disconnected while we were reading
235 # its request
235 # its request
236 return 1
236 return 1
237
237
238 return 0
238 return 0
@@ -1,183 +1,183 b''
1 # config.py - configuration parsing for Mercurial
1 # config.py - configuration parsing for Mercurial
2 #
2 #
3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import error, util
9 import error, util
10 import os, errno
10 import os, errno
11
11
12 class sortdict(dict):
12 class sortdict(dict):
13 'a simple sorted dictionary'
13 'a simple sorted dictionary'
14 def __init__(self, data=None):
14 def __init__(self, data=None):
15 self._list = []
15 self._list = []
16 if data:
16 if data:
17 self.update(data)
17 self.update(data)
18 def copy(self):
18 def copy(self):
19 return sortdict(self)
19 return sortdict(self)
20 def __setitem__(self, key, val):
20 def __setitem__(self, key, val):
21 if key in self:
21 if key in self:
22 self._list.remove(key)
22 self._list.remove(key)
23 self._list.append(key)
23 self._list.append(key)
24 dict.__setitem__(self, key, val)
24 dict.__setitem__(self, key, val)
25 def __iter__(self):
25 def __iter__(self):
26 return self._list.__iter__()
26 return self._list.__iter__()
27 def update(self, src):
27 def update(self, src):
28 for k in src:
28 for k in src:
29 self[k] = src[k]
29 self[k] = src[k]
30 def clear(self):
30 def clear(self):
31 dict.clear(self)
31 dict.clear(self)
32 self._list = []
32 self._list = []
33 def items(self):
33 def items(self):
34 return [(k, self[k]) for k in self._list]
34 return [(k, self[k]) for k in self._list]
35 def __delitem__(self, key):
35 def __delitem__(self, key):
36 dict.__delitem__(self, key)
36 dict.__delitem__(self, key)
37 self._list.remove(key)
37 self._list.remove(key)
38 def keys(self):
38 def keys(self):
39 return self._list
39 return self._list
40 def iterkeys(self):
40 def iterkeys(self):
41 return self._list.__iter__()
41 return self._list.__iter__()
42
42
43 class config(object):
43 class config(object):
44 def __init__(self, data=None):
44 def __init__(self, data=None):
45 self._data = {}
45 self._data = {}
46 self._source = {}
46 self._source = {}
47 if data:
47 if data:
48 for k in data._data:
48 for k in data._data:
49 self._data[k] = data[k].copy()
49 self._data[k] = data[k].copy()
50 self._source = data._source.copy()
50 self._source = data._source.copy()
51 def copy(self):
51 def copy(self):
52 return config(self)
52 return config(self)
53 def __contains__(self, section):
53 def __contains__(self, section):
54 return section in self._data
54 return section in self._data
55 def __getitem__(self, section):
55 def __getitem__(self, section):
56 return self._data.get(section, {})
56 return self._data.get(section, {})
57 def __iter__(self):
57 def __iter__(self):
58 for d in self.sections():
58 for d in self.sections():
59 yield d
59 yield d
60 def update(self, src):
60 def update(self, src):
61 for s in src:
61 for s in src:
62 if s not in self:
62 if s not in self:
63 self._data[s] = sortdict()
63 self._data[s] = sortdict()
64 self._data[s].update(src._data[s])
64 self._data[s].update(src._data[s])
65 self._source.update(src._source)
65 self._source.update(src._source)
66 def get(self, section, item, default=None):
66 def get(self, section, item, default=None):
67 return self._data.get(section, {}).get(item, default)
67 return self._data.get(section, {}).get(item, default)
68
68
69 def backup(self, section, item):
69 def backup(self, section, item):
70 """return a tuple allowing restore to reinstall a previous valuesi
70 """return a tuple allowing restore to reinstall previous values
71
71
72 The main reason we need it is because it handle the "no data" case.
72 The main reason we need it is because it handle the "no data" case.
73 """
73 """
74 try:
74 try:
75 value = self._data[section][item]
75 value = self._data[section][item]
76 source = self.source(section, item)
76 source = self.source(section, item)
77 return (section, item, value, source)
77 return (section, item, value, source)
78 except KeyError:
78 except KeyError:
79 return (section, item)
79 return (section, item)
80
80
81 def source(self, section, item):
81 def source(self, section, item):
82 return self._source.get((section, item), "")
82 return self._source.get((section, item), "")
83 def sections(self):
83 def sections(self):
84 return sorted(self._data.keys())
84 return sorted(self._data.keys())
85 def items(self, section):
85 def items(self, section):
86 return self._data.get(section, {}).items()
86 return self._data.get(section, {}).items()
87 def set(self, section, item, value, source=""):
87 def set(self, section, item, value, source=""):
88 if section not in self:
88 if section not in self:
89 self._data[section] = sortdict()
89 self._data[section] = sortdict()
90 self._data[section][item] = value
90 self._data[section][item] = value
91 self._source[(section, item)] = source
91 self._source[(section, item)] = source
92
92
93 def restore(self, data):
93 def restore(self, data):
94 """restore data returned by self.backup"""
94 """restore data returned by self.backup"""
95 if len(data) == 4:
95 if len(data) == 4:
96 # restore old data
96 # restore old data
97 section, item, value, source = data
97 section, item, value, source = data
98 self._data[section][item] = value
98 self._data[section][item] = value
99 self._source[(section, item)] = source
99 self._source[(section, item)] = source
100 else:
100 else:
101 # no data before, remove everything
101 # no data before, remove everything
102 section, item = data
102 section, item = data
103 if section in self._data:
103 if section in self._data:
104 del self._data[section][item]
104 del self._data[section][item]
105 self._source.pop((section, item), None)
105 self._source.pop((section, item), None)
106
106
107 def parse(self, src, data, sections=None, remap=None, include=None):
107 def parse(self, src, data, sections=None, remap=None, include=None):
108 sectionre = util.compilere(r'\[([^\[]+)\]')
108 sectionre = util.compilere(r'\[([^\[]+)\]')
109 itemre = util.compilere(r'([^=\s][^=]*?)\s*=\s*(.*\S|)')
109 itemre = util.compilere(r'([^=\s][^=]*?)\s*=\s*(.*\S|)')
110 contre = util.compilere(r'\s+(\S|\S.*\S)\s*$')
110 contre = util.compilere(r'\s+(\S|\S.*\S)\s*$')
111 emptyre = util.compilere(r'(;|#|\s*$)')
111 emptyre = util.compilere(r'(;|#|\s*$)')
112 commentre = util.compilere(r'(;|#)')
112 commentre = util.compilere(r'(;|#)')
113 unsetre = util.compilere(r'%unset\s+(\S+)')
113 unsetre = util.compilere(r'%unset\s+(\S+)')
114 includere = util.compilere(r'%include\s+(\S|\S.*\S)\s*$')
114 includere = util.compilere(r'%include\s+(\S|\S.*\S)\s*$')
115 section = ""
115 section = ""
116 item = None
116 item = None
117 line = 0
117 line = 0
118 cont = False
118 cont = False
119
119
120 for l in data.splitlines(True):
120 for l in data.splitlines(True):
121 line += 1
121 line += 1
122 if line == 1 and l.startswith('\xef\xbb\xbf'):
122 if line == 1 and l.startswith('\xef\xbb\xbf'):
123 # Someone set us up the BOM
123 # Someone set us up the BOM
124 l = l[3:]
124 l = l[3:]
125 if cont:
125 if cont:
126 if commentre.match(l):
126 if commentre.match(l):
127 continue
127 continue
128 m = contre.match(l)
128 m = contre.match(l)
129 if m:
129 if m:
130 if sections and section not in sections:
130 if sections and section not in sections:
131 continue
131 continue
132 v = self.get(section, item) + "\n" + m.group(1)
132 v = self.get(section, item) + "\n" + m.group(1)
133 self.set(section, item, v, "%s:%d" % (src, line))
133 self.set(section, item, v, "%s:%d" % (src, line))
134 continue
134 continue
135 item = None
135 item = None
136 cont = False
136 cont = False
137 m = includere.match(l)
137 m = includere.match(l)
138 if m:
138 if m:
139 inc = util.expandpath(m.group(1))
139 inc = util.expandpath(m.group(1))
140 base = os.path.dirname(src)
140 base = os.path.dirname(src)
141 inc = os.path.normpath(os.path.join(base, inc))
141 inc = os.path.normpath(os.path.join(base, inc))
142 if include:
142 if include:
143 try:
143 try:
144 include(inc, remap=remap, sections=sections)
144 include(inc, remap=remap, sections=sections)
145 except IOError, inst:
145 except IOError, inst:
146 if inst.errno != errno.ENOENT:
146 if inst.errno != errno.ENOENT:
147 raise error.ParseError(_("cannot include %s (%s)")
147 raise error.ParseError(_("cannot include %s (%s)")
148 % (inc, inst.strerror),
148 % (inc, inst.strerror),
149 "%s:%s" % (src, line))
149 "%s:%s" % (src, line))
150 continue
150 continue
151 if emptyre.match(l):
151 if emptyre.match(l):
152 continue
152 continue
153 m = sectionre.match(l)
153 m = sectionre.match(l)
154 if m:
154 if m:
155 section = m.group(1)
155 section = m.group(1)
156 if remap:
156 if remap:
157 section = remap.get(section, section)
157 section = remap.get(section, section)
158 if section not in self:
158 if section not in self:
159 self._data[section] = sortdict()
159 self._data[section] = sortdict()
160 continue
160 continue
161 m = itemre.match(l)
161 m = itemre.match(l)
162 if m:
162 if m:
163 item = m.group(1)
163 item = m.group(1)
164 cont = True
164 cont = True
165 if sections and section not in sections:
165 if sections and section not in sections:
166 continue
166 continue
167 self.set(section, item, m.group(2), "%s:%d" % (src, line))
167 self.set(section, item, m.group(2), "%s:%d" % (src, line))
168 continue
168 continue
169 m = unsetre.match(l)
169 m = unsetre.match(l)
170 if m:
170 if m:
171 name = m.group(1)
171 name = m.group(1)
172 if sections and section not in sections:
172 if sections and section not in sections:
173 continue
173 continue
174 if self.get(section, name) is not None:
174 if self.get(section, name) is not None:
175 del self._data[section][name]
175 del self._data[section][name]
176 continue
176 continue
177
177
178 raise error.ParseError(l.rstrip(), ("%s:%s" % (src, line)))
178 raise error.ParseError(l.rstrip(), ("%s:%s" % (src, line)))
179
179
180 def read(self, path, fp=None, sections=None, remap=None):
180 def read(self, path, fp=None, sections=None, remap=None):
181 if not fp:
181 if not fp:
182 fp = util.posixfile(path)
182 fp = util.posixfile(path)
183 self.parse(path, fp.read(), sections, remap, self.read)
183 self.parse(path, fp.read(), sections, remap, self.read)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now