##// END OF EJS Templates
util: fold ENOENT check into unlinkpath, controlled by new ignoremissing flag...
Mads Kiilerich -
r18143:242d2f4e default
parent child Browse files
Show More
@@ -1,469 +1,465 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 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
40 util.unlinkpath(repo.wjoin(f))
41 except OSError, inst:
42 if inst.errno != errno.ENOENT:
43 raise
44 repo[None].forget(list)
40 repo[None].forget(list)
45 finally:
41 finally:
46 wlock.release()
42 wlock.release()
47 return remove(list, unlink=unlink)
43 return remove(list, unlink=unlink)
48
44
49 def repoforget(repo, list):
45 def repoforget(repo, list):
50 forget = repo[None].forget
46 forget = repo[None].forget
51 return forget(list)
47 return forget(list)
52
48
53 def findoutgoing(repo, remote, force):
49 def findoutgoing(repo, remote, force):
54 from mercurial import discovery
50 from mercurial import discovery
55 outgoing = discovery.findcommonoutgoing(repo, remote.peer(), force=force)
51 outgoing = discovery.findcommonoutgoing(repo, remote.peer(), force=force)
56 return outgoing.missing
52 return outgoing.missing
57
53
58 # -- Private worker functions ------------------------------------------
54 # -- Private worker functions ------------------------------------------
59
55
60 def getminsize(ui, assumelfiles, opt, default=10):
56 def getminsize(ui, assumelfiles, opt, default=10):
61 lfsize = opt
57 lfsize = opt
62 if not lfsize and assumelfiles:
58 if not lfsize and assumelfiles:
63 lfsize = ui.config(longname, 'minsize', default=default)
59 lfsize = ui.config(longname, 'minsize', default=default)
64 if lfsize:
60 if lfsize:
65 try:
61 try:
66 lfsize = float(lfsize)
62 lfsize = float(lfsize)
67 except ValueError:
63 except ValueError:
68 raise util.Abort(_('largefiles: size must be number (not %s)\n')
64 raise util.Abort(_('largefiles: size must be number (not %s)\n')
69 % lfsize)
65 % lfsize)
70 if lfsize is None:
66 if lfsize is None:
71 raise util.Abort(_('minimum size for largefiles must be specified'))
67 raise util.Abort(_('minimum size for largefiles must be specified'))
72 return lfsize
68 return lfsize
73
69
74 def link(src, dest):
70 def link(src, dest):
75 try:
71 try:
76 util.oslink(src, dest)
72 util.oslink(src, dest)
77 except OSError:
73 except OSError:
78 # if hardlinks fail, fallback on atomic copy
74 # if hardlinks fail, fallback on atomic copy
79 dst = util.atomictempfile(dest)
75 dst = util.atomictempfile(dest)
80 for chunk in util.filechunkiter(open(src, 'rb')):
76 for chunk in util.filechunkiter(open(src, 'rb')):
81 dst.write(chunk)
77 dst.write(chunk)
82 dst.close()
78 dst.close()
83 os.chmod(dest, os.stat(src).st_mode)
79 os.chmod(dest, os.stat(src).st_mode)
84
80
85 def usercachepath(ui, hash):
81 def usercachepath(ui, hash):
86 path = ui.configpath(longname, 'usercache', None)
82 path = ui.configpath(longname, 'usercache', None)
87 if path:
83 if path:
88 path = os.path.join(path, hash)
84 path = os.path.join(path, hash)
89 else:
85 else:
90 if os.name == 'nt':
86 if os.name == 'nt':
91 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
87 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
92 if appdata:
88 if appdata:
93 path = os.path.join(appdata, longname, hash)
89 path = os.path.join(appdata, longname, hash)
94 elif platform.system() == 'Darwin':
90 elif platform.system() == 'Darwin':
95 home = os.getenv('HOME')
91 home = os.getenv('HOME')
96 if home:
92 if home:
97 path = os.path.join(home, 'Library', 'Caches',
93 path = os.path.join(home, 'Library', 'Caches',
98 longname, hash)
94 longname, hash)
99 elif os.name == 'posix':
95 elif os.name == 'posix':
100 path = os.getenv('XDG_CACHE_HOME')
96 path = os.getenv('XDG_CACHE_HOME')
101 if path:
97 if path:
102 path = os.path.join(path, longname, hash)
98 path = os.path.join(path, longname, hash)
103 else:
99 else:
104 home = os.getenv('HOME')
100 home = os.getenv('HOME')
105 if home:
101 if home:
106 path = os.path.join(home, '.cache', longname, hash)
102 path = os.path.join(home, '.cache', longname, hash)
107 else:
103 else:
108 raise util.Abort(_('unknown operating system: %s\n') % os.name)
104 raise util.Abort(_('unknown operating system: %s\n') % os.name)
109 return path
105 return path
110
106
111 def inusercache(ui, hash):
107 def inusercache(ui, hash):
112 path = usercachepath(ui, hash)
108 path = usercachepath(ui, hash)
113 return path and os.path.exists(path)
109 return path and os.path.exists(path)
114
110
115 def findfile(repo, hash):
111 def findfile(repo, hash):
116 if instore(repo, hash):
112 if instore(repo, hash):
117 repo.ui.note(_('found %s in store\n') % hash)
113 repo.ui.note(_('found %s in store\n') % hash)
118 return storepath(repo, hash)
114 return storepath(repo, hash)
119 elif inusercache(repo.ui, hash):
115 elif inusercache(repo.ui, hash):
120 repo.ui.note(_('found %s in system cache\n') % hash)
116 repo.ui.note(_('found %s in system cache\n') % hash)
121 path = storepath(repo, hash)
117 path = storepath(repo, hash)
122 util.makedirs(os.path.dirname(path))
118 util.makedirs(os.path.dirname(path))
123 link(usercachepath(repo.ui, hash), path)
119 link(usercachepath(repo.ui, hash), path)
124 return path
120 return path
125 return None
121 return None
126
122
127 class largefilesdirstate(dirstate.dirstate):
123 class largefilesdirstate(dirstate.dirstate):
128 def __getitem__(self, key):
124 def __getitem__(self, key):
129 return super(largefilesdirstate, self).__getitem__(unixpath(key))
125 return super(largefilesdirstate, self).__getitem__(unixpath(key))
130 def normal(self, f):
126 def normal(self, f):
131 return super(largefilesdirstate, self).normal(unixpath(f))
127 return super(largefilesdirstate, self).normal(unixpath(f))
132 def remove(self, f):
128 def remove(self, f):
133 return super(largefilesdirstate, self).remove(unixpath(f))
129 return super(largefilesdirstate, self).remove(unixpath(f))
134 def add(self, f):
130 def add(self, f):
135 return super(largefilesdirstate, self).add(unixpath(f))
131 return super(largefilesdirstate, self).add(unixpath(f))
136 def drop(self, f):
132 def drop(self, f):
137 return super(largefilesdirstate, self).drop(unixpath(f))
133 return super(largefilesdirstate, self).drop(unixpath(f))
138 def forget(self, f):
134 def forget(self, f):
139 return super(largefilesdirstate, self).forget(unixpath(f))
135 return super(largefilesdirstate, self).forget(unixpath(f))
140 def normallookup(self, f):
136 def normallookup(self, f):
141 return super(largefilesdirstate, self).normallookup(unixpath(f))
137 return super(largefilesdirstate, self).normallookup(unixpath(f))
142
138
143 def openlfdirstate(ui, repo, create=True):
139 def openlfdirstate(ui, repo, create=True):
144 '''
140 '''
145 Return a dirstate object that tracks largefiles: i.e. its root is
141 Return a dirstate object that tracks largefiles: i.e. its root is
146 the repo root, but it is saved in .hg/largefiles/dirstate.
142 the repo root, but it is saved in .hg/largefiles/dirstate.
147 '''
143 '''
148 admin = repo.join(longname)
144 admin = repo.join(longname)
149 opener = scmutil.opener(admin)
145 opener = scmutil.opener(admin)
150 lfdirstate = largefilesdirstate(opener, ui, repo.root,
146 lfdirstate = largefilesdirstate(opener, ui, repo.root,
151 repo.dirstate._validate)
147 repo.dirstate._validate)
152
148
153 # If the largefiles dirstate does not exist, populate and create
149 # If the largefiles dirstate does not exist, populate and create
154 # it. This ensures that we create it on the first meaningful
150 # it. This ensures that we create it on the first meaningful
155 # largefiles operation in a new clone.
151 # largefiles operation in a new clone.
156 if create and not os.path.exists(os.path.join(admin, 'dirstate')):
152 if create and not os.path.exists(os.path.join(admin, 'dirstate')):
157 util.makedirs(admin)
153 util.makedirs(admin)
158 matcher = getstandinmatcher(repo)
154 matcher = getstandinmatcher(repo)
159 for standin in dirstatewalk(repo.dirstate, matcher):
155 for standin in dirstatewalk(repo.dirstate, matcher):
160 lfile = splitstandin(standin)
156 lfile = splitstandin(standin)
161 hash = readstandin(repo, lfile)
157 hash = readstandin(repo, lfile)
162 lfdirstate.normallookup(lfile)
158 lfdirstate.normallookup(lfile)
163 try:
159 try:
164 if hash == hashfile(repo.wjoin(lfile)):
160 if hash == hashfile(repo.wjoin(lfile)):
165 lfdirstate.normal(lfile)
161 lfdirstate.normal(lfile)
166 except OSError, err:
162 except OSError, err:
167 if err.errno != errno.ENOENT:
163 if err.errno != errno.ENOENT:
168 raise
164 raise
169 return lfdirstate
165 return lfdirstate
170
166
171 def lfdirstatestatus(lfdirstate, repo, rev):
167 def lfdirstatestatus(lfdirstate, repo, rev):
172 match = match_.always(repo.root, repo.getcwd())
168 match = match_.always(repo.root, repo.getcwd())
173 s = lfdirstate.status(match, [], False, False, False)
169 s = lfdirstate.status(match, [], False, False, False)
174 unsure, modified, added, removed, missing, unknown, ignored, clean = s
170 unsure, modified, added, removed, missing, unknown, ignored, clean = s
175 for lfile in unsure:
171 for lfile in unsure:
176 if repo[rev][standin(lfile)].data().strip() != \
172 if repo[rev][standin(lfile)].data().strip() != \
177 hashfile(repo.wjoin(lfile)):
173 hashfile(repo.wjoin(lfile)):
178 modified.append(lfile)
174 modified.append(lfile)
179 else:
175 else:
180 clean.append(lfile)
176 clean.append(lfile)
181 lfdirstate.normal(lfile)
177 lfdirstate.normal(lfile)
182 return (modified, added, removed, missing, unknown, ignored, clean)
178 return (modified, added, removed, missing, unknown, ignored, clean)
183
179
184 def listlfiles(repo, rev=None, matcher=None):
180 def listlfiles(repo, rev=None, matcher=None):
185 '''return a list of largefiles in the working copy or the
181 '''return a list of largefiles in the working copy or the
186 specified changeset'''
182 specified changeset'''
187
183
188 if matcher is None:
184 if matcher is None:
189 matcher = getstandinmatcher(repo)
185 matcher = getstandinmatcher(repo)
190
186
191 # ignore unknown files in working directory
187 # ignore unknown files in working directory
192 return [splitstandin(f)
188 return [splitstandin(f)
193 for f in repo[rev].walk(matcher)
189 for f in repo[rev].walk(matcher)
194 if rev is not None or repo.dirstate[f] != '?']
190 if rev is not None or repo.dirstate[f] != '?']
195
191
196 def instore(repo, hash):
192 def instore(repo, hash):
197 return os.path.exists(storepath(repo, hash))
193 return os.path.exists(storepath(repo, hash))
198
194
199 def storepath(repo, hash):
195 def storepath(repo, hash):
200 return repo.join(os.path.join(longname, hash))
196 return repo.join(os.path.join(longname, hash))
201
197
202 def copyfromcache(repo, hash, filename):
198 def copyfromcache(repo, hash, filename):
203 '''Copy the specified largefile from the repo or system cache to
199 '''Copy the specified largefile from the repo or system cache to
204 filename in the repository. Return true on success or false if the
200 filename in the repository. Return true on success or false if the
205 file was not found in either cache (which should not happened:
201 file was not found in either cache (which should not happened:
206 this is meant to be called only after ensuring that the needed
202 this is meant to be called only after ensuring that the needed
207 largefile exists in the cache).'''
203 largefile exists in the cache).'''
208 path = findfile(repo, hash)
204 path = findfile(repo, hash)
209 if path is None:
205 if path is None:
210 return False
206 return False
211 util.makedirs(os.path.dirname(repo.wjoin(filename)))
207 util.makedirs(os.path.dirname(repo.wjoin(filename)))
212 # The write may fail before the file is fully written, but we
208 # The write may fail before the file is fully written, but we
213 # don't use atomic writes in the working copy.
209 # don't use atomic writes in the working copy.
214 shutil.copy(path, repo.wjoin(filename))
210 shutil.copy(path, repo.wjoin(filename))
215 return True
211 return True
216
212
217 def copytostore(repo, rev, file, uploaded=False):
213 def copytostore(repo, rev, file, uploaded=False):
218 hash = readstandin(repo, file, rev)
214 hash = readstandin(repo, file, rev)
219 if instore(repo, hash):
215 if instore(repo, hash):
220 return
216 return
221 copytostoreabsolute(repo, repo.wjoin(file), hash)
217 copytostoreabsolute(repo, repo.wjoin(file), hash)
222
218
223 def copyalltostore(repo, node):
219 def copyalltostore(repo, node):
224 '''Copy all largefiles in a given revision to the store'''
220 '''Copy all largefiles in a given revision to the store'''
225
221
226 ctx = repo[node]
222 ctx = repo[node]
227 for filename in ctx.files():
223 for filename in ctx.files():
228 if isstandin(filename) and filename in ctx.manifest():
224 if isstandin(filename) and filename in ctx.manifest():
229 realfile = splitstandin(filename)
225 realfile = splitstandin(filename)
230 copytostore(repo, ctx.node(), realfile)
226 copytostore(repo, ctx.node(), realfile)
231
227
232
228
233 def copytostoreabsolute(repo, file, hash):
229 def copytostoreabsolute(repo, file, hash):
234 util.makedirs(os.path.dirname(storepath(repo, hash)))
230 util.makedirs(os.path.dirname(storepath(repo, hash)))
235 if inusercache(repo.ui, hash):
231 if inusercache(repo.ui, hash):
236 link(usercachepath(repo.ui, hash), storepath(repo, hash))
232 link(usercachepath(repo.ui, hash), storepath(repo, hash))
237 elif not getattr(repo, "_isconverting", False):
233 elif not getattr(repo, "_isconverting", False):
238 dst = util.atomictempfile(storepath(repo, hash),
234 dst = util.atomictempfile(storepath(repo, hash),
239 createmode=repo.store.createmode)
235 createmode=repo.store.createmode)
240 for chunk in util.filechunkiter(open(file, 'rb')):
236 for chunk in util.filechunkiter(open(file, 'rb')):
241 dst.write(chunk)
237 dst.write(chunk)
242 dst.close()
238 dst.close()
243 linktousercache(repo, hash)
239 linktousercache(repo, hash)
244
240
245 def linktousercache(repo, hash):
241 def linktousercache(repo, hash):
246 path = usercachepath(repo.ui, hash)
242 path = usercachepath(repo.ui, hash)
247 if path:
243 if path:
248 util.makedirs(os.path.dirname(path))
244 util.makedirs(os.path.dirname(path))
249 link(storepath(repo, hash), path)
245 link(storepath(repo, hash), path)
250
246
251 def getstandinmatcher(repo, pats=[], opts={}):
247 def getstandinmatcher(repo, pats=[], opts={}):
252 '''Return a match object that applies pats to the standin directory'''
248 '''Return a match object that applies pats to the standin directory'''
253 standindir = repo.pathto(shortname)
249 standindir = repo.pathto(shortname)
254 if pats:
250 if pats:
255 # patterns supplied: search standin directory relative to current dir
251 # patterns supplied: search standin directory relative to current dir
256 cwd = repo.getcwd()
252 cwd = repo.getcwd()
257 if os.path.isabs(cwd):
253 if os.path.isabs(cwd):
258 # cwd is an absolute path for hg -R <reponame>
254 # cwd is an absolute path for hg -R <reponame>
259 # work relative to the repository root in this case
255 # work relative to the repository root in this case
260 cwd = ''
256 cwd = ''
261 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
257 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
262 elif os.path.isdir(standindir):
258 elif os.path.isdir(standindir):
263 # no patterns: relative to repo root
259 # no patterns: relative to repo root
264 pats = [standindir]
260 pats = [standindir]
265 else:
261 else:
266 # no patterns and no standin dir: return matcher that matches nothing
262 # no patterns and no standin dir: return matcher that matches nothing
267 match = match_.match(repo.root, None, [], exact=True)
263 match = match_.match(repo.root, None, [], exact=True)
268 match.matchfn = lambda f: False
264 match.matchfn = lambda f: False
269 return match
265 return match
270 return getmatcher(repo, pats, opts, showbad=False)
266 return getmatcher(repo, pats, opts, showbad=False)
271
267
272 def getmatcher(repo, pats=[], opts={}, showbad=True):
268 def getmatcher(repo, pats=[], opts={}, showbad=True):
273 '''Wrapper around scmutil.match() that adds showbad: if false,
269 '''Wrapper around scmutil.match() that adds showbad: if false,
274 neuter the match object's bad() method so it does not print any
270 neuter the match object's bad() method so it does not print any
275 warnings about missing files or directories.'''
271 warnings about missing files or directories.'''
276 match = scmutil.match(repo[None], pats, opts)
272 match = scmutil.match(repo[None], pats, opts)
277
273
278 if not showbad:
274 if not showbad:
279 match.bad = lambda f, msg: None
275 match.bad = lambda f, msg: None
280 return match
276 return match
281
277
282 def composestandinmatcher(repo, rmatcher):
278 def composestandinmatcher(repo, rmatcher):
283 '''Return a matcher that accepts standins corresponding to the
279 '''Return a matcher that accepts standins corresponding to the
284 files accepted by rmatcher. Pass the list of files in the matcher
280 files accepted by rmatcher. Pass the list of files in the matcher
285 as the paths specified by the user.'''
281 as the paths specified by the user.'''
286 smatcher = getstandinmatcher(repo, rmatcher.files())
282 smatcher = getstandinmatcher(repo, rmatcher.files())
287 isstandin = smatcher.matchfn
283 isstandin = smatcher.matchfn
288 def composedmatchfn(f):
284 def composedmatchfn(f):
289 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
285 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
290 smatcher.matchfn = composedmatchfn
286 smatcher.matchfn = composedmatchfn
291
287
292 return smatcher
288 return smatcher
293
289
294 def standin(filename):
290 def standin(filename):
295 '''Return the repo-relative path to the standin for the specified big
291 '''Return the repo-relative path to the standin for the specified big
296 file.'''
292 file.'''
297 # Notes:
293 # Notes:
298 # 1) Some callers want an absolute path, but for instance addlargefiles
294 # 1) Some callers want an absolute path, but for instance addlargefiles
299 # needs it repo-relative so it can be passed to repoadd(). So leave
295 # needs it repo-relative so it can be passed to repoadd(). So leave
300 # it up to the caller to use repo.wjoin() to get an absolute path.
296 # it up to the caller to use repo.wjoin() to get an absolute path.
301 # 2) Join with '/' because that's what dirstate always uses, even on
297 # 2) Join with '/' because that's what dirstate always uses, even on
302 # Windows. Change existing separator to '/' first in case we are
298 # Windows. Change existing separator to '/' first in case we are
303 # passed filenames from an external source (like the command line).
299 # passed filenames from an external source (like the command line).
304 return shortname + '/' + util.pconvert(filename)
300 return shortname + '/' + util.pconvert(filename)
305
301
306 def isstandin(filename):
302 def isstandin(filename):
307 '''Return true if filename is a big file standin. filename must be
303 '''Return true if filename is a big file standin. filename must be
308 in Mercurial's internal form (slash-separated).'''
304 in Mercurial's internal form (slash-separated).'''
309 return filename.startswith(shortname + '/')
305 return filename.startswith(shortname + '/')
310
306
311 def splitstandin(filename):
307 def splitstandin(filename):
312 # Split on / because that's what dirstate always uses, even on Windows.
308 # Split on / because that's what dirstate always uses, even on Windows.
313 # Change local separator to / first just in case we are passed filenames
309 # Change local separator to / first just in case we are passed filenames
314 # from an external source (like the command line).
310 # from an external source (like the command line).
315 bits = util.pconvert(filename).split('/', 1)
311 bits = util.pconvert(filename).split('/', 1)
316 if len(bits) == 2 and bits[0] == shortname:
312 if len(bits) == 2 and bits[0] == shortname:
317 return bits[1]
313 return bits[1]
318 else:
314 else:
319 return None
315 return None
320
316
321 def updatestandin(repo, standin):
317 def updatestandin(repo, standin):
322 file = repo.wjoin(splitstandin(standin))
318 file = repo.wjoin(splitstandin(standin))
323 if os.path.exists(file):
319 if os.path.exists(file):
324 hash = hashfile(file)
320 hash = hashfile(file)
325 executable = getexecutable(file)
321 executable = getexecutable(file)
326 writestandin(repo, standin, hash, executable)
322 writestandin(repo, standin, hash, executable)
327
323
328 def readstandin(repo, filename, node=None):
324 def readstandin(repo, filename, node=None):
329 '''read hex hash from standin for filename at given node, or working
325 '''read hex hash from standin for filename at given node, or working
330 directory if no node is given'''
326 directory if no node is given'''
331 return repo[node][standin(filename)].data().strip()
327 return repo[node][standin(filename)].data().strip()
332
328
333 def writestandin(repo, standin, hash, executable):
329 def writestandin(repo, standin, hash, executable):
334 '''write hash to <repo.root>/<standin>'''
330 '''write hash to <repo.root>/<standin>'''
335 writehash(hash, repo.wjoin(standin), executable)
331 writehash(hash, repo.wjoin(standin), executable)
336
332
337 def copyandhash(instream, outfile):
333 def copyandhash(instream, outfile):
338 '''Read bytes from instream (iterable) and write them to outfile,
334 '''Read bytes from instream (iterable) and write them to outfile,
339 computing the SHA-1 hash of the data along the way. Close outfile
335 computing the SHA-1 hash of the data along the way. Close outfile
340 when done and return the binary hash.'''
336 when done and return the binary hash.'''
341 hasher = util.sha1('')
337 hasher = util.sha1('')
342 for data in instream:
338 for data in instream:
343 hasher.update(data)
339 hasher.update(data)
344 outfile.write(data)
340 outfile.write(data)
345
341
346 # Blecch: closing a file that somebody else opened is rude and
342 # Blecch: closing a file that somebody else opened is rude and
347 # wrong. But it's so darn convenient and practical! After all,
343 # wrong. But it's so darn convenient and practical! After all,
348 # outfile was opened just to copy and hash.
344 # outfile was opened just to copy and hash.
349 outfile.close()
345 outfile.close()
350
346
351 return hasher.digest()
347 return hasher.digest()
352
348
353 def hashrepofile(repo, file):
349 def hashrepofile(repo, file):
354 return hashfile(repo.wjoin(file))
350 return hashfile(repo.wjoin(file))
355
351
356 def hashfile(file):
352 def hashfile(file):
357 if not os.path.exists(file):
353 if not os.path.exists(file):
358 return ''
354 return ''
359 hasher = util.sha1('')
355 hasher = util.sha1('')
360 fd = open(file, 'rb')
356 fd = open(file, 'rb')
361 for data in blockstream(fd):
357 for data in blockstream(fd):
362 hasher.update(data)
358 hasher.update(data)
363 fd.close()
359 fd.close()
364 return hasher.hexdigest()
360 return hasher.hexdigest()
365
361
366 class limitreader(object):
362 class limitreader(object):
367 def __init__(self, f, limit):
363 def __init__(self, f, limit):
368 self.f = f
364 self.f = f
369 self.limit = limit
365 self.limit = limit
370
366
371 def read(self, length):
367 def read(self, length):
372 if self.limit == 0:
368 if self.limit == 0:
373 return ''
369 return ''
374 length = length > self.limit and self.limit or length
370 length = length > self.limit and self.limit or length
375 self.limit -= length
371 self.limit -= length
376 return self.f.read(length)
372 return self.f.read(length)
377
373
378 def close(self):
374 def close(self):
379 pass
375 pass
380
376
381 def blockstream(infile, blocksize=128 * 1024):
377 def blockstream(infile, blocksize=128 * 1024):
382 """Generator that yields blocks of data from infile and closes infile."""
378 """Generator that yields blocks of data from infile and closes infile."""
383 while True:
379 while True:
384 data = infile.read(blocksize)
380 data = infile.read(blocksize)
385 if not data:
381 if not data:
386 break
382 break
387 yield data
383 yield data
388 # same blecch as copyandhash() above
384 # same blecch as copyandhash() above
389 infile.close()
385 infile.close()
390
386
391 def writehash(hash, filename, executable):
387 def writehash(hash, filename, executable):
392 util.makedirs(os.path.dirname(filename))
388 util.makedirs(os.path.dirname(filename))
393 util.writefile(filename, hash + '\n')
389 util.writefile(filename, hash + '\n')
394 os.chmod(filename, getmode(executable))
390 os.chmod(filename, getmode(executable))
395
391
396 def getexecutable(filename):
392 def getexecutable(filename):
397 mode = os.stat(filename).st_mode
393 mode = os.stat(filename).st_mode
398 return ((mode & stat.S_IXUSR) and
394 return ((mode & stat.S_IXUSR) and
399 (mode & stat.S_IXGRP) and
395 (mode & stat.S_IXGRP) and
400 (mode & stat.S_IXOTH))
396 (mode & stat.S_IXOTH))
401
397
402 def getmode(executable):
398 def getmode(executable):
403 if executable:
399 if executable:
404 return 0755
400 return 0755
405 else:
401 else:
406 return 0644
402 return 0644
407
403
408 def urljoin(first, second, *arg):
404 def urljoin(first, second, *arg):
409 def join(left, right):
405 def join(left, right):
410 if not left.endswith('/'):
406 if not left.endswith('/'):
411 left += '/'
407 left += '/'
412 if right.startswith('/'):
408 if right.startswith('/'):
413 right = right[1:]
409 right = right[1:]
414 return left + right
410 return left + right
415
411
416 url = join(first, second)
412 url = join(first, second)
417 for a in arg:
413 for a in arg:
418 url = join(url, a)
414 url = join(url, a)
419 return url
415 return url
420
416
421 def hexsha1(data):
417 def hexsha1(data):
422 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
418 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
423 object data"""
419 object data"""
424 h = util.sha1()
420 h = util.sha1()
425 for chunk in util.filechunkiter(data):
421 for chunk in util.filechunkiter(data):
426 h.update(chunk)
422 h.update(chunk)
427 return h.hexdigest()
423 return h.hexdigest()
428
424
429 def httpsendfile(ui, filename):
425 def httpsendfile(ui, filename):
430 return httpconnection.httpsendfile(ui, filename, 'rb')
426 return httpconnection.httpsendfile(ui, filename, 'rb')
431
427
432 def unixpath(path):
428 def unixpath(path):
433 '''Return a version of path normalized for use with the lfdirstate.'''
429 '''Return a version of path normalized for use with the lfdirstate.'''
434 return util.pconvert(os.path.normpath(path))
430 return util.pconvert(os.path.normpath(path))
435
431
436 def islfilesrepo(repo):
432 def islfilesrepo(repo):
437 if ('largefiles' in repo.requirements and
433 if ('largefiles' in repo.requirements and
438 util.any(shortname + '/' in f[0] for f in repo.store.datafiles())):
434 util.any(shortname + '/' in f[0] for f in repo.store.datafiles())):
439 return True
435 return True
440
436
441 return util.any(openlfdirstate(repo.ui, repo, False))
437 return util.any(openlfdirstate(repo.ui, repo, False))
442
438
443 class storeprotonotcapable(Exception):
439 class storeprotonotcapable(Exception):
444 def __init__(self, storetypes):
440 def __init__(self, storetypes):
445 self.storetypes = storetypes
441 self.storetypes = storetypes
446
442
447 def getcurrentheads(repo):
443 def getcurrentheads(repo):
448 branches = repo.branchmap()
444 branches = repo.branchmap()
449 heads = []
445 heads = []
450 for branch in branches:
446 for branch in branches:
451 newheads = repo.branchheads(branch)
447 newheads = repo.branchheads(branch)
452 heads = heads + newheads
448 heads = heads + newheads
453 return heads
449 return heads
454
450
455 def getstandinsstate(repo):
451 def getstandinsstate(repo):
456 standins = []
452 standins = []
457 matcher = getstandinmatcher(repo)
453 matcher = getstandinmatcher(repo)
458 for standin in dirstatewalk(repo.dirstate, matcher):
454 for standin in dirstatewalk(repo.dirstate, matcher):
459 lfile = splitstandin(standin)
455 lfile = splitstandin(standin)
460 standins.append((lfile, readstandin(repo, lfile)))
456 standins.append((lfile, readstandin(repo, lfile)))
461 return standins
457 return standins
462
458
463 def getlfilestoupdate(oldstandins, newstandins):
459 def getlfilestoupdate(oldstandins, newstandins):
464 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
460 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
465 filelist = []
461 filelist = []
466 for f in changedstandins:
462 for f in changedstandins:
467 if f[0] not in filelist:
463 if f[0] not in filelist:
468 filelist.append(f[0])
464 filelist.append(f[0])
469 return filelist
465 return filelist
@@ -1,3611 +1,3603 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, error, phases
66 from mercurial import repair, extensions, 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 repo = repo.unfiltered()
278 repo = repo.unfiltered()
279 if phase is None:
279 if phase is None:
280 if repo.ui.configbool('mq', 'secret', False):
280 if repo.ui.configbool('mq', 'secret', False):
281 phase = phases.secret
281 phase = phases.secret
282 if phase is not None:
282 if phase is not None:
283 backup = repo.ui.backupconfig('phases', 'new-commit')
283 backup = repo.ui.backupconfig('phases', 'new-commit')
284 # Marking the repository as committing an mq patch can be used
284 # Marking the repository as committing an mq patch can be used
285 # to optimize operations like _branchtags().
285 # to optimize operations like _branchtags().
286 repo._committingpatch = True
286 repo._committingpatch = True
287 try:
287 try:
288 if phase is not None:
288 if phase is not None:
289 repo.ui.setconfig('phases', 'new-commit', phase)
289 repo.ui.setconfig('phases', 'new-commit', phase)
290 return repo.commit(*args, **kwargs)
290 return repo.commit(*args, **kwargs)
291 finally:
291 finally:
292 repo._committingpatch = False
292 repo._committingpatch = False
293 if phase is not None:
293 if phase is not None:
294 repo.ui.restoreconfig(backup)
294 repo.ui.restoreconfig(backup)
295
295
296 class AbortNoCleanup(error.Abort):
296 class AbortNoCleanup(error.Abort):
297 pass
297 pass
298
298
299 class queue(object):
299 class queue(object):
300 def __init__(self, ui, path, patchdir=None):
300 def __init__(self, ui, path, patchdir=None):
301 self.basepath = path
301 self.basepath = path
302 try:
302 try:
303 fh = open(os.path.join(path, 'patches.queue'))
303 fh = open(os.path.join(path, 'patches.queue'))
304 cur = fh.read().rstrip()
304 cur = fh.read().rstrip()
305 fh.close()
305 fh.close()
306 if not cur:
306 if not cur:
307 curpath = os.path.join(path, 'patches')
307 curpath = os.path.join(path, 'patches')
308 else:
308 else:
309 curpath = os.path.join(path, 'patches-' + cur)
309 curpath = os.path.join(path, 'patches-' + cur)
310 except IOError:
310 except IOError:
311 curpath = os.path.join(path, 'patches')
311 curpath = os.path.join(path, 'patches')
312 self.path = patchdir or curpath
312 self.path = patchdir or curpath
313 self.opener = scmutil.opener(self.path)
313 self.opener = scmutil.opener(self.path)
314 self.ui = ui
314 self.ui = ui
315 self.applieddirty = False
315 self.applieddirty = False
316 self.seriesdirty = False
316 self.seriesdirty = False
317 self.added = []
317 self.added = []
318 self.seriespath = "series"
318 self.seriespath = "series"
319 self.statuspath = "status"
319 self.statuspath = "status"
320 self.guardspath = "guards"
320 self.guardspath = "guards"
321 self.activeguards = None
321 self.activeguards = None
322 self.guardsdirty = False
322 self.guardsdirty = False
323 # Handle mq.git as a bool with extended values
323 # Handle mq.git as a bool with extended values
324 try:
324 try:
325 gitmode = ui.configbool('mq', 'git', None)
325 gitmode = ui.configbool('mq', 'git', None)
326 if gitmode is None:
326 if gitmode is None:
327 raise error.ConfigError
327 raise error.ConfigError
328 self.gitmode = gitmode and 'yes' or 'no'
328 self.gitmode = gitmode and 'yes' or 'no'
329 except error.ConfigError:
329 except error.ConfigError:
330 self.gitmode = ui.config('mq', 'git', 'auto').lower()
330 self.gitmode = ui.config('mq', 'git', 'auto').lower()
331 self.plainmode = ui.configbool('mq', 'plain', False)
331 self.plainmode = ui.configbool('mq', 'plain', False)
332
332
333 @util.propertycache
333 @util.propertycache
334 def applied(self):
334 def applied(self):
335 def parselines(lines):
335 def parselines(lines):
336 for l in lines:
336 for l in lines:
337 entry = l.split(':', 1)
337 entry = l.split(':', 1)
338 if len(entry) > 1:
338 if len(entry) > 1:
339 n, name = entry
339 n, name = entry
340 yield statusentry(bin(n), name)
340 yield statusentry(bin(n), name)
341 elif l.strip():
341 elif l.strip():
342 self.ui.warn(_('malformated mq status line: %s\n') % entry)
342 self.ui.warn(_('malformated mq status line: %s\n') % entry)
343 # else we ignore empty lines
343 # else we ignore empty lines
344 try:
344 try:
345 lines = self.opener.read(self.statuspath).splitlines()
345 lines = self.opener.read(self.statuspath).splitlines()
346 return list(parselines(lines))
346 return list(parselines(lines))
347 except IOError, e:
347 except IOError, e:
348 if e.errno == errno.ENOENT:
348 if e.errno == errno.ENOENT:
349 return []
349 return []
350 raise
350 raise
351
351
352 @util.propertycache
352 @util.propertycache
353 def fullseries(self):
353 def fullseries(self):
354 try:
354 try:
355 return self.opener.read(self.seriespath).splitlines()
355 return self.opener.read(self.seriespath).splitlines()
356 except IOError, e:
356 except IOError, e:
357 if e.errno == errno.ENOENT:
357 if e.errno == errno.ENOENT:
358 return []
358 return []
359 raise
359 raise
360
360
361 @util.propertycache
361 @util.propertycache
362 def series(self):
362 def series(self):
363 self.parseseries()
363 self.parseseries()
364 return self.series
364 return self.series
365
365
366 @util.propertycache
366 @util.propertycache
367 def seriesguards(self):
367 def seriesguards(self):
368 self.parseseries()
368 self.parseseries()
369 return self.seriesguards
369 return self.seriesguards
370
370
371 def invalidate(self):
371 def invalidate(self):
372 for a in 'applied fullseries series seriesguards'.split():
372 for a in 'applied fullseries series seriesguards'.split():
373 if a in self.__dict__:
373 if a in self.__dict__:
374 delattr(self, a)
374 delattr(self, a)
375 self.applieddirty = False
375 self.applieddirty = False
376 self.seriesdirty = False
376 self.seriesdirty = False
377 self.guardsdirty = False
377 self.guardsdirty = False
378 self.activeguards = None
378 self.activeguards = None
379
379
380 def diffopts(self, opts={}, patchfn=None):
380 def diffopts(self, opts={}, patchfn=None):
381 diffopts = patchmod.diffopts(self.ui, opts)
381 diffopts = patchmod.diffopts(self.ui, opts)
382 if self.gitmode == 'auto':
382 if self.gitmode == 'auto':
383 diffopts.upgrade = True
383 diffopts.upgrade = True
384 elif self.gitmode == 'keep':
384 elif self.gitmode == 'keep':
385 pass
385 pass
386 elif self.gitmode in ('yes', 'no'):
386 elif self.gitmode in ('yes', 'no'):
387 diffopts.git = self.gitmode == 'yes'
387 diffopts.git = self.gitmode == 'yes'
388 else:
388 else:
389 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
389 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
390 ' got %s') % self.gitmode)
390 ' got %s') % self.gitmode)
391 if patchfn:
391 if patchfn:
392 diffopts = self.patchopts(diffopts, patchfn)
392 diffopts = self.patchopts(diffopts, patchfn)
393 return diffopts
393 return diffopts
394
394
395 def patchopts(self, diffopts, *patches):
395 def patchopts(self, diffopts, *patches):
396 """Return a copy of input diff options with git set to true if
396 """Return a copy of input diff options with git set to true if
397 referenced patch is a git patch and should be preserved as such.
397 referenced patch is a git patch and should be preserved as such.
398 """
398 """
399 diffopts = diffopts.copy()
399 diffopts = diffopts.copy()
400 if not diffopts.git and self.gitmode == 'keep':
400 if not diffopts.git and self.gitmode == 'keep':
401 for patchfn in patches:
401 for patchfn in patches:
402 patchf = self.opener(patchfn, 'r')
402 patchf = self.opener(patchfn, 'r')
403 # if the patch was a git patch, refresh it as a git patch
403 # if the patch was a git patch, refresh it as a git patch
404 for line in patchf:
404 for line in patchf:
405 if line.startswith('diff --git'):
405 if line.startswith('diff --git'):
406 diffopts.git = True
406 diffopts.git = True
407 break
407 break
408 patchf.close()
408 patchf.close()
409 return diffopts
409 return diffopts
410
410
411 def join(self, *p):
411 def join(self, *p):
412 return os.path.join(self.path, *p)
412 return os.path.join(self.path, *p)
413
413
414 def findseries(self, patch):
414 def findseries(self, patch):
415 def matchpatch(l):
415 def matchpatch(l):
416 l = l.split('#', 1)[0]
416 l = l.split('#', 1)[0]
417 return l.strip() == patch
417 return l.strip() == patch
418 for index, l in enumerate(self.fullseries):
418 for index, l in enumerate(self.fullseries):
419 if matchpatch(l):
419 if matchpatch(l):
420 return index
420 return index
421 return None
421 return None
422
422
423 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
423 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
424
424
425 def parseseries(self):
425 def parseseries(self):
426 self.series = []
426 self.series = []
427 self.seriesguards = []
427 self.seriesguards = []
428 for l in self.fullseries:
428 for l in self.fullseries:
429 h = l.find('#')
429 h = l.find('#')
430 if h == -1:
430 if h == -1:
431 patch = l
431 patch = l
432 comment = ''
432 comment = ''
433 elif h == 0:
433 elif h == 0:
434 continue
434 continue
435 else:
435 else:
436 patch = l[:h]
436 patch = l[:h]
437 comment = l[h:]
437 comment = l[h:]
438 patch = patch.strip()
438 patch = patch.strip()
439 if patch:
439 if patch:
440 if patch in self.series:
440 if patch in self.series:
441 raise util.Abort(_('%s appears more than once in %s') %
441 raise util.Abort(_('%s appears more than once in %s') %
442 (patch, self.join(self.seriespath)))
442 (patch, self.join(self.seriespath)))
443 self.series.append(patch)
443 self.series.append(patch)
444 self.seriesguards.append(self.guard_re.findall(comment))
444 self.seriesguards.append(self.guard_re.findall(comment))
445
445
446 def checkguard(self, guard):
446 def checkguard(self, guard):
447 if not guard:
447 if not guard:
448 return _('guard cannot be an empty string')
448 return _('guard cannot be an empty string')
449 bad_chars = '# \t\r\n\f'
449 bad_chars = '# \t\r\n\f'
450 first = guard[0]
450 first = guard[0]
451 if first in '-+':
451 if first in '-+':
452 return (_('guard %r starts with invalid character: %r') %
452 return (_('guard %r starts with invalid character: %r') %
453 (guard, first))
453 (guard, first))
454 for c in bad_chars:
454 for c in bad_chars:
455 if c in guard:
455 if c in guard:
456 return _('invalid character in guard %r: %r') % (guard, c)
456 return _('invalid character in guard %r: %r') % (guard, c)
457
457
458 def setactive(self, guards):
458 def setactive(self, guards):
459 for guard in guards:
459 for guard in guards:
460 bad = self.checkguard(guard)
460 bad = self.checkguard(guard)
461 if bad:
461 if bad:
462 raise util.Abort(bad)
462 raise util.Abort(bad)
463 guards = sorted(set(guards))
463 guards = sorted(set(guards))
464 self.ui.debug('active guards: %s\n' % ' '.join(guards))
464 self.ui.debug('active guards: %s\n' % ' '.join(guards))
465 self.activeguards = guards
465 self.activeguards = guards
466 self.guardsdirty = True
466 self.guardsdirty = True
467
467
468 def active(self):
468 def active(self):
469 if self.activeguards is None:
469 if self.activeguards is None:
470 self.activeguards = []
470 self.activeguards = []
471 try:
471 try:
472 guards = self.opener.read(self.guardspath).split()
472 guards = self.opener.read(self.guardspath).split()
473 except IOError, err:
473 except IOError, err:
474 if err.errno != errno.ENOENT:
474 if err.errno != errno.ENOENT:
475 raise
475 raise
476 guards = []
476 guards = []
477 for i, guard in enumerate(guards):
477 for i, guard in enumerate(guards):
478 bad = self.checkguard(guard)
478 bad = self.checkguard(guard)
479 if bad:
479 if bad:
480 self.ui.warn('%s:%d: %s\n' %
480 self.ui.warn('%s:%d: %s\n' %
481 (self.join(self.guardspath), i + 1, bad))
481 (self.join(self.guardspath), i + 1, bad))
482 else:
482 else:
483 self.activeguards.append(guard)
483 self.activeguards.append(guard)
484 return self.activeguards
484 return self.activeguards
485
485
486 def setguards(self, idx, guards):
486 def setguards(self, idx, guards):
487 for g in guards:
487 for g in guards:
488 if len(g) < 2:
488 if len(g) < 2:
489 raise util.Abort(_('guard %r too short') % g)
489 raise util.Abort(_('guard %r too short') % g)
490 if g[0] not in '-+':
490 if g[0] not in '-+':
491 raise util.Abort(_('guard %r starts with invalid char') % g)
491 raise util.Abort(_('guard %r starts with invalid char') % g)
492 bad = self.checkguard(g[1:])
492 bad = self.checkguard(g[1:])
493 if bad:
493 if bad:
494 raise util.Abort(bad)
494 raise util.Abort(bad)
495 drop = self.guard_re.sub('', self.fullseries[idx])
495 drop = self.guard_re.sub('', self.fullseries[idx])
496 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
496 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
497 self.parseseries()
497 self.parseseries()
498 self.seriesdirty = True
498 self.seriesdirty = True
499
499
500 def pushable(self, idx):
500 def pushable(self, idx):
501 if isinstance(idx, str):
501 if isinstance(idx, str):
502 idx = self.series.index(idx)
502 idx = self.series.index(idx)
503 patchguards = self.seriesguards[idx]
503 patchguards = self.seriesguards[idx]
504 if not patchguards:
504 if not patchguards:
505 return True, None
505 return True, None
506 guards = self.active()
506 guards = self.active()
507 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
507 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
508 if exactneg:
508 if exactneg:
509 return False, repr(exactneg[0])
509 return False, repr(exactneg[0])
510 pos = [g for g in patchguards if g[0] == '+']
510 pos = [g for g in patchguards if g[0] == '+']
511 exactpos = [g for g in pos if g[1:] in guards]
511 exactpos = [g for g in pos if g[1:] in guards]
512 if pos:
512 if pos:
513 if exactpos:
513 if exactpos:
514 return True, repr(exactpos[0])
514 return True, repr(exactpos[0])
515 return False, ' '.join(map(repr, pos))
515 return False, ' '.join(map(repr, pos))
516 return True, ''
516 return True, ''
517
517
518 def explainpushable(self, idx, all_patches=False):
518 def explainpushable(self, idx, all_patches=False):
519 write = all_patches and self.ui.write or self.ui.warn
519 write = all_patches and self.ui.write or self.ui.warn
520 if all_patches or self.ui.verbose:
520 if all_patches or self.ui.verbose:
521 if isinstance(idx, str):
521 if isinstance(idx, str):
522 idx = self.series.index(idx)
522 idx = self.series.index(idx)
523 pushable, why = self.pushable(idx)
523 pushable, why = self.pushable(idx)
524 if all_patches and pushable:
524 if all_patches and pushable:
525 if why is None:
525 if why is None:
526 write(_('allowing %s - no guards in effect\n') %
526 write(_('allowing %s - no guards in effect\n') %
527 self.series[idx])
527 self.series[idx])
528 else:
528 else:
529 if not why:
529 if not why:
530 write(_('allowing %s - no matching negative guards\n') %
530 write(_('allowing %s - no matching negative guards\n') %
531 self.series[idx])
531 self.series[idx])
532 else:
532 else:
533 write(_('allowing %s - guarded by %s\n') %
533 write(_('allowing %s - guarded by %s\n') %
534 (self.series[idx], why))
534 (self.series[idx], why))
535 if not pushable:
535 if not pushable:
536 if why:
536 if why:
537 write(_('skipping %s - guarded by %s\n') %
537 write(_('skipping %s - guarded by %s\n') %
538 (self.series[idx], why))
538 (self.series[idx], why))
539 else:
539 else:
540 write(_('skipping %s - no matching guards\n') %
540 write(_('skipping %s - no matching guards\n') %
541 self.series[idx])
541 self.series[idx])
542
542
543 def savedirty(self):
543 def savedirty(self):
544 def writelist(items, path):
544 def writelist(items, path):
545 fp = self.opener(path, 'w')
545 fp = self.opener(path, 'w')
546 for i in items:
546 for i in items:
547 fp.write("%s\n" % i)
547 fp.write("%s\n" % i)
548 fp.close()
548 fp.close()
549 if self.applieddirty:
549 if self.applieddirty:
550 writelist(map(str, self.applied), self.statuspath)
550 writelist(map(str, self.applied), self.statuspath)
551 self.applieddirty = False
551 self.applieddirty = False
552 if self.seriesdirty:
552 if self.seriesdirty:
553 writelist(self.fullseries, self.seriespath)
553 writelist(self.fullseries, self.seriespath)
554 self.seriesdirty = False
554 self.seriesdirty = False
555 if self.guardsdirty:
555 if self.guardsdirty:
556 writelist(self.activeguards, self.guardspath)
556 writelist(self.activeguards, self.guardspath)
557 self.guardsdirty = False
557 self.guardsdirty = False
558 if self.added:
558 if self.added:
559 qrepo = self.qrepo()
559 qrepo = self.qrepo()
560 if qrepo:
560 if qrepo:
561 qrepo[None].add(f for f in self.added if f not in qrepo[None])
561 qrepo[None].add(f for f in self.added if f not in qrepo[None])
562 self.added = []
562 self.added = []
563
563
564 def removeundo(self, repo):
564 def removeundo(self, repo):
565 undo = repo.sjoin('undo')
565 undo = repo.sjoin('undo')
566 if not os.path.exists(undo):
566 if not os.path.exists(undo):
567 return
567 return
568 try:
568 try:
569 os.unlink(undo)
569 os.unlink(undo)
570 except OSError, inst:
570 except OSError, inst:
571 self.ui.warn(_('error removing undo: %s\n') % str(inst))
571 self.ui.warn(_('error removing undo: %s\n') % str(inst))
572
572
573 def backup(self, repo, files, copy=False):
573 def backup(self, repo, files, copy=False):
574 # backup local changes in --force case
574 # backup local changes in --force case
575 for f in sorted(files):
575 for f in sorted(files):
576 absf = repo.wjoin(f)
576 absf = repo.wjoin(f)
577 if os.path.lexists(absf):
577 if os.path.lexists(absf):
578 self.ui.note(_('saving current version of %s as %s\n') %
578 self.ui.note(_('saving current version of %s as %s\n') %
579 (f, f + '.orig'))
579 (f, f + '.orig'))
580 if copy:
580 if copy:
581 util.copyfile(absf, absf + '.orig')
581 util.copyfile(absf, absf + '.orig')
582 else:
582 else:
583 util.rename(absf, absf + '.orig')
583 util.rename(absf, absf + '.orig')
584
584
585 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
585 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
586 fp=None, changes=None, opts={}):
586 fp=None, changes=None, opts={}):
587 stat = opts.get('stat')
587 stat = opts.get('stat')
588 m = scmutil.match(repo[node1], files, opts)
588 m = scmutil.match(repo[node1], files, opts)
589 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
589 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
590 changes, stat, fp)
590 changes, stat, fp)
591
591
592 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
592 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
593 # first try just applying the patch
593 # first try just applying the patch
594 (err, n) = self.apply(repo, [patch], update_status=False,
594 (err, n) = self.apply(repo, [patch], update_status=False,
595 strict=True, merge=rev)
595 strict=True, merge=rev)
596
596
597 if err == 0:
597 if err == 0:
598 return (err, n)
598 return (err, n)
599
599
600 if n is None:
600 if n is None:
601 raise util.Abort(_("apply failed for patch %s") % patch)
601 raise util.Abort(_("apply failed for patch %s") % patch)
602
602
603 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
603 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
604
604
605 # apply failed, strip away that rev and merge.
605 # apply failed, strip away that rev and merge.
606 hg.clean(repo, head)
606 hg.clean(repo, head)
607 self.strip(repo, [n], update=False, backup='strip')
607 self.strip(repo, [n], update=False, backup='strip')
608
608
609 ctx = repo[rev]
609 ctx = repo[rev]
610 ret = hg.merge(repo, rev)
610 ret = hg.merge(repo, rev)
611 if ret:
611 if ret:
612 raise util.Abort(_("update returned %d") % ret)
612 raise util.Abort(_("update returned %d") % ret)
613 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
613 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
614 if n is None:
614 if n is None:
615 raise util.Abort(_("repo commit failed"))
615 raise util.Abort(_("repo commit failed"))
616 try:
616 try:
617 ph = patchheader(mergeq.join(patch), self.plainmode)
617 ph = patchheader(mergeq.join(patch), self.plainmode)
618 except Exception:
618 except Exception:
619 raise util.Abort(_("unable to read %s") % patch)
619 raise util.Abort(_("unable to read %s") % patch)
620
620
621 diffopts = self.patchopts(diffopts, patch)
621 diffopts = self.patchopts(diffopts, patch)
622 patchf = self.opener(patch, "w")
622 patchf = self.opener(patch, "w")
623 comments = str(ph)
623 comments = str(ph)
624 if comments:
624 if comments:
625 patchf.write(comments)
625 patchf.write(comments)
626 self.printdiff(repo, diffopts, head, n, fp=patchf)
626 self.printdiff(repo, diffopts, head, n, fp=patchf)
627 patchf.close()
627 patchf.close()
628 self.removeundo(repo)
628 self.removeundo(repo)
629 return (0, n)
629 return (0, n)
630
630
631 def qparents(self, repo, rev=None):
631 def qparents(self, repo, rev=None):
632 if rev is None:
632 if rev is None:
633 (p1, p2) = repo.dirstate.parents()
633 (p1, p2) = repo.dirstate.parents()
634 if p2 == nullid:
634 if p2 == nullid:
635 return p1
635 return p1
636 if not self.applied:
636 if not self.applied:
637 return None
637 return None
638 return self.applied[-1].node
638 return self.applied[-1].node
639 p1, p2 = repo.changelog.parents(rev)
639 p1, p2 = repo.changelog.parents(rev)
640 if p2 != nullid and p2 in [x.node for x in self.applied]:
640 if p2 != nullid and p2 in [x.node for x in self.applied]:
641 return p2
641 return p2
642 return p1
642 return p1
643
643
644 def mergepatch(self, repo, mergeq, series, diffopts):
644 def mergepatch(self, repo, mergeq, series, diffopts):
645 if not self.applied:
645 if not self.applied:
646 # each of the patches merged in will have two parents. This
646 # each of the patches merged in will have two parents. This
647 # can confuse the qrefresh, qdiff, and strip code because it
647 # can confuse the qrefresh, qdiff, and strip code because it
648 # needs to know which parent is actually in the patch queue.
648 # needs to know which parent is actually in the patch queue.
649 # so, we insert a merge marker with only one parent. This way
649 # so, we insert a merge marker with only one parent. This way
650 # the first patch in the queue is never a merge patch
650 # the first patch in the queue is never a merge patch
651 #
651 #
652 pname = ".hg.patches.merge.marker"
652 pname = ".hg.patches.merge.marker"
653 n = newcommit(repo, None, '[mq]: merge marker', force=True)
653 n = newcommit(repo, None, '[mq]: merge marker', force=True)
654 self.removeundo(repo)
654 self.removeundo(repo)
655 self.applied.append(statusentry(n, pname))
655 self.applied.append(statusentry(n, pname))
656 self.applieddirty = True
656 self.applieddirty = True
657
657
658 head = self.qparents(repo)
658 head = self.qparents(repo)
659
659
660 for patch in series:
660 for patch in series:
661 patch = mergeq.lookup(patch, strict=True)
661 patch = mergeq.lookup(patch, strict=True)
662 if not patch:
662 if not patch:
663 self.ui.warn(_("patch %s does not exist\n") % patch)
663 self.ui.warn(_("patch %s does not exist\n") % patch)
664 return (1, None)
664 return (1, None)
665 pushable, reason = self.pushable(patch)
665 pushable, reason = self.pushable(patch)
666 if not pushable:
666 if not pushable:
667 self.explainpushable(patch, all_patches=True)
667 self.explainpushable(patch, all_patches=True)
668 continue
668 continue
669 info = mergeq.isapplied(patch)
669 info = mergeq.isapplied(patch)
670 if not info:
670 if not info:
671 self.ui.warn(_("patch %s is not applied\n") % patch)
671 self.ui.warn(_("patch %s is not applied\n") % patch)
672 return (1, None)
672 return (1, None)
673 rev = info[1]
673 rev = info[1]
674 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
674 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
675 if head:
675 if head:
676 self.applied.append(statusentry(head, patch))
676 self.applied.append(statusentry(head, patch))
677 self.applieddirty = True
677 self.applieddirty = True
678 if err:
678 if err:
679 return (err, head)
679 return (err, head)
680 self.savedirty()
680 self.savedirty()
681 return (0, head)
681 return (0, head)
682
682
683 def patch(self, repo, patchfile):
683 def patch(self, repo, patchfile):
684 '''Apply patchfile to the working directory.
684 '''Apply patchfile to the working directory.
685 patchfile: name of patch file'''
685 patchfile: name of patch file'''
686 files = set()
686 files = set()
687 try:
687 try:
688 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
688 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
689 files=files, eolmode=None)
689 files=files, eolmode=None)
690 return (True, list(files), fuzz)
690 return (True, list(files), fuzz)
691 except Exception, inst:
691 except Exception, inst:
692 self.ui.note(str(inst) + '\n')
692 self.ui.note(str(inst) + '\n')
693 if not self.ui.verbose:
693 if not self.ui.verbose:
694 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
694 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
695 self.ui.traceback()
695 self.ui.traceback()
696 return (False, list(files), False)
696 return (False, list(files), False)
697
697
698 def apply(self, repo, series, list=False, update_status=True,
698 def apply(self, repo, series, list=False, update_status=True,
699 strict=False, patchdir=None, merge=None, all_files=None,
699 strict=False, patchdir=None, merge=None, all_files=None,
700 tobackup=None, keepchanges=False):
700 tobackup=None, keepchanges=False):
701 wlock = lock = tr = None
701 wlock = lock = tr = None
702 try:
702 try:
703 wlock = repo.wlock()
703 wlock = repo.wlock()
704 lock = repo.lock()
704 lock = repo.lock()
705 tr = repo.transaction("qpush")
705 tr = repo.transaction("qpush")
706 try:
706 try:
707 ret = self._apply(repo, series, list, update_status,
707 ret = self._apply(repo, series, list, update_status,
708 strict, patchdir, merge, all_files=all_files,
708 strict, patchdir, merge, all_files=all_files,
709 tobackup=tobackup, keepchanges=keepchanges)
709 tobackup=tobackup, keepchanges=keepchanges)
710 tr.close()
710 tr.close()
711 self.savedirty()
711 self.savedirty()
712 return ret
712 return ret
713 except AbortNoCleanup:
713 except AbortNoCleanup:
714 tr.close()
714 tr.close()
715 self.savedirty()
715 self.savedirty()
716 return 2, repo.dirstate.p1()
716 return 2, repo.dirstate.p1()
717 except: # re-raises
717 except: # re-raises
718 try:
718 try:
719 tr.abort()
719 tr.abort()
720 finally:
720 finally:
721 repo.invalidate()
721 repo.invalidate()
722 repo.dirstate.invalidate()
722 repo.dirstate.invalidate()
723 self.invalidate()
723 self.invalidate()
724 raise
724 raise
725 finally:
725 finally:
726 release(tr, lock, wlock)
726 release(tr, lock, wlock)
727 self.removeundo(repo)
727 self.removeundo(repo)
728
728
729 def _apply(self, repo, series, list=False, update_status=True,
729 def _apply(self, repo, series, list=False, update_status=True,
730 strict=False, patchdir=None, merge=None, all_files=None,
730 strict=False, patchdir=None, merge=None, all_files=None,
731 tobackup=None, keepchanges=False):
731 tobackup=None, keepchanges=False):
732 """returns (error, hash)
732 """returns (error, hash)
733
733
734 error = 1 for unable to read, 2 for patch failed, 3 for patch
734 error = 1 for unable to read, 2 for patch failed, 3 for patch
735 fuzz. tobackup is None or a set of files to backup before they
735 fuzz. tobackup is None or a set of files to backup before they
736 are modified by a patch.
736 are modified by a patch.
737 """
737 """
738 # TODO unify with commands.py
738 # TODO unify with commands.py
739 if not patchdir:
739 if not patchdir:
740 patchdir = self.path
740 patchdir = self.path
741 err = 0
741 err = 0
742 n = None
742 n = None
743 for patchname in series:
743 for patchname in series:
744 pushable, reason = self.pushable(patchname)
744 pushable, reason = self.pushable(patchname)
745 if not pushable:
745 if not pushable:
746 self.explainpushable(patchname, all_patches=True)
746 self.explainpushable(patchname, all_patches=True)
747 continue
747 continue
748 self.ui.status(_("applying %s\n") % patchname)
748 self.ui.status(_("applying %s\n") % patchname)
749 pf = os.path.join(patchdir, patchname)
749 pf = os.path.join(patchdir, patchname)
750
750
751 try:
751 try:
752 ph = patchheader(self.join(patchname), self.plainmode)
752 ph = patchheader(self.join(patchname), self.plainmode)
753 except IOError:
753 except IOError:
754 self.ui.warn(_("unable to read %s\n") % patchname)
754 self.ui.warn(_("unable to read %s\n") % patchname)
755 err = 1
755 err = 1
756 break
756 break
757
757
758 message = ph.message
758 message = ph.message
759 if not message:
759 if not message:
760 # The commit message should not be translated
760 # The commit message should not be translated
761 message = "imported patch %s\n" % patchname
761 message = "imported patch %s\n" % patchname
762 else:
762 else:
763 if list:
763 if list:
764 # The commit message should not be translated
764 # The commit message should not be translated
765 message.append("\nimported patch %s" % patchname)
765 message.append("\nimported patch %s" % patchname)
766 message = '\n'.join(message)
766 message = '\n'.join(message)
767
767
768 if ph.haspatch:
768 if ph.haspatch:
769 if tobackup:
769 if tobackup:
770 touched = patchmod.changedfiles(self.ui, repo, pf)
770 touched = patchmod.changedfiles(self.ui, repo, pf)
771 touched = set(touched) & tobackup
771 touched = set(touched) & tobackup
772 if touched and keepchanges:
772 if touched and keepchanges:
773 raise AbortNoCleanup(
773 raise AbortNoCleanup(
774 _("local changes found, refresh first"))
774 _("local changes found, refresh first"))
775 self.backup(repo, touched, copy=True)
775 self.backup(repo, touched, copy=True)
776 tobackup = tobackup - touched
776 tobackup = tobackup - touched
777 (patcherr, files, fuzz) = self.patch(repo, pf)
777 (patcherr, files, fuzz) = self.patch(repo, pf)
778 if all_files is not None:
778 if all_files is not None:
779 all_files.update(files)
779 all_files.update(files)
780 patcherr = not patcherr
780 patcherr = not patcherr
781 else:
781 else:
782 self.ui.warn(_("patch %s is empty\n") % patchname)
782 self.ui.warn(_("patch %s is empty\n") % patchname)
783 patcherr, files, fuzz = 0, [], 0
783 patcherr, files, fuzz = 0, [], 0
784
784
785 if merge and files:
785 if merge and files:
786 # Mark as removed/merged and update dirstate parent info
786 # Mark as removed/merged and update dirstate parent info
787 removed = []
787 removed = []
788 merged = []
788 merged = []
789 for f in files:
789 for f in files:
790 if os.path.lexists(repo.wjoin(f)):
790 if os.path.lexists(repo.wjoin(f)):
791 merged.append(f)
791 merged.append(f)
792 else:
792 else:
793 removed.append(f)
793 removed.append(f)
794 for f in removed:
794 for f in removed:
795 repo.dirstate.remove(f)
795 repo.dirstate.remove(f)
796 for f in merged:
796 for f in merged:
797 repo.dirstate.merge(f)
797 repo.dirstate.merge(f)
798 p1, p2 = repo.dirstate.parents()
798 p1, p2 = repo.dirstate.parents()
799 repo.setparents(p1, merge)
799 repo.setparents(p1, merge)
800
800
801 match = scmutil.matchfiles(repo, files or [])
801 match = scmutil.matchfiles(repo, files or [])
802 oldtip = repo['tip']
802 oldtip = repo['tip']
803 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
803 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
804 force=True)
804 force=True)
805 if repo['tip'] == oldtip:
805 if repo['tip'] == oldtip:
806 raise util.Abort(_("qpush exactly duplicates child changeset"))
806 raise util.Abort(_("qpush exactly duplicates child changeset"))
807 if n is None:
807 if n is None:
808 raise util.Abort(_("repository commit failed"))
808 raise util.Abort(_("repository commit failed"))
809
809
810 if update_status:
810 if update_status:
811 self.applied.append(statusentry(n, patchname))
811 self.applied.append(statusentry(n, patchname))
812
812
813 if patcherr:
813 if patcherr:
814 self.ui.warn(_("patch failed, rejects left in working dir\n"))
814 self.ui.warn(_("patch failed, rejects left in working dir\n"))
815 err = 2
815 err = 2
816 break
816 break
817
817
818 if fuzz and strict:
818 if fuzz and strict:
819 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
819 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
820 err = 3
820 err = 3
821 break
821 break
822 return (err, n)
822 return (err, n)
823
823
824 def _cleanup(self, patches, numrevs, keep=False):
824 def _cleanup(self, patches, numrevs, keep=False):
825 if not keep:
825 if not keep:
826 r = self.qrepo()
826 r = self.qrepo()
827 if r:
827 if r:
828 r[None].forget(patches)
828 r[None].forget(patches)
829 for p in patches:
829 for p in patches:
830 try:
830 try:
831 os.unlink(self.join(p))
831 os.unlink(self.join(p))
832 except OSError, inst:
832 except OSError, inst:
833 if inst.errno != errno.ENOENT:
833 if inst.errno != errno.ENOENT:
834 raise
834 raise
835
835
836 qfinished = []
836 qfinished = []
837 if numrevs:
837 if numrevs:
838 qfinished = self.applied[:numrevs]
838 qfinished = self.applied[:numrevs]
839 del self.applied[:numrevs]
839 del self.applied[:numrevs]
840 self.applieddirty = True
840 self.applieddirty = True
841
841
842 unknown = []
842 unknown = []
843
843
844 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
844 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
845 reverse=True):
845 reverse=True):
846 if i is not None:
846 if i is not None:
847 del self.fullseries[i]
847 del self.fullseries[i]
848 else:
848 else:
849 unknown.append(p)
849 unknown.append(p)
850
850
851 if unknown:
851 if unknown:
852 if numrevs:
852 if numrevs:
853 rev = dict((entry.name, entry.node) for entry in qfinished)
853 rev = dict((entry.name, entry.node) for entry in qfinished)
854 for p in unknown:
854 for p in unknown:
855 msg = _('revision %s refers to unknown patches: %s\n')
855 msg = _('revision %s refers to unknown patches: %s\n')
856 self.ui.warn(msg % (short(rev[p]), p))
856 self.ui.warn(msg % (short(rev[p]), p))
857 else:
857 else:
858 msg = _('unknown patches: %s\n')
858 msg = _('unknown patches: %s\n')
859 raise util.Abort(''.join(msg % p for p in unknown))
859 raise util.Abort(''.join(msg % p for p in unknown))
860
860
861 self.parseseries()
861 self.parseseries()
862 self.seriesdirty = True
862 self.seriesdirty = True
863 return [entry.node for entry in qfinished]
863 return [entry.node for entry in qfinished]
864
864
865 def _revpatches(self, repo, revs):
865 def _revpatches(self, repo, revs):
866 firstrev = repo[self.applied[0].node].rev()
866 firstrev = repo[self.applied[0].node].rev()
867 patches = []
867 patches = []
868 for i, rev in enumerate(revs):
868 for i, rev in enumerate(revs):
869
869
870 if rev < firstrev:
870 if rev < firstrev:
871 raise util.Abort(_('revision %d is not managed') % rev)
871 raise util.Abort(_('revision %d is not managed') % rev)
872
872
873 ctx = repo[rev]
873 ctx = repo[rev]
874 base = self.applied[i].node
874 base = self.applied[i].node
875 if ctx.node() != base:
875 if ctx.node() != base:
876 msg = _('cannot delete revision %d above applied patches')
876 msg = _('cannot delete revision %d above applied patches')
877 raise util.Abort(msg % rev)
877 raise util.Abort(msg % rev)
878
878
879 patch = self.applied[i].name
879 patch = self.applied[i].name
880 for fmt in ('[mq]: %s', 'imported patch %s'):
880 for fmt in ('[mq]: %s', 'imported patch %s'):
881 if ctx.description() == fmt % patch:
881 if ctx.description() == fmt % patch:
882 msg = _('patch %s finalized without changeset message\n')
882 msg = _('patch %s finalized without changeset message\n')
883 repo.ui.status(msg % patch)
883 repo.ui.status(msg % patch)
884 break
884 break
885
885
886 patches.append(patch)
886 patches.append(patch)
887 return patches
887 return patches
888
888
889 def finish(self, repo, revs):
889 def finish(self, repo, revs):
890 # Manually trigger phase computation to ensure phasedefaults is
890 # Manually trigger phase computation to ensure phasedefaults is
891 # executed before we remove the patches.
891 # executed before we remove the patches.
892 repo._phasecache
892 repo._phasecache
893 patches = self._revpatches(repo, sorted(revs))
893 patches = self._revpatches(repo, sorted(revs))
894 qfinished = self._cleanup(patches, len(patches))
894 qfinished = self._cleanup(patches, len(patches))
895 if qfinished and repo.ui.configbool('mq', 'secret', False):
895 if qfinished and repo.ui.configbool('mq', 'secret', False):
896 # only use this logic when the secret option is added
896 # only use this logic when the secret option is added
897 oldqbase = repo[qfinished[0]]
897 oldqbase = repo[qfinished[0]]
898 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
898 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
899 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
899 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
900 phases.advanceboundary(repo, tphase, qfinished)
900 phases.advanceboundary(repo, tphase, qfinished)
901
901
902 def delete(self, repo, patches, opts):
902 def delete(self, repo, patches, opts):
903 if not patches and not opts.get('rev'):
903 if not patches and not opts.get('rev'):
904 raise util.Abort(_('qdelete requires at least one revision or '
904 raise util.Abort(_('qdelete requires at least one revision or '
905 'patch name'))
905 'patch name'))
906
906
907 realpatches = []
907 realpatches = []
908 for patch in patches:
908 for patch in patches:
909 patch = self.lookup(patch, strict=True)
909 patch = self.lookup(patch, strict=True)
910 info = self.isapplied(patch)
910 info = self.isapplied(patch)
911 if info:
911 if info:
912 raise util.Abort(_("cannot delete applied patch %s") % patch)
912 raise util.Abort(_("cannot delete applied patch %s") % patch)
913 if patch not in self.series:
913 if patch not in self.series:
914 raise util.Abort(_("patch %s not in series file") % patch)
914 raise util.Abort(_("patch %s not in series file") % patch)
915 if patch not in realpatches:
915 if patch not in realpatches:
916 realpatches.append(patch)
916 realpatches.append(patch)
917
917
918 numrevs = 0
918 numrevs = 0
919 if opts.get('rev'):
919 if opts.get('rev'):
920 if not self.applied:
920 if not self.applied:
921 raise util.Abort(_('no patches applied'))
921 raise util.Abort(_('no patches applied'))
922 revs = scmutil.revrange(repo, opts.get('rev'))
922 revs = scmutil.revrange(repo, opts.get('rev'))
923 if len(revs) > 1 and revs[0] > revs[1]:
923 if len(revs) > 1 and revs[0] > revs[1]:
924 revs.reverse()
924 revs.reverse()
925 revpatches = self._revpatches(repo, revs)
925 revpatches = self._revpatches(repo, revs)
926 realpatches += revpatches
926 realpatches += revpatches
927 numrevs = len(revpatches)
927 numrevs = len(revpatches)
928
928
929 self._cleanup(realpatches, numrevs, opts.get('keep'))
929 self._cleanup(realpatches, numrevs, opts.get('keep'))
930
930
931 def checktoppatch(self, repo):
931 def checktoppatch(self, repo):
932 if self.applied:
932 if self.applied:
933 top = self.applied[-1].node
933 top = self.applied[-1].node
934 patch = self.applied[-1].name
934 patch = self.applied[-1].name
935 pp = repo.dirstate.parents()
935 pp = repo.dirstate.parents()
936 if top not in pp:
936 if top not in pp:
937 raise util.Abort(_("working directory revision is not qtip"))
937 raise util.Abort(_("working directory revision is not qtip"))
938 return top, patch
938 return top, patch
939 return None, None
939 return None, None
940
940
941 def checksubstate(self, repo, baserev=None):
941 def checksubstate(self, repo, baserev=None):
942 '''return list of subrepos at a different revision than substate.
942 '''return list of subrepos at a different revision than substate.
943 Abort if any subrepos have uncommitted changes.'''
943 Abort if any subrepos have uncommitted changes.'''
944 inclsubs = []
944 inclsubs = []
945 wctx = repo[None]
945 wctx = repo[None]
946 if baserev:
946 if baserev:
947 bctx = repo[baserev]
947 bctx = repo[baserev]
948 else:
948 else:
949 bctx = wctx.parents()[0]
949 bctx = wctx.parents()[0]
950 for s in wctx.substate:
950 for s in wctx.substate:
951 if wctx.sub(s).dirty(True):
951 if wctx.sub(s).dirty(True):
952 raise util.Abort(
952 raise util.Abort(
953 _("uncommitted changes in subrepository %s") % s)
953 _("uncommitted changes in subrepository %s") % s)
954 elif s not in bctx.substate or bctx.sub(s).dirty():
954 elif s not in bctx.substate or bctx.sub(s).dirty():
955 inclsubs.append(s)
955 inclsubs.append(s)
956 return inclsubs
956 return inclsubs
957
957
958 def putsubstate2changes(self, substatestate, changes):
958 def putsubstate2changes(self, substatestate, changes):
959 for files in changes[:3]:
959 for files in changes[:3]:
960 if '.hgsubstate' in files:
960 if '.hgsubstate' in files:
961 return # already listed up
961 return # already listed up
962 # not yet listed up
962 # not yet listed up
963 if substatestate in 'a?':
963 if substatestate in 'a?':
964 changes[1].append('.hgsubstate')
964 changes[1].append('.hgsubstate')
965 elif substatestate in 'r':
965 elif substatestate in 'r':
966 changes[2].append('.hgsubstate')
966 changes[2].append('.hgsubstate')
967 else: # modified
967 else: # modified
968 changes[0].append('.hgsubstate')
968 changes[0].append('.hgsubstate')
969
969
970 def localchangesfound(self, refresh=True):
970 def localchangesfound(self, refresh=True):
971 if refresh:
971 if refresh:
972 raise util.Abort(_("local changes found, refresh first"))
972 raise util.Abort(_("local changes found, refresh first"))
973 else:
973 else:
974 raise util.Abort(_("local changes found"))
974 raise util.Abort(_("local changes found"))
975
975
976 def checklocalchanges(self, repo, force=False, refresh=True):
976 def checklocalchanges(self, repo, force=False, refresh=True):
977 m, a, r, d = repo.status()[:4]
977 m, a, r, d = repo.status()[:4]
978 if (m or a or r or d) and not force:
978 if (m or a or r or d) and not force:
979 self.localchangesfound(refresh)
979 self.localchangesfound(refresh)
980 return m, a, r, d
980 return m, a, r, d
981
981
982 _reserved = ('series', 'status', 'guards', '.', '..')
982 _reserved = ('series', 'status', 'guards', '.', '..')
983 def checkreservedname(self, name):
983 def checkreservedname(self, name):
984 if name in self._reserved:
984 if name in self._reserved:
985 raise util.Abort(_('"%s" cannot be used as the name of a patch')
985 raise util.Abort(_('"%s" cannot be used as the name of a patch')
986 % name)
986 % name)
987 for prefix in ('.hg', '.mq'):
987 for prefix in ('.hg', '.mq'):
988 if name.startswith(prefix):
988 if name.startswith(prefix):
989 raise util.Abort(_('patch name cannot begin with "%s"')
989 raise util.Abort(_('patch name cannot begin with "%s"')
990 % prefix)
990 % prefix)
991 for c in ('#', ':'):
991 for c in ('#', ':'):
992 if c in name:
992 if c in name:
993 raise util.Abort(_('"%s" cannot be used in the name of a patch')
993 raise util.Abort(_('"%s" cannot be used in the name of a patch')
994 % c)
994 % c)
995
995
996 def checkpatchname(self, name, force=False):
996 def checkpatchname(self, name, force=False):
997 self.checkreservedname(name)
997 self.checkreservedname(name)
998 if not force and os.path.exists(self.join(name)):
998 if not force and os.path.exists(self.join(name)):
999 if os.path.isdir(self.join(name)):
999 if os.path.isdir(self.join(name)):
1000 raise util.Abort(_('"%s" already exists as a directory')
1000 raise util.Abort(_('"%s" already exists as a directory')
1001 % name)
1001 % name)
1002 else:
1002 else:
1003 raise util.Abort(_('patch "%s" already exists') % name)
1003 raise util.Abort(_('patch "%s" already exists') % name)
1004
1004
1005 def checkkeepchanges(self, keepchanges, force):
1005 def checkkeepchanges(self, keepchanges, force):
1006 if force and keepchanges:
1006 if force and keepchanges:
1007 raise util.Abort(_('cannot use both --force and --keep-changes'))
1007 raise util.Abort(_('cannot use both --force and --keep-changes'))
1008
1008
1009 def new(self, repo, patchfn, *pats, **opts):
1009 def new(self, repo, patchfn, *pats, **opts):
1010 """options:
1010 """options:
1011 msg: a string or a no-argument function returning a string
1011 msg: a string or a no-argument function returning a string
1012 """
1012 """
1013 msg = opts.get('msg')
1013 msg = opts.get('msg')
1014 user = opts.get('user')
1014 user = opts.get('user')
1015 date = opts.get('date')
1015 date = opts.get('date')
1016 if date:
1016 if date:
1017 date = util.parsedate(date)
1017 date = util.parsedate(date)
1018 diffopts = self.diffopts({'git': opts.get('git')})
1018 diffopts = self.diffopts({'git': opts.get('git')})
1019 if opts.get('checkname', True):
1019 if opts.get('checkname', True):
1020 self.checkpatchname(patchfn)
1020 self.checkpatchname(patchfn)
1021 inclsubs = self.checksubstate(repo)
1021 inclsubs = self.checksubstate(repo)
1022 if inclsubs:
1022 if inclsubs:
1023 inclsubs.append('.hgsubstate')
1023 inclsubs.append('.hgsubstate')
1024 substatestate = repo.dirstate['.hgsubstate']
1024 substatestate = repo.dirstate['.hgsubstate']
1025 if opts.get('include') or opts.get('exclude') or pats:
1025 if opts.get('include') or opts.get('exclude') or pats:
1026 if inclsubs:
1026 if inclsubs:
1027 pats = list(pats or []) + inclsubs
1027 pats = list(pats or []) + inclsubs
1028 match = scmutil.match(repo[None], pats, opts)
1028 match = scmutil.match(repo[None], pats, opts)
1029 # detect missing files in pats
1029 # detect missing files in pats
1030 def badfn(f, msg):
1030 def badfn(f, msg):
1031 if f != '.hgsubstate': # .hgsubstate is auto-created
1031 if f != '.hgsubstate': # .hgsubstate is auto-created
1032 raise util.Abort('%s: %s' % (f, msg))
1032 raise util.Abort('%s: %s' % (f, msg))
1033 match.bad = badfn
1033 match.bad = badfn
1034 changes = repo.status(match=match)
1034 changes = repo.status(match=match)
1035 m, a, r, d = changes[:4]
1035 m, a, r, d = changes[:4]
1036 else:
1036 else:
1037 changes = self.checklocalchanges(repo, force=True)
1037 changes = self.checklocalchanges(repo, force=True)
1038 m, a, r, d = changes
1038 m, a, r, d = changes
1039 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1039 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1040 if len(repo[None].parents()) > 1:
1040 if len(repo[None].parents()) > 1:
1041 raise util.Abort(_('cannot manage merge changesets'))
1041 raise util.Abort(_('cannot manage merge changesets'))
1042 commitfiles = m + a + r
1042 commitfiles = m + a + r
1043 self.checktoppatch(repo)
1043 self.checktoppatch(repo)
1044 insert = self.fullseriesend()
1044 insert = self.fullseriesend()
1045 wlock = repo.wlock()
1045 wlock = repo.wlock()
1046 try:
1046 try:
1047 try:
1047 try:
1048 # if patch file write fails, abort early
1048 # if patch file write fails, abort early
1049 p = self.opener(patchfn, "w")
1049 p = self.opener(patchfn, "w")
1050 except IOError, e:
1050 except IOError, e:
1051 raise util.Abort(_('cannot write patch "%s": %s')
1051 raise util.Abort(_('cannot write patch "%s": %s')
1052 % (patchfn, e.strerror))
1052 % (patchfn, e.strerror))
1053 try:
1053 try:
1054 if self.plainmode:
1054 if self.plainmode:
1055 if user:
1055 if user:
1056 p.write("From: " + user + "\n")
1056 p.write("From: " + user + "\n")
1057 if not date:
1057 if not date:
1058 p.write("\n")
1058 p.write("\n")
1059 if date:
1059 if date:
1060 p.write("Date: %d %d\n\n" % date)
1060 p.write("Date: %d %d\n\n" % date)
1061 else:
1061 else:
1062 p.write("# HG changeset patch\n")
1062 p.write("# HG changeset patch\n")
1063 p.write("# Parent "
1063 p.write("# Parent "
1064 + hex(repo[None].p1().node()) + "\n")
1064 + hex(repo[None].p1().node()) + "\n")
1065 if user:
1065 if user:
1066 p.write("# User " + user + "\n")
1066 p.write("# User " + user + "\n")
1067 if date:
1067 if date:
1068 p.write("# Date %s %s\n\n" % date)
1068 p.write("# Date %s %s\n\n" % date)
1069 if util.safehasattr(msg, '__call__'):
1069 if util.safehasattr(msg, '__call__'):
1070 msg = msg()
1070 msg = msg()
1071 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1071 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1072 n = newcommit(repo, None, commitmsg, user, date, match=match,
1072 n = newcommit(repo, None, commitmsg, user, date, match=match,
1073 force=True)
1073 force=True)
1074 if n is None:
1074 if n is None:
1075 raise util.Abort(_("repo commit failed"))
1075 raise util.Abort(_("repo commit failed"))
1076 try:
1076 try:
1077 self.fullseries[insert:insert] = [patchfn]
1077 self.fullseries[insert:insert] = [patchfn]
1078 self.applied.append(statusentry(n, patchfn))
1078 self.applied.append(statusentry(n, patchfn))
1079 self.parseseries()
1079 self.parseseries()
1080 self.seriesdirty = True
1080 self.seriesdirty = True
1081 self.applieddirty = True
1081 self.applieddirty = True
1082 if msg:
1082 if msg:
1083 msg = msg + "\n\n"
1083 msg = msg + "\n\n"
1084 p.write(msg)
1084 p.write(msg)
1085 if commitfiles:
1085 if commitfiles:
1086 parent = self.qparents(repo, n)
1086 parent = self.qparents(repo, n)
1087 if inclsubs:
1087 if inclsubs:
1088 self.putsubstate2changes(substatestate, changes)
1088 self.putsubstate2changes(substatestate, changes)
1089 chunks = patchmod.diff(repo, node1=parent, node2=n,
1089 chunks = patchmod.diff(repo, node1=parent, node2=n,
1090 changes=changes, opts=diffopts)
1090 changes=changes, opts=diffopts)
1091 for chunk in chunks:
1091 for chunk in chunks:
1092 p.write(chunk)
1092 p.write(chunk)
1093 p.close()
1093 p.close()
1094 r = self.qrepo()
1094 r = self.qrepo()
1095 if r:
1095 if r:
1096 r[None].add([patchfn])
1096 r[None].add([patchfn])
1097 except: # re-raises
1097 except: # re-raises
1098 repo.rollback()
1098 repo.rollback()
1099 raise
1099 raise
1100 except Exception:
1100 except Exception:
1101 patchpath = self.join(patchfn)
1101 patchpath = self.join(patchfn)
1102 try:
1102 try:
1103 os.unlink(patchpath)
1103 os.unlink(patchpath)
1104 except OSError:
1104 except OSError:
1105 self.ui.warn(_('error unlinking %s\n') % patchpath)
1105 self.ui.warn(_('error unlinking %s\n') % patchpath)
1106 raise
1106 raise
1107 self.removeundo(repo)
1107 self.removeundo(repo)
1108 finally:
1108 finally:
1109 release(wlock)
1109 release(wlock)
1110
1110
1111 def strip(self, repo, revs, update=True, backup="all", force=None):
1111 def strip(self, repo, revs, update=True, backup="all", force=None):
1112 wlock = lock = None
1112 wlock = lock = None
1113 try:
1113 try:
1114 wlock = repo.wlock()
1114 wlock = repo.wlock()
1115 lock = repo.lock()
1115 lock = repo.lock()
1116
1116
1117 if update:
1117 if update:
1118 self.checklocalchanges(repo, force=force, refresh=False)
1118 self.checklocalchanges(repo, force=force, refresh=False)
1119 urev = self.qparents(repo, revs[0])
1119 urev = self.qparents(repo, revs[0])
1120 hg.clean(repo, urev)
1120 hg.clean(repo, urev)
1121 repo.dirstate.write()
1121 repo.dirstate.write()
1122
1122
1123 repair.strip(self.ui, repo, revs, backup)
1123 repair.strip(self.ui, repo, revs, backup)
1124 finally:
1124 finally:
1125 release(lock, wlock)
1125 release(lock, wlock)
1126
1126
1127 def isapplied(self, patch):
1127 def isapplied(self, patch):
1128 """returns (index, rev, patch)"""
1128 """returns (index, rev, patch)"""
1129 for i, a in enumerate(self.applied):
1129 for i, a in enumerate(self.applied):
1130 if a.name == patch:
1130 if a.name == patch:
1131 return (i, a.node, a.name)
1131 return (i, a.node, a.name)
1132 return None
1132 return None
1133
1133
1134 # if the exact patch name does not exist, we try a few
1134 # if the exact patch name does not exist, we try a few
1135 # variations. If strict is passed, we try only #1
1135 # variations. If strict is passed, we try only #1
1136 #
1136 #
1137 # 1) a number (as string) to indicate an offset in the series file
1137 # 1) a number (as string) to indicate an offset in the series file
1138 # 2) a unique substring of the patch name was given
1138 # 2) a unique substring of the patch name was given
1139 # 3) patchname[-+]num to indicate an offset in the series file
1139 # 3) patchname[-+]num to indicate an offset in the series file
1140 def lookup(self, patch, strict=False):
1140 def lookup(self, patch, strict=False):
1141 def partialname(s):
1141 def partialname(s):
1142 if s in self.series:
1142 if s in self.series:
1143 return s
1143 return s
1144 matches = [x for x in self.series if s in x]
1144 matches = [x for x in self.series if s in x]
1145 if len(matches) > 1:
1145 if len(matches) > 1:
1146 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1146 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1147 for m in matches:
1147 for m in matches:
1148 self.ui.warn(' %s\n' % m)
1148 self.ui.warn(' %s\n' % m)
1149 return None
1149 return None
1150 if matches:
1150 if matches:
1151 return matches[0]
1151 return matches[0]
1152 if self.series and self.applied:
1152 if self.series and self.applied:
1153 if s == 'qtip':
1153 if s == 'qtip':
1154 return self.series[self.seriesend(True) - 1]
1154 return self.series[self.seriesend(True) - 1]
1155 if s == 'qbase':
1155 if s == 'qbase':
1156 return self.series[0]
1156 return self.series[0]
1157 return None
1157 return None
1158
1158
1159 if patch in self.series:
1159 if patch in self.series:
1160 return patch
1160 return patch
1161
1161
1162 if not os.path.isfile(self.join(patch)):
1162 if not os.path.isfile(self.join(patch)):
1163 try:
1163 try:
1164 sno = int(patch)
1164 sno = int(patch)
1165 except (ValueError, OverflowError):
1165 except (ValueError, OverflowError):
1166 pass
1166 pass
1167 else:
1167 else:
1168 if -len(self.series) <= sno < len(self.series):
1168 if -len(self.series) <= sno < len(self.series):
1169 return self.series[sno]
1169 return self.series[sno]
1170
1170
1171 if not strict:
1171 if not strict:
1172 res = partialname(patch)
1172 res = partialname(patch)
1173 if res:
1173 if res:
1174 return res
1174 return res
1175 minus = patch.rfind('-')
1175 minus = patch.rfind('-')
1176 if minus >= 0:
1176 if minus >= 0:
1177 res = partialname(patch[:minus])
1177 res = partialname(patch[:minus])
1178 if res:
1178 if res:
1179 i = self.series.index(res)
1179 i = self.series.index(res)
1180 try:
1180 try:
1181 off = int(patch[minus + 1:] or 1)
1181 off = int(patch[minus + 1:] or 1)
1182 except (ValueError, OverflowError):
1182 except (ValueError, OverflowError):
1183 pass
1183 pass
1184 else:
1184 else:
1185 if i - off >= 0:
1185 if i - off >= 0:
1186 return self.series[i - off]
1186 return self.series[i - off]
1187 plus = patch.rfind('+')
1187 plus = patch.rfind('+')
1188 if plus >= 0:
1188 if plus >= 0:
1189 res = partialname(patch[:plus])
1189 res = partialname(patch[:plus])
1190 if res:
1190 if res:
1191 i = self.series.index(res)
1191 i = self.series.index(res)
1192 try:
1192 try:
1193 off = int(patch[plus + 1:] or 1)
1193 off = int(patch[plus + 1:] or 1)
1194 except (ValueError, OverflowError):
1194 except (ValueError, OverflowError):
1195 pass
1195 pass
1196 else:
1196 else:
1197 if i + off < len(self.series):
1197 if i + off < len(self.series):
1198 return self.series[i + off]
1198 return self.series[i + off]
1199 raise util.Abort(_("patch %s not in series") % patch)
1199 raise util.Abort(_("patch %s not in series") % patch)
1200
1200
1201 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1201 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1202 all=False, move=False, exact=False, nobackup=False,
1202 all=False, move=False, exact=False, nobackup=False,
1203 keepchanges=False):
1203 keepchanges=False):
1204 self.checkkeepchanges(keepchanges, force)
1204 self.checkkeepchanges(keepchanges, force)
1205 diffopts = self.diffopts()
1205 diffopts = self.diffopts()
1206 wlock = repo.wlock()
1206 wlock = repo.wlock()
1207 try:
1207 try:
1208 heads = []
1208 heads = []
1209 for b, ls in repo.branchmap().iteritems():
1209 for b, ls in repo.branchmap().iteritems():
1210 heads += ls
1210 heads += ls
1211 if not heads:
1211 if not heads:
1212 heads = [nullid]
1212 heads = [nullid]
1213 if repo.dirstate.p1() not in heads and not exact:
1213 if repo.dirstate.p1() not in heads and not exact:
1214 self.ui.status(_("(working directory not at a head)\n"))
1214 self.ui.status(_("(working directory not at a head)\n"))
1215
1215
1216 if not self.series:
1216 if not self.series:
1217 self.ui.warn(_('no patches in series\n'))
1217 self.ui.warn(_('no patches in series\n'))
1218 return 0
1218 return 0
1219
1219
1220 # Suppose our series file is: A B C and the current 'top'
1220 # Suppose our series file is: A B C and the current 'top'
1221 # patch is B. qpush C should be performed (moving forward)
1221 # patch is B. qpush C should be performed (moving forward)
1222 # qpush B is a NOP (no change) qpush A is an error (can't
1222 # qpush B is a NOP (no change) qpush A is an error (can't
1223 # go backwards with qpush)
1223 # go backwards with qpush)
1224 if patch:
1224 if patch:
1225 patch = self.lookup(patch)
1225 patch = self.lookup(patch)
1226 info = self.isapplied(patch)
1226 info = self.isapplied(patch)
1227 if info and info[0] >= len(self.applied) - 1:
1227 if info and info[0] >= len(self.applied) - 1:
1228 self.ui.warn(
1228 self.ui.warn(
1229 _('qpush: %s is already at the top\n') % patch)
1229 _('qpush: %s is already at the top\n') % patch)
1230 return 0
1230 return 0
1231
1231
1232 pushable, reason = self.pushable(patch)
1232 pushable, reason = self.pushable(patch)
1233 if pushable:
1233 if pushable:
1234 if self.series.index(patch) < self.seriesend():
1234 if self.series.index(patch) < self.seriesend():
1235 raise util.Abort(
1235 raise util.Abort(
1236 _("cannot push to a previous patch: %s") % patch)
1236 _("cannot push to a previous patch: %s") % patch)
1237 else:
1237 else:
1238 if reason:
1238 if reason:
1239 reason = _('guarded by %s') % reason
1239 reason = _('guarded by %s') % reason
1240 else:
1240 else:
1241 reason = _('no matching guards')
1241 reason = _('no matching guards')
1242 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1242 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1243 return 1
1243 return 1
1244 elif all:
1244 elif all:
1245 patch = self.series[-1]
1245 patch = self.series[-1]
1246 if self.isapplied(patch):
1246 if self.isapplied(patch):
1247 self.ui.warn(_('all patches are currently applied\n'))
1247 self.ui.warn(_('all patches are currently applied\n'))
1248 return 0
1248 return 0
1249
1249
1250 # Following the above example, starting at 'top' of B:
1250 # Following the above example, starting at 'top' of B:
1251 # qpush should be performed (pushes C), but a subsequent
1251 # qpush should be performed (pushes C), but a subsequent
1252 # qpush without an argument is an error (nothing to
1252 # qpush without an argument is an error (nothing to
1253 # apply). This allows a loop of "...while hg qpush..." to
1253 # apply). This allows a loop of "...while hg qpush..." to
1254 # work as it detects an error when done
1254 # work as it detects an error when done
1255 start = self.seriesend()
1255 start = self.seriesend()
1256 if start == len(self.series):
1256 if start == len(self.series):
1257 self.ui.warn(_('patch series already fully applied\n'))
1257 self.ui.warn(_('patch series already fully applied\n'))
1258 return 1
1258 return 1
1259 if not force and not keepchanges:
1259 if not force and not keepchanges:
1260 self.checklocalchanges(repo, refresh=self.applied)
1260 self.checklocalchanges(repo, refresh=self.applied)
1261
1261
1262 if exact:
1262 if exact:
1263 if keepchanges:
1263 if keepchanges:
1264 raise util.Abort(
1264 raise util.Abort(
1265 _("cannot use --exact and --keep-changes together"))
1265 _("cannot use --exact and --keep-changes together"))
1266 if move:
1266 if move:
1267 raise util.Abort(_('cannot use --exact and --move '
1267 raise util.Abort(_('cannot use --exact and --move '
1268 'together'))
1268 'together'))
1269 if self.applied:
1269 if self.applied:
1270 raise util.Abort(_('cannot push --exact with applied '
1270 raise util.Abort(_('cannot push --exact with applied '
1271 'patches'))
1271 'patches'))
1272 root = self.series[start]
1272 root = self.series[start]
1273 target = patchheader(self.join(root), self.plainmode).parent
1273 target = patchheader(self.join(root), self.plainmode).parent
1274 if not target:
1274 if not target:
1275 raise util.Abort(
1275 raise util.Abort(
1276 _("%s does not have a parent recorded") % root)
1276 _("%s does not have a parent recorded") % root)
1277 if not repo[target] == repo['.']:
1277 if not repo[target] == repo['.']:
1278 hg.update(repo, target)
1278 hg.update(repo, target)
1279
1279
1280 if move:
1280 if move:
1281 if not patch:
1281 if not patch:
1282 raise util.Abort(_("please specify the patch to move"))
1282 raise util.Abort(_("please specify the patch to move"))
1283 for fullstart, rpn in enumerate(self.fullseries):
1283 for fullstart, rpn in enumerate(self.fullseries):
1284 # strip markers for patch guards
1284 # strip markers for patch guards
1285 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1285 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1286 break
1286 break
1287 for i, rpn in enumerate(self.fullseries[fullstart:]):
1287 for i, rpn in enumerate(self.fullseries[fullstart:]):
1288 # strip markers for patch guards
1288 # strip markers for patch guards
1289 if self.guard_re.split(rpn, 1)[0] == patch:
1289 if self.guard_re.split(rpn, 1)[0] == patch:
1290 break
1290 break
1291 index = fullstart + i
1291 index = fullstart + i
1292 assert index < len(self.fullseries)
1292 assert index < len(self.fullseries)
1293 fullpatch = self.fullseries[index]
1293 fullpatch = self.fullseries[index]
1294 del self.fullseries[index]
1294 del self.fullseries[index]
1295 self.fullseries.insert(fullstart, fullpatch)
1295 self.fullseries.insert(fullstart, fullpatch)
1296 self.parseseries()
1296 self.parseseries()
1297 self.seriesdirty = True
1297 self.seriesdirty = True
1298
1298
1299 self.applieddirty = True
1299 self.applieddirty = True
1300 if start > 0:
1300 if start > 0:
1301 self.checktoppatch(repo)
1301 self.checktoppatch(repo)
1302 if not patch:
1302 if not patch:
1303 patch = self.series[start]
1303 patch = self.series[start]
1304 end = start + 1
1304 end = start + 1
1305 else:
1305 else:
1306 end = self.series.index(patch, start) + 1
1306 end = self.series.index(patch, start) + 1
1307
1307
1308 tobackup = set()
1308 tobackup = set()
1309 if (not nobackup and force) or keepchanges:
1309 if (not nobackup and force) or keepchanges:
1310 m, a, r, d = self.checklocalchanges(repo, force=True)
1310 m, a, r, d = self.checklocalchanges(repo, force=True)
1311 if keepchanges:
1311 if keepchanges:
1312 tobackup.update(m + a + r + d)
1312 tobackup.update(m + a + r + d)
1313 else:
1313 else:
1314 tobackup.update(m + a)
1314 tobackup.update(m + a)
1315
1315
1316 s = self.series[start:end]
1316 s = self.series[start:end]
1317 all_files = set()
1317 all_files = set()
1318 try:
1318 try:
1319 if mergeq:
1319 if mergeq:
1320 ret = self.mergepatch(repo, mergeq, s, diffopts)
1320 ret = self.mergepatch(repo, mergeq, s, diffopts)
1321 else:
1321 else:
1322 ret = self.apply(repo, s, list, all_files=all_files,
1322 ret = self.apply(repo, s, list, all_files=all_files,
1323 tobackup=tobackup, keepchanges=keepchanges)
1323 tobackup=tobackup, keepchanges=keepchanges)
1324 except: # re-raises
1324 except: # re-raises
1325 self.ui.warn(_('cleaning up working directory...'))
1325 self.ui.warn(_('cleaning up working directory...'))
1326 node = repo.dirstate.p1()
1326 node = repo.dirstate.p1()
1327 hg.revert(repo, node, None)
1327 hg.revert(repo, node, None)
1328 # only remove unknown files that we know we touched or
1328 # only remove unknown files that we know we touched or
1329 # created while patching
1329 # created while patching
1330 for f in all_files:
1330 for f in all_files:
1331 if f not in repo.dirstate:
1331 if f not in repo.dirstate:
1332 try:
1332 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1333 util.unlinkpath(repo.wjoin(f))
1334 except OSError, inst:
1335 if inst.errno != errno.ENOENT:
1336 raise
1337 self.ui.warn(_('done\n'))
1333 self.ui.warn(_('done\n'))
1338 raise
1334 raise
1339
1335
1340 if not self.applied:
1336 if not self.applied:
1341 return ret[0]
1337 return ret[0]
1342 top = self.applied[-1].name
1338 top = self.applied[-1].name
1343 if ret[0] and ret[0] > 1:
1339 if ret[0] and ret[0] > 1:
1344 msg = _("errors during apply, please fix and refresh %s\n")
1340 msg = _("errors during apply, please fix and refresh %s\n")
1345 self.ui.write(msg % top)
1341 self.ui.write(msg % top)
1346 else:
1342 else:
1347 self.ui.write(_("now at: %s\n") % top)
1343 self.ui.write(_("now at: %s\n") % top)
1348 return ret[0]
1344 return ret[0]
1349
1345
1350 finally:
1346 finally:
1351 wlock.release()
1347 wlock.release()
1352
1348
1353 def pop(self, repo, patch=None, force=False, update=True, all=False,
1349 def pop(self, repo, patch=None, force=False, update=True, all=False,
1354 nobackup=False, keepchanges=False):
1350 nobackup=False, keepchanges=False):
1355 self.checkkeepchanges(keepchanges, force)
1351 self.checkkeepchanges(keepchanges, force)
1356 wlock = repo.wlock()
1352 wlock = repo.wlock()
1357 try:
1353 try:
1358 if patch:
1354 if patch:
1359 # index, rev, patch
1355 # index, rev, patch
1360 info = self.isapplied(patch)
1356 info = self.isapplied(patch)
1361 if not info:
1357 if not info:
1362 patch = self.lookup(patch)
1358 patch = self.lookup(patch)
1363 info = self.isapplied(patch)
1359 info = self.isapplied(patch)
1364 if not info:
1360 if not info:
1365 raise util.Abort(_("patch %s is not applied") % patch)
1361 raise util.Abort(_("patch %s is not applied") % patch)
1366
1362
1367 if not self.applied:
1363 if not self.applied:
1368 # Allow qpop -a to work repeatedly,
1364 # Allow qpop -a to work repeatedly,
1369 # but not qpop without an argument
1365 # but not qpop without an argument
1370 self.ui.warn(_("no patches applied\n"))
1366 self.ui.warn(_("no patches applied\n"))
1371 return not all
1367 return not all
1372
1368
1373 if all:
1369 if all:
1374 start = 0
1370 start = 0
1375 elif patch:
1371 elif patch:
1376 start = info[0] + 1
1372 start = info[0] + 1
1377 else:
1373 else:
1378 start = len(self.applied) - 1
1374 start = len(self.applied) - 1
1379
1375
1380 if start >= len(self.applied):
1376 if start >= len(self.applied):
1381 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1377 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1382 return
1378 return
1383
1379
1384 if not update:
1380 if not update:
1385 parents = repo.dirstate.parents()
1381 parents = repo.dirstate.parents()
1386 rr = [x.node for x in self.applied]
1382 rr = [x.node for x in self.applied]
1387 for p in parents:
1383 for p in parents:
1388 if p in rr:
1384 if p in rr:
1389 self.ui.warn(_("qpop: forcing dirstate update\n"))
1385 self.ui.warn(_("qpop: forcing dirstate update\n"))
1390 update = True
1386 update = True
1391 else:
1387 else:
1392 parents = [p.node() for p in repo[None].parents()]
1388 parents = [p.node() for p in repo[None].parents()]
1393 needupdate = False
1389 needupdate = False
1394 for entry in self.applied[start:]:
1390 for entry in self.applied[start:]:
1395 if entry.node in parents:
1391 if entry.node in parents:
1396 needupdate = True
1392 needupdate = True
1397 break
1393 break
1398 update = needupdate
1394 update = needupdate
1399
1395
1400 tobackup = set()
1396 tobackup = set()
1401 if update:
1397 if update:
1402 m, a, r, d = self.checklocalchanges(
1398 m, a, r, d = self.checklocalchanges(
1403 repo, force=force or keepchanges)
1399 repo, force=force or keepchanges)
1404 if force:
1400 if force:
1405 if not nobackup:
1401 if not nobackup:
1406 tobackup.update(m + a)
1402 tobackup.update(m + a)
1407 elif keepchanges:
1403 elif keepchanges:
1408 tobackup.update(m + a + r + d)
1404 tobackup.update(m + a + r + d)
1409
1405
1410 self.applieddirty = True
1406 self.applieddirty = True
1411 end = len(self.applied)
1407 end = len(self.applied)
1412 rev = self.applied[start].node
1408 rev = self.applied[start].node
1413 if update:
1409 if update:
1414 top = self.checktoppatch(repo)[0]
1410 top = self.checktoppatch(repo)[0]
1415
1411
1416 try:
1412 try:
1417 heads = repo.changelog.heads(rev)
1413 heads = repo.changelog.heads(rev)
1418 except error.LookupError:
1414 except error.LookupError:
1419 node = short(rev)
1415 node = short(rev)
1420 raise util.Abort(_('trying to pop unknown node %s') % node)
1416 raise util.Abort(_('trying to pop unknown node %s') % node)
1421
1417
1422 if heads != [self.applied[-1].node]:
1418 if heads != [self.applied[-1].node]:
1423 raise util.Abort(_("popping would remove a revision not "
1419 raise util.Abort(_("popping would remove a revision not "
1424 "managed by this patch queue"))
1420 "managed by this patch queue"))
1425 if not repo[self.applied[-1].node].mutable():
1421 if not repo[self.applied[-1].node].mutable():
1426 raise util.Abort(
1422 raise util.Abort(
1427 _("popping would remove an immutable revision"),
1423 _("popping would remove an immutable revision"),
1428 hint=_('see "hg help phases" for details'))
1424 hint=_('see "hg help phases" for details'))
1429
1425
1430 # we know there are no local changes, so we can make a simplified
1426 # we know there are no local changes, so we can make a simplified
1431 # form of hg.update.
1427 # form of hg.update.
1432 if update:
1428 if update:
1433 qp = self.qparents(repo, rev)
1429 qp = self.qparents(repo, rev)
1434 ctx = repo[qp]
1430 ctx = repo[qp]
1435 m, a, r, d = repo.status(qp, top)[:4]
1431 m, a, r, d = repo.status(qp, top)[:4]
1436 if d:
1432 if d:
1437 raise util.Abort(_("deletions found between repo revs"))
1433 raise util.Abort(_("deletions found between repo revs"))
1438
1434
1439 tobackup = set(a + m + r) & tobackup
1435 tobackup = set(a + m + r) & tobackup
1440 if keepchanges and tobackup:
1436 if keepchanges and tobackup:
1441 self.localchangesfound()
1437 self.localchangesfound()
1442 self.backup(repo, tobackup)
1438 self.backup(repo, tobackup)
1443
1439
1444 for f in a:
1440 for f in a:
1445 try:
1441 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1446 util.unlinkpath(repo.wjoin(f))
1447 except OSError, e:
1448 if e.errno != errno.ENOENT:
1449 raise
1450 repo.dirstate.drop(f)
1442 repo.dirstate.drop(f)
1451 for f in m + r:
1443 for f in m + r:
1452 fctx = ctx[f]
1444 fctx = ctx[f]
1453 repo.wwrite(f, fctx.data(), fctx.flags())
1445 repo.wwrite(f, fctx.data(), fctx.flags())
1454 repo.dirstate.normal(f)
1446 repo.dirstate.normal(f)
1455 repo.setparents(qp, nullid)
1447 repo.setparents(qp, nullid)
1456 for patch in reversed(self.applied[start:end]):
1448 for patch in reversed(self.applied[start:end]):
1457 self.ui.status(_("popping %s\n") % patch.name)
1449 self.ui.status(_("popping %s\n") % patch.name)
1458 del self.applied[start:end]
1450 del self.applied[start:end]
1459 self.strip(repo, [rev], update=False, backup='strip')
1451 self.strip(repo, [rev], update=False, backup='strip')
1460 if self.applied:
1452 if self.applied:
1461 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1453 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1462 else:
1454 else:
1463 self.ui.write(_("patch queue now empty\n"))
1455 self.ui.write(_("patch queue now empty\n"))
1464 finally:
1456 finally:
1465 wlock.release()
1457 wlock.release()
1466
1458
1467 def diff(self, repo, pats, opts):
1459 def diff(self, repo, pats, opts):
1468 top, patch = self.checktoppatch(repo)
1460 top, patch = self.checktoppatch(repo)
1469 if not top:
1461 if not top:
1470 self.ui.write(_("no patches applied\n"))
1462 self.ui.write(_("no patches applied\n"))
1471 return
1463 return
1472 qp = self.qparents(repo, top)
1464 qp = self.qparents(repo, top)
1473 if opts.get('reverse'):
1465 if opts.get('reverse'):
1474 node1, node2 = None, qp
1466 node1, node2 = None, qp
1475 else:
1467 else:
1476 node1, node2 = qp, None
1468 node1, node2 = qp, None
1477 diffopts = self.diffopts(opts, patch)
1469 diffopts = self.diffopts(opts, patch)
1478 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1470 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1479
1471
1480 def refresh(self, repo, pats=None, **opts):
1472 def refresh(self, repo, pats=None, **opts):
1481 if not self.applied:
1473 if not self.applied:
1482 self.ui.write(_("no patches applied\n"))
1474 self.ui.write(_("no patches applied\n"))
1483 return 1
1475 return 1
1484 msg = opts.get('msg', '').rstrip()
1476 msg = opts.get('msg', '').rstrip()
1485 newuser = opts.get('user')
1477 newuser = opts.get('user')
1486 newdate = opts.get('date')
1478 newdate = opts.get('date')
1487 if newdate:
1479 if newdate:
1488 newdate = '%d %d' % util.parsedate(newdate)
1480 newdate = '%d %d' % util.parsedate(newdate)
1489 wlock = repo.wlock()
1481 wlock = repo.wlock()
1490
1482
1491 try:
1483 try:
1492 self.checktoppatch(repo)
1484 self.checktoppatch(repo)
1493 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1485 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1494 if repo.changelog.heads(top) != [top]:
1486 if repo.changelog.heads(top) != [top]:
1495 raise util.Abort(_("cannot refresh a revision with children"))
1487 raise util.Abort(_("cannot refresh a revision with children"))
1496 if not repo[top].mutable():
1488 if not repo[top].mutable():
1497 raise util.Abort(_("cannot refresh immutable revision"),
1489 raise util.Abort(_("cannot refresh immutable revision"),
1498 hint=_('see "hg help phases" for details'))
1490 hint=_('see "hg help phases" for details'))
1499
1491
1500 cparents = repo.changelog.parents(top)
1492 cparents = repo.changelog.parents(top)
1501 patchparent = self.qparents(repo, top)
1493 patchparent = self.qparents(repo, top)
1502
1494
1503 inclsubs = self.checksubstate(repo, hex(patchparent))
1495 inclsubs = self.checksubstate(repo, hex(patchparent))
1504 if inclsubs:
1496 if inclsubs:
1505 inclsubs.append('.hgsubstate')
1497 inclsubs.append('.hgsubstate')
1506 substatestate = repo.dirstate['.hgsubstate']
1498 substatestate = repo.dirstate['.hgsubstate']
1507
1499
1508 ph = patchheader(self.join(patchfn), self.plainmode)
1500 ph = patchheader(self.join(patchfn), self.plainmode)
1509 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1501 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1510 if msg:
1502 if msg:
1511 ph.setmessage(msg)
1503 ph.setmessage(msg)
1512 if newuser:
1504 if newuser:
1513 ph.setuser(newuser)
1505 ph.setuser(newuser)
1514 if newdate:
1506 if newdate:
1515 ph.setdate(newdate)
1507 ph.setdate(newdate)
1516 ph.setparent(hex(patchparent))
1508 ph.setparent(hex(patchparent))
1517
1509
1518 # only commit new patch when write is complete
1510 # only commit new patch when write is complete
1519 patchf = self.opener(patchfn, 'w', atomictemp=True)
1511 patchf = self.opener(patchfn, 'w', atomictemp=True)
1520
1512
1521 comments = str(ph)
1513 comments = str(ph)
1522 if comments:
1514 if comments:
1523 patchf.write(comments)
1515 patchf.write(comments)
1524
1516
1525 # update the dirstate in place, strip off the qtip commit
1517 # update the dirstate in place, strip off the qtip commit
1526 # and then commit.
1518 # and then commit.
1527 #
1519 #
1528 # this should really read:
1520 # this should really read:
1529 # mm, dd, aa = repo.status(top, patchparent)[:3]
1521 # mm, dd, aa = repo.status(top, patchparent)[:3]
1530 # but we do it backwards to take advantage of manifest/changelog
1522 # but we do it backwards to take advantage of manifest/changelog
1531 # caching against the next repo.status call
1523 # caching against the next repo.status call
1532 mm, aa, dd = repo.status(patchparent, top)[:3]
1524 mm, aa, dd = repo.status(patchparent, top)[:3]
1533 changes = repo.changelog.read(top)
1525 changes = repo.changelog.read(top)
1534 man = repo.manifest.read(changes[0])
1526 man = repo.manifest.read(changes[0])
1535 aaa = aa[:]
1527 aaa = aa[:]
1536 matchfn = scmutil.match(repo[None], pats, opts)
1528 matchfn = scmutil.match(repo[None], pats, opts)
1537 # in short mode, we only diff the files included in the
1529 # in short mode, we only diff the files included in the
1538 # patch already plus specified files
1530 # patch already plus specified files
1539 if opts.get('short'):
1531 if opts.get('short'):
1540 # if amending a patch, we start with existing
1532 # if amending a patch, we start with existing
1541 # files plus specified files - unfiltered
1533 # files plus specified files - unfiltered
1542 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1534 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1543 # filter with include/exclude options
1535 # filter with include/exclude options
1544 matchfn = scmutil.match(repo[None], opts=opts)
1536 matchfn = scmutil.match(repo[None], opts=opts)
1545 else:
1537 else:
1546 match = scmutil.matchall(repo)
1538 match = scmutil.matchall(repo)
1547 m, a, r, d = repo.status(match=match)[:4]
1539 m, a, r, d = repo.status(match=match)[:4]
1548 mm = set(mm)
1540 mm = set(mm)
1549 aa = set(aa)
1541 aa = set(aa)
1550 dd = set(dd)
1542 dd = set(dd)
1551
1543
1552 # we might end up with files that were added between
1544 # we might end up with files that were added between
1553 # qtip and the dirstate parent, but then changed in the
1545 # qtip and the dirstate parent, but then changed in the
1554 # local dirstate. in this case, we want them to only
1546 # local dirstate. in this case, we want them to only
1555 # show up in the added section
1547 # show up in the added section
1556 for x in m:
1548 for x in m:
1557 if x not in aa:
1549 if x not in aa:
1558 mm.add(x)
1550 mm.add(x)
1559 # we might end up with files added by the local dirstate that
1551 # we might end up with files added by the local dirstate that
1560 # were deleted by the patch. In this case, they should only
1552 # were deleted by the patch. In this case, they should only
1561 # show up in the changed section.
1553 # show up in the changed section.
1562 for x in a:
1554 for x in a:
1563 if x in dd:
1555 if x in dd:
1564 dd.remove(x)
1556 dd.remove(x)
1565 mm.add(x)
1557 mm.add(x)
1566 else:
1558 else:
1567 aa.add(x)
1559 aa.add(x)
1568 # make sure any files deleted in the local dirstate
1560 # make sure any files deleted in the local dirstate
1569 # are not in the add or change column of the patch
1561 # are not in the add or change column of the patch
1570 forget = []
1562 forget = []
1571 for x in d + r:
1563 for x in d + r:
1572 if x in aa:
1564 if x in aa:
1573 aa.remove(x)
1565 aa.remove(x)
1574 forget.append(x)
1566 forget.append(x)
1575 continue
1567 continue
1576 else:
1568 else:
1577 mm.discard(x)
1569 mm.discard(x)
1578 dd.add(x)
1570 dd.add(x)
1579
1571
1580 m = list(mm)
1572 m = list(mm)
1581 r = list(dd)
1573 r = list(dd)
1582 a = list(aa)
1574 a = list(aa)
1583
1575
1584 # create 'match' that includes the files to be recommited.
1576 # create 'match' that includes the files to be recommited.
1585 # apply matchfn via repo.status to ensure correct case handling.
1577 # apply matchfn via repo.status to ensure correct case handling.
1586 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1578 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1587 allmatches = set(cm + ca + cr + cd)
1579 allmatches = set(cm + ca + cr + cd)
1588 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1580 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1589
1581
1590 files = set(inclsubs)
1582 files = set(inclsubs)
1591 for x in refreshchanges:
1583 for x in refreshchanges:
1592 files.update(x)
1584 files.update(x)
1593 match = scmutil.matchfiles(repo, files)
1585 match = scmutil.matchfiles(repo, files)
1594
1586
1595 bmlist = repo[top].bookmarks()
1587 bmlist = repo[top].bookmarks()
1596
1588
1597 try:
1589 try:
1598 if diffopts.git or diffopts.upgrade:
1590 if diffopts.git or diffopts.upgrade:
1599 copies = {}
1591 copies = {}
1600 for dst in a:
1592 for dst in a:
1601 src = repo.dirstate.copied(dst)
1593 src = repo.dirstate.copied(dst)
1602 # during qfold, the source file for copies may
1594 # during qfold, the source file for copies may
1603 # be removed. Treat this as a simple add.
1595 # be removed. Treat this as a simple add.
1604 if src is not None and src in repo.dirstate:
1596 if src is not None and src in repo.dirstate:
1605 copies.setdefault(src, []).append(dst)
1597 copies.setdefault(src, []).append(dst)
1606 repo.dirstate.add(dst)
1598 repo.dirstate.add(dst)
1607 # remember the copies between patchparent and qtip
1599 # remember the copies between patchparent and qtip
1608 for dst in aaa:
1600 for dst in aaa:
1609 f = repo.file(dst)
1601 f = repo.file(dst)
1610 src = f.renamed(man[dst])
1602 src = f.renamed(man[dst])
1611 if src:
1603 if src:
1612 copies.setdefault(src[0], []).extend(
1604 copies.setdefault(src[0], []).extend(
1613 copies.get(dst, []))
1605 copies.get(dst, []))
1614 if dst in a:
1606 if dst in a:
1615 copies[src[0]].append(dst)
1607 copies[src[0]].append(dst)
1616 # we can't copy a file created by the patch itself
1608 # we can't copy a file created by the patch itself
1617 if dst in copies:
1609 if dst in copies:
1618 del copies[dst]
1610 del copies[dst]
1619 for src, dsts in copies.iteritems():
1611 for src, dsts in copies.iteritems():
1620 for dst in dsts:
1612 for dst in dsts:
1621 repo.dirstate.copy(src, dst)
1613 repo.dirstate.copy(src, dst)
1622 else:
1614 else:
1623 for dst in a:
1615 for dst in a:
1624 repo.dirstate.add(dst)
1616 repo.dirstate.add(dst)
1625 # Drop useless copy information
1617 # Drop useless copy information
1626 for f in list(repo.dirstate.copies()):
1618 for f in list(repo.dirstate.copies()):
1627 repo.dirstate.copy(None, f)
1619 repo.dirstate.copy(None, f)
1628 for f in r:
1620 for f in r:
1629 repo.dirstate.remove(f)
1621 repo.dirstate.remove(f)
1630 # if the patch excludes a modified file, mark that
1622 # if the patch excludes a modified file, mark that
1631 # file with mtime=0 so status can see it.
1623 # file with mtime=0 so status can see it.
1632 mm = []
1624 mm = []
1633 for i in xrange(len(m) - 1, -1, -1):
1625 for i in xrange(len(m) - 1, -1, -1):
1634 if not matchfn(m[i]):
1626 if not matchfn(m[i]):
1635 mm.append(m[i])
1627 mm.append(m[i])
1636 del m[i]
1628 del m[i]
1637 for f in m:
1629 for f in m:
1638 repo.dirstate.normal(f)
1630 repo.dirstate.normal(f)
1639 for f in mm:
1631 for f in mm:
1640 repo.dirstate.normallookup(f)
1632 repo.dirstate.normallookup(f)
1641 for f in forget:
1633 for f in forget:
1642 repo.dirstate.drop(f)
1634 repo.dirstate.drop(f)
1643
1635
1644 if not msg:
1636 if not msg:
1645 if not ph.message:
1637 if not ph.message:
1646 message = "[mq]: %s\n" % patchfn
1638 message = "[mq]: %s\n" % patchfn
1647 else:
1639 else:
1648 message = "\n".join(ph.message)
1640 message = "\n".join(ph.message)
1649 else:
1641 else:
1650 message = msg
1642 message = msg
1651
1643
1652 user = ph.user or changes[1]
1644 user = ph.user or changes[1]
1653
1645
1654 oldphase = repo[top].phase()
1646 oldphase = repo[top].phase()
1655
1647
1656 # assumes strip can roll itself back if interrupted
1648 # assumes strip can roll itself back if interrupted
1657 repo.setparents(*cparents)
1649 repo.setparents(*cparents)
1658 self.applied.pop()
1650 self.applied.pop()
1659 self.applieddirty = True
1651 self.applieddirty = True
1660 self.strip(repo, [top], update=False,
1652 self.strip(repo, [top], update=False,
1661 backup='strip')
1653 backup='strip')
1662 except: # re-raises
1654 except: # re-raises
1663 repo.dirstate.invalidate()
1655 repo.dirstate.invalidate()
1664 raise
1656 raise
1665
1657
1666 try:
1658 try:
1667 # might be nice to attempt to roll back strip after this
1659 # might be nice to attempt to roll back strip after this
1668
1660
1669 # Ensure we create a new changeset in the same phase than
1661 # Ensure we create a new changeset in the same phase than
1670 # the old one.
1662 # the old one.
1671 n = newcommit(repo, oldphase, message, user, ph.date,
1663 n = newcommit(repo, oldphase, message, user, ph.date,
1672 match=match, force=True)
1664 match=match, force=True)
1673 # only write patch after a successful commit
1665 # only write patch after a successful commit
1674 c = [list(x) for x in refreshchanges]
1666 c = [list(x) for x in refreshchanges]
1675 if inclsubs:
1667 if inclsubs:
1676 self.putsubstate2changes(substatestate, c)
1668 self.putsubstate2changes(substatestate, c)
1677 chunks = patchmod.diff(repo, patchparent,
1669 chunks = patchmod.diff(repo, patchparent,
1678 changes=c, opts=diffopts)
1670 changes=c, opts=diffopts)
1679 for chunk in chunks:
1671 for chunk in chunks:
1680 patchf.write(chunk)
1672 patchf.write(chunk)
1681 patchf.close()
1673 patchf.close()
1682
1674
1683 marks = repo._bookmarks
1675 marks = repo._bookmarks
1684 for bm in bmlist:
1676 for bm in bmlist:
1685 marks[bm] = n
1677 marks[bm] = n
1686 marks.write()
1678 marks.write()
1687
1679
1688 self.applied.append(statusentry(n, patchfn))
1680 self.applied.append(statusentry(n, patchfn))
1689 except: # re-raises
1681 except: # re-raises
1690 ctx = repo[cparents[0]]
1682 ctx = repo[cparents[0]]
1691 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1683 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1692 self.savedirty()
1684 self.savedirty()
1693 self.ui.warn(_('refresh interrupted while patch was popped! '
1685 self.ui.warn(_('refresh interrupted while patch was popped! '
1694 '(revert --all, qpush to recover)\n'))
1686 '(revert --all, qpush to recover)\n'))
1695 raise
1687 raise
1696 finally:
1688 finally:
1697 wlock.release()
1689 wlock.release()
1698 self.removeundo(repo)
1690 self.removeundo(repo)
1699
1691
1700 def init(self, repo, create=False):
1692 def init(self, repo, create=False):
1701 if not create and os.path.isdir(self.path):
1693 if not create and os.path.isdir(self.path):
1702 raise util.Abort(_("patch queue directory already exists"))
1694 raise util.Abort(_("patch queue directory already exists"))
1703 try:
1695 try:
1704 os.mkdir(self.path)
1696 os.mkdir(self.path)
1705 except OSError, inst:
1697 except OSError, inst:
1706 if inst.errno != errno.EEXIST or not create:
1698 if inst.errno != errno.EEXIST or not create:
1707 raise
1699 raise
1708 if create:
1700 if create:
1709 return self.qrepo(create=True)
1701 return self.qrepo(create=True)
1710
1702
1711 def unapplied(self, repo, patch=None):
1703 def unapplied(self, repo, patch=None):
1712 if patch and patch not in self.series:
1704 if patch and patch not in self.series:
1713 raise util.Abort(_("patch %s is not in series file") % patch)
1705 raise util.Abort(_("patch %s is not in series file") % patch)
1714 if not patch:
1706 if not patch:
1715 start = self.seriesend()
1707 start = self.seriesend()
1716 else:
1708 else:
1717 start = self.series.index(patch) + 1
1709 start = self.series.index(patch) + 1
1718 unapplied = []
1710 unapplied = []
1719 for i in xrange(start, len(self.series)):
1711 for i in xrange(start, len(self.series)):
1720 pushable, reason = self.pushable(i)
1712 pushable, reason = self.pushable(i)
1721 if pushable:
1713 if pushable:
1722 unapplied.append((i, self.series[i]))
1714 unapplied.append((i, self.series[i]))
1723 self.explainpushable(i)
1715 self.explainpushable(i)
1724 return unapplied
1716 return unapplied
1725
1717
1726 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1718 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1727 summary=False):
1719 summary=False):
1728 def displayname(pfx, patchname, state):
1720 def displayname(pfx, patchname, state):
1729 if pfx:
1721 if pfx:
1730 self.ui.write(pfx)
1722 self.ui.write(pfx)
1731 if summary:
1723 if summary:
1732 ph = patchheader(self.join(patchname), self.plainmode)
1724 ph = patchheader(self.join(patchname), self.plainmode)
1733 msg = ph.message and ph.message[0] or ''
1725 msg = ph.message and ph.message[0] or ''
1734 if self.ui.formatted():
1726 if self.ui.formatted():
1735 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1727 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1736 if width > 0:
1728 if width > 0:
1737 msg = util.ellipsis(msg, width)
1729 msg = util.ellipsis(msg, width)
1738 else:
1730 else:
1739 msg = ''
1731 msg = ''
1740 self.ui.write(patchname, label='qseries.' + state)
1732 self.ui.write(patchname, label='qseries.' + state)
1741 self.ui.write(': ')
1733 self.ui.write(': ')
1742 self.ui.write(msg, label='qseries.message.' + state)
1734 self.ui.write(msg, label='qseries.message.' + state)
1743 else:
1735 else:
1744 self.ui.write(patchname, label='qseries.' + state)
1736 self.ui.write(patchname, label='qseries.' + state)
1745 self.ui.write('\n')
1737 self.ui.write('\n')
1746
1738
1747 applied = set([p.name for p in self.applied])
1739 applied = set([p.name for p in self.applied])
1748 if length is None:
1740 if length is None:
1749 length = len(self.series) - start
1741 length = len(self.series) - start
1750 if not missing:
1742 if not missing:
1751 if self.ui.verbose:
1743 if self.ui.verbose:
1752 idxwidth = len(str(start + length - 1))
1744 idxwidth = len(str(start + length - 1))
1753 for i in xrange(start, start + length):
1745 for i in xrange(start, start + length):
1754 patch = self.series[i]
1746 patch = self.series[i]
1755 if patch in applied:
1747 if patch in applied:
1756 char, state = 'A', 'applied'
1748 char, state = 'A', 'applied'
1757 elif self.pushable(i)[0]:
1749 elif self.pushable(i)[0]:
1758 char, state = 'U', 'unapplied'
1750 char, state = 'U', 'unapplied'
1759 else:
1751 else:
1760 char, state = 'G', 'guarded'
1752 char, state = 'G', 'guarded'
1761 pfx = ''
1753 pfx = ''
1762 if self.ui.verbose:
1754 if self.ui.verbose:
1763 pfx = '%*d %s ' % (idxwidth, i, char)
1755 pfx = '%*d %s ' % (idxwidth, i, char)
1764 elif status and status != char:
1756 elif status and status != char:
1765 continue
1757 continue
1766 displayname(pfx, patch, state)
1758 displayname(pfx, patch, state)
1767 else:
1759 else:
1768 msng_list = []
1760 msng_list = []
1769 for root, dirs, files in os.walk(self.path):
1761 for root, dirs, files in os.walk(self.path):
1770 d = root[len(self.path) + 1:]
1762 d = root[len(self.path) + 1:]
1771 for f in files:
1763 for f in files:
1772 fl = os.path.join(d, f)
1764 fl = os.path.join(d, f)
1773 if (fl not in self.series and
1765 if (fl not in self.series and
1774 fl not in (self.statuspath, self.seriespath,
1766 fl not in (self.statuspath, self.seriespath,
1775 self.guardspath)
1767 self.guardspath)
1776 and not fl.startswith('.')):
1768 and not fl.startswith('.')):
1777 msng_list.append(fl)
1769 msng_list.append(fl)
1778 for x in sorted(msng_list):
1770 for x in sorted(msng_list):
1779 pfx = self.ui.verbose and ('D ') or ''
1771 pfx = self.ui.verbose and ('D ') or ''
1780 displayname(pfx, x, 'missing')
1772 displayname(pfx, x, 'missing')
1781
1773
1782 def issaveline(self, l):
1774 def issaveline(self, l):
1783 if l.name == '.hg.patches.save.line':
1775 if l.name == '.hg.patches.save.line':
1784 return True
1776 return True
1785
1777
1786 def qrepo(self, create=False):
1778 def qrepo(self, create=False):
1787 ui = self.ui.copy()
1779 ui = self.ui.copy()
1788 ui.setconfig('paths', 'default', '', overlay=False)
1780 ui.setconfig('paths', 'default', '', overlay=False)
1789 ui.setconfig('paths', 'default-push', '', overlay=False)
1781 ui.setconfig('paths', 'default-push', '', overlay=False)
1790 if create or os.path.isdir(self.join(".hg")):
1782 if create or os.path.isdir(self.join(".hg")):
1791 return hg.repository(ui, path=self.path, create=create)
1783 return hg.repository(ui, path=self.path, create=create)
1792
1784
1793 def restore(self, repo, rev, delete=None, qupdate=None):
1785 def restore(self, repo, rev, delete=None, qupdate=None):
1794 desc = repo[rev].description().strip()
1786 desc = repo[rev].description().strip()
1795 lines = desc.splitlines()
1787 lines = desc.splitlines()
1796 i = 0
1788 i = 0
1797 datastart = None
1789 datastart = None
1798 series = []
1790 series = []
1799 applied = []
1791 applied = []
1800 qpp = None
1792 qpp = None
1801 for i, line in enumerate(lines):
1793 for i, line in enumerate(lines):
1802 if line == 'Patch Data:':
1794 if line == 'Patch Data:':
1803 datastart = i + 1
1795 datastart = i + 1
1804 elif line.startswith('Dirstate:'):
1796 elif line.startswith('Dirstate:'):
1805 l = line.rstrip()
1797 l = line.rstrip()
1806 l = l[10:].split(' ')
1798 l = l[10:].split(' ')
1807 qpp = [bin(x) for x in l]
1799 qpp = [bin(x) for x in l]
1808 elif datastart is not None:
1800 elif datastart is not None:
1809 l = line.rstrip()
1801 l = line.rstrip()
1810 n, name = l.split(':', 1)
1802 n, name = l.split(':', 1)
1811 if n:
1803 if n:
1812 applied.append(statusentry(bin(n), name))
1804 applied.append(statusentry(bin(n), name))
1813 else:
1805 else:
1814 series.append(l)
1806 series.append(l)
1815 if datastart is None:
1807 if datastart is None:
1816 self.ui.warn(_("no saved patch data found\n"))
1808 self.ui.warn(_("no saved patch data found\n"))
1817 return 1
1809 return 1
1818 self.ui.warn(_("restoring status: %s\n") % lines[0])
1810 self.ui.warn(_("restoring status: %s\n") % lines[0])
1819 self.fullseries = series
1811 self.fullseries = series
1820 self.applied = applied
1812 self.applied = applied
1821 self.parseseries()
1813 self.parseseries()
1822 self.seriesdirty = True
1814 self.seriesdirty = True
1823 self.applieddirty = True
1815 self.applieddirty = True
1824 heads = repo.changelog.heads()
1816 heads = repo.changelog.heads()
1825 if delete:
1817 if delete:
1826 if rev not in heads:
1818 if rev not in heads:
1827 self.ui.warn(_("save entry has children, leaving it alone\n"))
1819 self.ui.warn(_("save entry has children, leaving it alone\n"))
1828 else:
1820 else:
1829 self.ui.warn(_("removing save entry %s\n") % short(rev))
1821 self.ui.warn(_("removing save entry %s\n") % short(rev))
1830 pp = repo.dirstate.parents()
1822 pp = repo.dirstate.parents()
1831 if rev in pp:
1823 if rev in pp:
1832 update = True
1824 update = True
1833 else:
1825 else:
1834 update = False
1826 update = False
1835 self.strip(repo, [rev], update=update, backup='strip')
1827 self.strip(repo, [rev], update=update, backup='strip')
1836 if qpp:
1828 if qpp:
1837 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1829 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1838 (short(qpp[0]), short(qpp[1])))
1830 (short(qpp[0]), short(qpp[1])))
1839 if qupdate:
1831 if qupdate:
1840 self.ui.status(_("updating queue directory\n"))
1832 self.ui.status(_("updating queue directory\n"))
1841 r = self.qrepo()
1833 r = self.qrepo()
1842 if not r:
1834 if not r:
1843 self.ui.warn(_("unable to load queue repository\n"))
1835 self.ui.warn(_("unable to load queue repository\n"))
1844 return 1
1836 return 1
1845 hg.clean(r, qpp[0])
1837 hg.clean(r, qpp[0])
1846
1838
1847 def save(self, repo, msg=None):
1839 def save(self, repo, msg=None):
1848 if not self.applied:
1840 if not self.applied:
1849 self.ui.warn(_("save: no patches applied, exiting\n"))
1841 self.ui.warn(_("save: no patches applied, exiting\n"))
1850 return 1
1842 return 1
1851 if self.issaveline(self.applied[-1]):
1843 if self.issaveline(self.applied[-1]):
1852 self.ui.warn(_("status is already saved\n"))
1844 self.ui.warn(_("status is already saved\n"))
1853 return 1
1845 return 1
1854
1846
1855 if not msg:
1847 if not msg:
1856 msg = _("hg patches saved state")
1848 msg = _("hg patches saved state")
1857 else:
1849 else:
1858 msg = "hg patches: " + msg.rstrip('\r\n')
1850 msg = "hg patches: " + msg.rstrip('\r\n')
1859 r = self.qrepo()
1851 r = self.qrepo()
1860 if r:
1852 if r:
1861 pp = r.dirstate.parents()
1853 pp = r.dirstate.parents()
1862 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1854 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1863 msg += "\n\nPatch Data:\n"
1855 msg += "\n\nPatch Data:\n"
1864 msg += ''.join('%s\n' % x for x in self.applied)
1856 msg += ''.join('%s\n' % x for x in self.applied)
1865 msg += ''.join(':%s\n' % x for x in self.fullseries)
1857 msg += ''.join(':%s\n' % x for x in self.fullseries)
1866 n = repo.commit(msg, force=True)
1858 n = repo.commit(msg, force=True)
1867 if not n:
1859 if not n:
1868 self.ui.warn(_("repo commit failed\n"))
1860 self.ui.warn(_("repo commit failed\n"))
1869 return 1
1861 return 1
1870 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1862 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1871 self.applieddirty = True
1863 self.applieddirty = True
1872 self.removeundo(repo)
1864 self.removeundo(repo)
1873
1865
1874 def fullseriesend(self):
1866 def fullseriesend(self):
1875 if self.applied:
1867 if self.applied:
1876 p = self.applied[-1].name
1868 p = self.applied[-1].name
1877 end = self.findseries(p)
1869 end = self.findseries(p)
1878 if end is None:
1870 if end is None:
1879 return len(self.fullseries)
1871 return len(self.fullseries)
1880 return end + 1
1872 return end + 1
1881 return 0
1873 return 0
1882
1874
1883 def seriesend(self, all_patches=False):
1875 def seriesend(self, all_patches=False):
1884 """If all_patches is False, return the index of the next pushable patch
1876 """If all_patches is False, return the index of the next pushable patch
1885 in the series, or the series length. If all_patches is True, return the
1877 in the series, or the series length. If all_patches is True, return the
1886 index of the first patch past the last applied one.
1878 index of the first patch past the last applied one.
1887 """
1879 """
1888 end = 0
1880 end = 0
1889 def next(start):
1881 def next(start):
1890 if all_patches or start >= len(self.series):
1882 if all_patches or start >= len(self.series):
1891 return start
1883 return start
1892 for i in xrange(start, len(self.series)):
1884 for i in xrange(start, len(self.series)):
1893 p, reason = self.pushable(i)
1885 p, reason = self.pushable(i)
1894 if p:
1886 if p:
1895 return i
1887 return i
1896 self.explainpushable(i)
1888 self.explainpushable(i)
1897 return len(self.series)
1889 return len(self.series)
1898 if self.applied:
1890 if self.applied:
1899 p = self.applied[-1].name
1891 p = self.applied[-1].name
1900 try:
1892 try:
1901 end = self.series.index(p)
1893 end = self.series.index(p)
1902 except ValueError:
1894 except ValueError:
1903 return 0
1895 return 0
1904 return next(end + 1)
1896 return next(end + 1)
1905 return next(end)
1897 return next(end)
1906
1898
1907 def appliedname(self, index):
1899 def appliedname(self, index):
1908 pname = self.applied[index].name
1900 pname = self.applied[index].name
1909 if not self.ui.verbose:
1901 if not self.ui.verbose:
1910 p = pname
1902 p = pname
1911 else:
1903 else:
1912 p = str(self.series.index(pname)) + " " + pname
1904 p = str(self.series.index(pname)) + " " + pname
1913 return p
1905 return p
1914
1906
1915 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1907 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1916 force=None, git=False):
1908 force=None, git=False):
1917 def checkseries(patchname):
1909 def checkseries(patchname):
1918 if patchname in self.series:
1910 if patchname in self.series:
1919 raise util.Abort(_('patch %s is already in the series file')
1911 raise util.Abort(_('patch %s is already in the series file')
1920 % patchname)
1912 % patchname)
1921
1913
1922 if rev:
1914 if rev:
1923 if files:
1915 if files:
1924 raise util.Abort(_('option "-r" not valid when importing '
1916 raise util.Abort(_('option "-r" not valid when importing '
1925 'files'))
1917 'files'))
1926 rev = scmutil.revrange(repo, rev)
1918 rev = scmutil.revrange(repo, rev)
1927 rev.sort(reverse=True)
1919 rev.sort(reverse=True)
1928 elif not files:
1920 elif not files:
1929 raise util.Abort(_('no files or revisions specified'))
1921 raise util.Abort(_('no files or revisions specified'))
1930 if (len(files) > 1 or len(rev) > 1) and patchname:
1922 if (len(files) > 1 or len(rev) > 1) and patchname:
1931 raise util.Abort(_('option "-n" not valid when importing multiple '
1923 raise util.Abort(_('option "-n" not valid when importing multiple '
1932 'patches'))
1924 'patches'))
1933 imported = []
1925 imported = []
1934 if rev:
1926 if rev:
1935 # If mq patches are applied, we can only import revisions
1927 # If mq patches are applied, we can only import revisions
1936 # that form a linear path to qbase.
1928 # that form a linear path to qbase.
1937 # Otherwise, they should form a linear path to a head.
1929 # Otherwise, they should form a linear path to a head.
1938 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1930 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1939 if len(heads) > 1:
1931 if len(heads) > 1:
1940 raise util.Abort(_('revision %d is the root of more than one '
1932 raise util.Abort(_('revision %d is the root of more than one '
1941 'branch') % rev[-1])
1933 'branch') % rev[-1])
1942 if self.applied:
1934 if self.applied:
1943 base = repo.changelog.node(rev[0])
1935 base = repo.changelog.node(rev[0])
1944 if base in [n.node for n in self.applied]:
1936 if base in [n.node for n in self.applied]:
1945 raise util.Abort(_('revision %d is already managed')
1937 raise util.Abort(_('revision %d is already managed')
1946 % rev[0])
1938 % rev[0])
1947 if heads != [self.applied[-1].node]:
1939 if heads != [self.applied[-1].node]:
1948 raise util.Abort(_('revision %d is not the parent of '
1940 raise util.Abort(_('revision %d is not the parent of '
1949 'the queue') % rev[0])
1941 'the queue') % rev[0])
1950 base = repo.changelog.rev(self.applied[0].node)
1942 base = repo.changelog.rev(self.applied[0].node)
1951 lastparent = repo.changelog.parentrevs(base)[0]
1943 lastparent = repo.changelog.parentrevs(base)[0]
1952 else:
1944 else:
1953 if heads != [repo.changelog.node(rev[0])]:
1945 if heads != [repo.changelog.node(rev[0])]:
1954 raise util.Abort(_('revision %d has unmanaged children')
1946 raise util.Abort(_('revision %d has unmanaged children')
1955 % rev[0])
1947 % rev[0])
1956 lastparent = None
1948 lastparent = None
1957
1949
1958 diffopts = self.diffopts({'git': git})
1950 diffopts = self.diffopts({'git': git})
1959 for r in rev:
1951 for r in rev:
1960 if not repo[r].mutable():
1952 if not repo[r].mutable():
1961 raise util.Abort(_('revision %d is not mutable') % r,
1953 raise util.Abort(_('revision %d is not mutable') % r,
1962 hint=_('see "hg help phases" for details'))
1954 hint=_('see "hg help phases" for details'))
1963 p1, p2 = repo.changelog.parentrevs(r)
1955 p1, p2 = repo.changelog.parentrevs(r)
1964 n = repo.changelog.node(r)
1956 n = repo.changelog.node(r)
1965 if p2 != nullrev:
1957 if p2 != nullrev:
1966 raise util.Abort(_('cannot import merge revision %d') % r)
1958 raise util.Abort(_('cannot import merge revision %d') % r)
1967 if lastparent and lastparent != r:
1959 if lastparent and lastparent != r:
1968 raise util.Abort(_('revision %d is not the parent of %d')
1960 raise util.Abort(_('revision %d is not the parent of %d')
1969 % (r, lastparent))
1961 % (r, lastparent))
1970 lastparent = p1
1962 lastparent = p1
1971
1963
1972 if not patchname:
1964 if not patchname:
1973 patchname = normname('%d.diff' % r)
1965 patchname = normname('%d.diff' % r)
1974 checkseries(patchname)
1966 checkseries(patchname)
1975 self.checkpatchname(patchname, force)
1967 self.checkpatchname(patchname, force)
1976 self.fullseries.insert(0, patchname)
1968 self.fullseries.insert(0, patchname)
1977
1969
1978 patchf = self.opener(patchname, "w")
1970 patchf = self.opener(patchname, "w")
1979 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1971 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1980 patchf.close()
1972 patchf.close()
1981
1973
1982 se = statusentry(n, patchname)
1974 se = statusentry(n, patchname)
1983 self.applied.insert(0, se)
1975 self.applied.insert(0, se)
1984
1976
1985 self.added.append(patchname)
1977 self.added.append(patchname)
1986 imported.append(patchname)
1978 imported.append(patchname)
1987 patchname = None
1979 patchname = None
1988 if rev and repo.ui.configbool('mq', 'secret', False):
1980 if rev and repo.ui.configbool('mq', 'secret', False):
1989 # if we added anything with --rev, we must move the secret root
1981 # if we added anything with --rev, we must move the secret root
1990 phases.retractboundary(repo, phases.secret, [n])
1982 phases.retractboundary(repo, phases.secret, [n])
1991 self.parseseries()
1983 self.parseseries()
1992 self.applieddirty = True
1984 self.applieddirty = True
1993 self.seriesdirty = True
1985 self.seriesdirty = True
1994
1986
1995 for i, filename in enumerate(files):
1987 for i, filename in enumerate(files):
1996 if existing:
1988 if existing:
1997 if filename == '-':
1989 if filename == '-':
1998 raise util.Abort(_('-e is incompatible with import from -'))
1990 raise util.Abort(_('-e is incompatible with import from -'))
1999 filename = normname(filename)
1991 filename = normname(filename)
2000 self.checkreservedname(filename)
1992 self.checkreservedname(filename)
2001 originpath = self.join(filename)
1993 originpath = self.join(filename)
2002 if not os.path.isfile(originpath):
1994 if not os.path.isfile(originpath):
2003 raise util.Abort(_("patch %s does not exist") % filename)
1995 raise util.Abort(_("patch %s does not exist") % filename)
2004
1996
2005 if patchname:
1997 if patchname:
2006 self.checkpatchname(patchname, force)
1998 self.checkpatchname(patchname, force)
2007
1999
2008 self.ui.write(_('renaming %s to %s\n')
2000 self.ui.write(_('renaming %s to %s\n')
2009 % (filename, patchname))
2001 % (filename, patchname))
2010 util.rename(originpath, self.join(patchname))
2002 util.rename(originpath, self.join(patchname))
2011 else:
2003 else:
2012 patchname = filename
2004 patchname = filename
2013
2005
2014 else:
2006 else:
2015 if filename == '-' and not patchname:
2007 if filename == '-' and not patchname:
2016 raise util.Abort(_('need --name to import a patch from -'))
2008 raise util.Abort(_('need --name to import a patch from -'))
2017 elif not patchname:
2009 elif not patchname:
2018 patchname = normname(os.path.basename(filename.rstrip('/')))
2010 patchname = normname(os.path.basename(filename.rstrip('/')))
2019 self.checkpatchname(patchname, force)
2011 self.checkpatchname(patchname, force)
2020 try:
2012 try:
2021 if filename == '-':
2013 if filename == '-':
2022 text = self.ui.fin.read()
2014 text = self.ui.fin.read()
2023 else:
2015 else:
2024 fp = hg.openpath(self.ui, filename)
2016 fp = hg.openpath(self.ui, filename)
2025 text = fp.read()
2017 text = fp.read()
2026 fp.close()
2018 fp.close()
2027 except (OSError, IOError):
2019 except (OSError, IOError):
2028 raise util.Abort(_("unable to read file %s") % filename)
2020 raise util.Abort(_("unable to read file %s") % filename)
2029 patchf = self.opener(patchname, "w")
2021 patchf = self.opener(patchname, "w")
2030 patchf.write(text)
2022 patchf.write(text)
2031 patchf.close()
2023 patchf.close()
2032 if not force:
2024 if not force:
2033 checkseries(patchname)
2025 checkseries(patchname)
2034 if patchname not in self.series:
2026 if patchname not in self.series:
2035 index = self.fullseriesend() + i
2027 index = self.fullseriesend() + i
2036 self.fullseries[index:index] = [patchname]
2028 self.fullseries[index:index] = [patchname]
2037 self.parseseries()
2029 self.parseseries()
2038 self.seriesdirty = True
2030 self.seriesdirty = True
2039 self.ui.warn(_("adding %s to series file\n") % patchname)
2031 self.ui.warn(_("adding %s to series file\n") % patchname)
2040 self.added.append(patchname)
2032 self.added.append(patchname)
2041 imported.append(patchname)
2033 imported.append(patchname)
2042 patchname = None
2034 patchname = None
2043
2035
2044 self.removeundo(repo)
2036 self.removeundo(repo)
2045 return imported
2037 return imported
2046
2038
2047 def fixkeepchangesopts(ui, opts):
2039 def fixkeepchangesopts(ui, opts):
2048 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2040 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2049 or opts.get('exact')):
2041 or opts.get('exact')):
2050 return opts
2042 return opts
2051 opts = dict(opts)
2043 opts = dict(opts)
2052 opts['keep_changes'] = True
2044 opts['keep_changes'] = True
2053 return opts
2045 return opts
2054
2046
2055 @command("qdelete|qremove|qrm",
2047 @command("qdelete|qremove|qrm",
2056 [('k', 'keep', None, _('keep patch file')),
2048 [('k', 'keep', None, _('keep patch file')),
2057 ('r', 'rev', [],
2049 ('r', 'rev', [],
2058 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2050 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2059 _('hg qdelete [-k] [PATCH]...'))
2051 _('hg qdelete [-k] [PATCH]...'))
2060 def delete(ui, repo, *patches, **opts):
2052 def delete(ui, repo, *patches, **opts):
2061 """remove patches from queue
2053 """remove patches from queue
2062
2054
2063 The patches must not be applied, and at least one patch is required. Exact
2055 The patches must not be applied, and at least one patch is required. Exact
2064 patch identifiers must be given. With -k/--keep, the patch files are
2056 patch identifiers must be given. With -k/--keep, the patch files are
2065 preserved in the patch directory.
2057 preserved in the patch directory.
2066
2058
2067 To stop managing a patch and move it into permanent history,
2059 To stop managing a patch and move it into permanent history,
2068 use the :hg:`qfinish` command."""
2060 use the :hg:`qfinish` command."""
2069 q = repo.mq
2061 q = repo.mq
2070 q.delete(repo, patches, opts)
2062 q.delete(repo, patches, opts)
2071 q.savedirty()
2063 q.savedirty()
2072 return 0
2064 return 0
2073
2065
2074 @command("qapplied",
2066 @command("qapplied",
2075 [('1', 'last', None, _('show only the preceding applied patch'))
2067 [('1', 'last', None, _('show only the preceding applied patch'))
2076 ] + seriesopts,
2068 ] + seriesopts,
2077 _('hg qapplied [-1] [-s] [PATCH]'))
2069 _('hg qapplied [-1] [-s] [PATCH]'))
2078 def applied(ui, repo, patch=None, **opts):
2070 def applied(ui, repo, patch=None, **opts):
2079 """print the patches already applied
2071 """print the patches already applied
2080
2072
2081 Returns 0 on success."""
2073 Returns 0 on success."""
2082
2074
2083 q = repo.mq
2075 q = repo.mq
2084
2076
2085 if patch:
2077 if patch:
2086 if patch not in q.series:
2078 if patch not in q.series:
2087 raise util.Abort(_("patch %s is not in series file") % patch)
2079 raise util.Abort(_("patch %s is not in series file") % patch)
2088 end = q.series.index(patch) + 1
2080 end = q.series.index(patch) + 1
2089 else:
2081 else:
2090 end = q.seriesend(True)
2082 end = q.seriesend(True)
2091
2083
2092 if opts.get('last') and not end:
2084 if opts.get('last') and not end:
2093 ui.write(_("no patches applied\n"))
2085 ui.write(_("no patches applied\n"))
2094 return 1
2086 return 1
2095 elif opts.get('last') and end == 1:
2087 elif opts.get('last') and end == 1:
2096 ui.write(_("only one patch applied\n"))
2088 ui.write(_("only one patch applied\n"))
2097 return 1
2089 return 1
2098 elif opts.get('last'):
2090 elif opts.get('last'):
2099 start = end - 2
2091 start = end - 2
2100 end = 1
2092 end = 1
2101 else:
2093 else:
2102 start = 0
2094 start = 0
2103
2095
2104 q.qseries(repo, length=end, start=start, status='A',
2096 q.qseries(repo, length=end, start=start, status='A',
2105 summary=opts.get('summary'))
2097 summary=opts.get('summary'))
2106
2098
2107
2099
2108 @command("qunapplied",
2100 @command("qunapplied",
2109 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2101 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2110 _('hg qunapplied [-1] [-s] [PATCH]'))
2102 _('hg qunapplied [-1] [-s] [PATCH]'))
2111 def unapplied(ui, repo, patch=None, **opts):
2103 def unapplied(ui, repo, patch=None, **opts):
2112 """print the patches not yet applied
2104 """print the patches not yet applied
2113
2105
2114 Returns 0 on success."""
2106 Returns 0 on success."""
2115
2107
2116 q = repo.mq
2108 q = repo.mq
2117 if patch:
2109 if patch:
2118 if patch not in q.series:
2110 if patch not in q.series:
2119 raise util.Abort(_("patch %s is not in series file") % patch)
2111 raise util.Abort(_("patch %s is not in series file") % patch)
2120 start = q.series.index(patch) + 1
2112 start = q.series.index(patch) + 1
2121 else:
2113 else:
2122 start = q.seriesend(True)
2114 start = q.seriesend(True)
2123
2115
2124 if start == len(q.series) and opts.get('first'):
2116 if start == len(q.series) and opts.get('first'):
2125 ui.write(_("all patches applied\n"))
2117 ui.write(_("all patches applied\n"))
2126 return 1
2118 return 1
2127
2119
2128 length = opts.get('first') and 1 or None
2120 length = opts.get('first') and 1 or None
2129 q.qseries(repo, start=start, length=length, status='U',
2121 q.qseries(repo, start=start, length=length, status='U',
2130 summary=opts.get('summary'))
2122 summary=opts.get('summary'))
2131
2123
2132 @command("qimport",
2124 @command("qimport",
2133 [('e', 'existing', None, _('import file in patch directory')),
2125 [('e', 'existing', None, _('import file in patch directory')),
2134 ('n', 'name', '',
2126 ('n', 'name', '',
2135 _('name of patch file'), _('NAME')),
2127 _('name of patch file'), _('NAME')),
2136 ('f', 'force', None, _('overwrite existing files')),
2128 ('f', 'force', None, _('overwrite existing files')),
2137 ('r', 'rev', [],
2129 ('r', 'rev', [],
2138 _('place existing revisions under mq control'), _('REV')),
2130 _('place existing revisions under mq control'), _('REV')),
2139 ('g', 'git', None, _('use git extended diff format')),
2131 ('g', 'git', None, _('use git extended diff format')),
2140 ('P', 'push', None, _('qpush after importing'))],
2132 ('P', 'push', None, _('qpush after importing'))],
2141 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2133 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2142 def qimport(ui, repo, *filename, **opts):
2134 def qimport(ui, repo, *filename, **opts):
2143 """import a patch or existing changeset
2135 """import a patch or existing changeset
2144
2136
2145 The patch is inserted into the series after the last applied
2137 The patch is inserted into the series after the last applied
2146 patch. If no patches have been applied, qimport prepends the patch
2138 patch. If no patches have been applied, qimport prepends the patch
2147 to the series.
2139 to the series.
2148
2140
2149 The patch will have the same name as its source file unless you
2141 The patch will have the same name as its source file unless you
2150 give it a new one with -n/--name.
2142 give it a new one with -n/--name.
2151
2143
2152 You can register an existing patch inside the patch directory with
2144 You can register an existing patch inside the patch directory with
2153 the -e/--existing flag.
2145 the -e/--existing flag.
2154
2146
2155 With -f/--force, an existing patch of the same name will be
2147 With -f/--force, an existing patch of the same name will be
2156 overwritten.
2148 overwritten.
2157
2149
2158 An existing changeset may be placed under mq control with -r/--rev
2150 An existing changeset may be placed under mq control with -r/--rev
2159 (e.g. qimport --rev tip -n patch will place tip under mq control).
2151 (e.g. qimport --rev tip -n patch will place tip under mq control).
2160 With -g/--git, patches imported with --rev will use the git diff
2152 With -g/--git, patches imported with --rev will use the git diff
2161 format. See the diffs help topic for information on why this is
2153 format. See the diffs help topic for information on why this is
2162 important for preserving rename/copy information and permission
2154 important for preserving rename/copy information and permission
2163 changes. Use :hg:`qfinish` to remove changesets from mq control.
2155 changes. Use :hg:`qfinish` to remove changesets from mq control.
2164
2156
2165 To import a patch from standard input, pass - as the patch file.
2157 To import a patch from standard input, pass - as the patch file.
2166 When importing from standard input, a patch name must be specified
2158 When importing from standard input, a patch name must be specified
2167 using the --name flag.
2159 using the --name flag.
2168
2160
2169 To import an existing patch while renaming it::
2161 To import an existing patch while renaming it::
2170
2162
2171 hg qimport -e existing-patch -n new-name
2163 hg qimport -e existing-patch -n new-name
2172
2164
2173 Returns 0 if import succeeded.
2165 Returns 0 if import succeeded.
2174 """
2166 """
2175 lock = repo.lock() # cause this may move phase
2167 lock = repo.lock() # cause this may move phase
2176 try:
2168 try:
2177 q = repo.mq
2169 q = repo.mq
2178 try:
2170 try:
2179 imported = q.qimport(
2171 imported = q.qimport(
2180 repo, filename, patchname=opts.get('name'),
2172 repo, filename, patchname=opts.get('name'),
2181 existing=opts.get('existing'), force=opts.get('force'),
2173 existing=opts.get('existing'), force=opts.get('force'),
2182 rev=opts.get('rev'), git=opts.get('git'))
2174 rev=opts.get('rev'), git=opts.get('git'))
2183 finally:
2175 finally:
2184 q.savedirty()
2176 q.savedirty()
2185 finally:
2177 finally:
2186 lock.release()
2178 lock.release()
2187
2179
2188 if imported and opts.get('push') and not opts.get('rev'):
2180 if imported and opts.get('push') and not opts.get('rev'):
2189 return q.push(repo, imported[-1])
2181 return q.push(repo, imported[-1])
2190 return 0
2182 return 0
2191
2183
2192 def qinit(ui, repo, create):
2184 def qinit(ui, repo, create):
2193 """initialize a new queue repository
2185 """initialize a new queue repository
2194
2186
2195 This command also creates a series file for ordering patches, and
2187 This command also creates a series file for ordering patches, and
2196 an mq-specific .hgignore file in the queue repository, to exclude
2188 an mq-specific .hgignore file in the queue repository, to exclude
2197 the status and guards files (these contain mostly transient state).
2189 the status and guards files (these contain mostly transient state).
2198
2190
2199 Returns 0 if initialization succeeded."""
2191 Returns 0 if initialization succeeded."""
2200 q = repo.mq
2192 q = repo.mq
2201 r = q.init(repo, create)
2193 r = q.init(repo, create)
2202 q.savedirty()
2194 q.savedirty()
2203 if r:
2195 if r:
2204 if not os.path.exists(r.wjoin('.hgignore')):
2196 if not os.path.exists(r.wjoin('.hgignore')):
2205 fp = r.wopener('.hgignore', 'w')
2197 fp = r.wopener('.hgignore', 'w')
2206 fp.write('^\\.hg\n')
2198 fp.write('^\\.hg\n')
2207 fp.write('^\\.mq\n')
2199 fp.write('^\\.mq\n')
2208 fp.write('syntax: glob\n')
2200 fp.write('syntax: glob\n')
2209 fp.write('status\n')
2201 fp.write('status\n')
2210 fp.write('guards\n')
2202 fp.write('guards\n')
2211 fp.close()
2203 fp.close()
2212 if not os.path.exists(r.wjoin('series')):
2204 if not os.path.exists(r.wjoin('series')):
2213 r.wopener('series', 'w').close()
2205 r.wopener('series', 'w').close()
2214 r[None].add(['.hgignore', 'series'])
2206 r[None].add(['.hgignore', 'series'])
2215 commands.add(ui, r)
2207 commands.add(ui, r)
2216 return 0
2208 return 0
2217
2209
2218 @command("^qinit",
2210 @command("^qinit",
2219 [('c', 'create-repo', None, _('create queue repository'))],
2211 [('c', 'create-repo', None, _('create queue repository'))],
2220 _('hg qinit [-c]'))
2212 _('hg qinit [-c]'))
2221 def init(ui, repo, **opts):
2213 def init(ui, repo, **opts):
2222 """init a new queue repository (DEPRECATED)
2214 """init a new queue repository (DEPRECATED)
2223
2215
2224 The queue repository is unversioned by default. If
2216 The queue repository is unversioned by default. If
2225 -c/--create-repo is specified, qinit will create a separate nested
2217 -c/--create-repo is specified, qinit will create a separate nested
2226 repository for patches (qinit -c may also be run later to convert
2218 repository for patches (qinit -c may also be run later to convert
2227 an unversioned patch repository into a versioned one). You can use
2219 an unversioned patch repository into a versioned one). You can use
2228 qcommit to commit changes to this queue repository.
2220 qcommit to commit changes to this queue repository.
2229
2221
2230 This command is deprecated. Without -c, it's implied by other relevant
2222 This command is deprecated. Without -c, it's implied by other relevant
2231 commands. With -c, use :hg:`init --mq` instead."""
2223 commands. With -c, use :hg:`init --mq` instead."""
2232 return qinit(ui, repo, create=opts.get('create_repo'))
2224 return qinit(ui, repo, create=opts.get('create_repo'))
2233
2225
2234 @command("qclone",
2226 @command("qclone",
2235 [('', 'pull', None, _('use pull protocol to copy metadata')),
2227 [('', 'pull', None, _('use pull protocol to copy metadata')),
2236 ('U', 'noupdate', None,
2228 ('U', 'noupdate', None,
2237 _('do not update the new working directories')),
2229 _('do not update the new working directories')),
2238 ('', 'uncompressed', None,
2230 ('', 'uncompressed', None,
2239 _('use uncompressed transfer (fast over LAN)')),
2231 _('use uncompressed transfer (fast over LAN)')),
2240 ('p', 'patches', '',
2232 ('p', 'patches', '',
2241 _('location of source patch repository'), _('REPO')),
2233 _('location of source patch repository'), _('REPO')),
2242 ] + commands.remoteopts,
2234 ] + commands.remoteopts,
2243 _('hg qclone [OPTION]... SOURCE [DEST]'))
2235 _('hg qclone [OPTION]... SOURCE [DEST]'))
2244 def clone(ui, source, dest=None, **opts):
2236 def clone(ui, source, dest=None, **opts):
2245 '''clone main and patch repository at same time
2237 '''clone main and patch repository at same time
2246
2238
2247 If source is local, destination will have no patches applied. If
2239 If source is local, destination will have no patches applied. If
2248 source is remote, this command can not check if patches are
2240 source is remote, this command can not check if patches are
2249 applied in source, so cannot guarantee that patches are not
2241 applied in source, so cannot guarantee that patches are not
2250 applied in destination. If you clone remote repository, be sure
2242 applied in destination. If you clone remote repository, be sure
2251 before that it has no patches applied.
2243 before that it has no patches applied.
2252
2244
2253 Source patch repository is looked for in <src>/.hg/patches by
2245 Source patch repository is looked for in <src>/.hg/patches by
2254 default. Use -p <url> to change.
2246 default. Use -p <url> to change.
2255
2247
2256 The patch directory must be a nested Mercurial repository, as
2248 The patch directory must be a nested Mercurial repository, as
2257 would be created by :hg:`init --mq`.
2249 would be created by :hg:`init --mq`.
2258
2250
2259 Return 0 on success.
2251 Return 0 on success.
2260 '''
2252 '''
2261 def patchdir(repo):
2253 def patchdir(repo):
2262 """compute a patch repo url from a repo object"""
2254 """compute a patch repo url from a repo object"""
2263 url = repo.url()
2255 url = repo.url()
2264 if url.endswith('/'):
2256 if url.endswith('/'):
2265 url = url[:-1]
2257 url = url[:-1]
2266 return url + '/.hg/patches'
2258 return url + '/.hg/patches'
2267
2259
2268 # main repo (destination and sources)
2260 # main repo (destination and sources)
2269 if dest is None:
2261 if dest is None:
2270 dest = hg.defaultdest(source)
2262 dest = hg.defaultdest(source)
2271 sr = hg.peer(ui, opts, ui.expandpath(source))
2263 sr = hg.peer(ui, opts, ui.expandpath(source))
2272
2264
2273 # patches repo (source only)
2265 # patches repo (source only)
2274 if opts.get('patches'):
2266 if opts.get('patches'):
2275 patchespath = ui.expandpath(opts.get('patches'))
2267 patchespath = ui.expandpath(opts.get('patches'))
2276 else:
2268 else:
2277 patchespath = patchdir(sr)
2269 patchespath = patchdir(sr)
2278 try:
2270 try:
2279 hg.peer(ui, opts, patchespath)
2271 hg.peer(ui, opts, patchespath)
2280 except error.RepoError:
2272 except error.RepoError:
2281 raise util.Abort(_('versioned patch repository not found'
2273 raise util.Abort(_('versioned patch repository not found'
2282 ' (see init --mq)'))
2274 ' (see init --mq)'))
2283 qbase, destrev = None, None
2275 qbase, destrev = None, None
2284 if sr.local():
2276 if sr.local():
2285 repo = sr.local()
2277 repo = sr.local()
2286 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2278 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2287 qbase = repo.mq.applied[0].node
2279 qbase = repo.mq.applied[0].node
2288 if not hg.islocal(dest):
2280 if not hg.islocal(dest):
2289 heads = set(repo.heads())
2281 heads = set(repo.heads())
2290 destrev = list(heads.difference(repo.heads(qbase)))
2282 destrev = list(heads.difference(repo.heads(qbase)))
2291 destrev.append(repo.changelog.parents(qbase)[0])
2283 destrev.append(repo.changelog.parents(qbase)[0])
2292 elif sr.capable('lookup'):
2284 elif sr.capable('lookup'):
2293 try:
2285 try:
2294 qbase = sr.lookup('qbase')
2286 qbase = sr.lookup('qbase')
2295 except error.RepoError:
2287 except error.RepoError:
2296 pass
2288 pass
2297
2289
2298 ui.note(_('cloning main repository\n'))
2290 ui.note(_('cloning main repository\n'))
2299 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2291 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2300 pull=opts.get('pull'),
2292 pull=opts.get('pull'),
2301 rev=destrev,
2293 rev=destrev,
2302 update=False,
2294 update=False,
2303 stream=opts.get('uncompressed'))
2295 stream=opts.get('uncompressed'))
2304
2296
2305 ui.note(_('cloning patch repository\n'))
2297 ui.note(_('cloning patch repository\n'))
2306 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2298 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2307 pull=opts.get('pull'), update=not opts.get('noupdate'),
2299 pull=opts.get('pull'), update=not opts.get('noupdate'),
2308 stream=opts.get('uncompressed'))
2300 stream=opts.get('uncompressed'))
2309
2301
2310 if dr.local():
2302 if dr.local():
2311 repo = dr.local()
2303 repo = dr.local()
2312 if qbase:
2304 if qbase:
2313 ui.note(_('stripping applied patches from destination '
2305 ui.note(_('stripping applied patches from destination '
2314 'repository\n'))
2306 'repository\n'))
2315 repo.mq.strip(repo, [qbase], update=False, backup=None)
2307 repo.mq.strip(repo, [qbase], update=False, backup=None)
2316 if not opts.get('noupdate'):
2308 if not opts.get('noupdate'):
2317 ui.note(_('updating destination repository\n'))
2309 ui.note(_('updating destination repository\n'))
2318 hg.update(repo, repo.changelog.tip())
2310 hg.update(repo, repo.changelog.tip())
2319
2311
2320 @command("qcommit|qci",
2312 @command("qcommit|qci",
2321 commands.table["^commit|ci"][1],
2313 commands.table["^commit|ci"][1],
2322 _('hg qcommit [OPTION]... [FILE]...'))
2314 _('hg qcommit [OPTION]... [FILE]...'))
2323 def commit(ui, repo, *pats, **opts):
2315 def commit(ui, repo, *pats, **opts):
2324 """commit changes in the queue repository (DEPRECATED)
2316 """commit changes in the queue repository (DEPRECATED)
2325
2317
2326 This command is deprecated; use :hg:`commit --mq` instead."""
2318 This command is deprecated; use :hg:`commit --mq` instead."""
2327 q = repo.mq
2319 q = repo.mq
2328 r = q.qrepo()
2320 r = q.qrepo()
2329 if not r:
2321 if not r:
2330 raise util.Abort('no queue repository')
2322 raise util.Abort('no queue repository')
2331 commands.commit(r.ui, r, *pats, **opts)
2323 commands.commit(r.ui, r, *pats, **opts)
2332
2324
2333 @command("qseries",
2325 @command("qseries",
2334 [('m', 'missing', None, _('print patches not in series')),
2326 [('m', 'missing', None, _('print patches not in series')),
2335 ] + seriesopts,
2327 ] + seriesopts,
2336 _('hg qseries [-ms]'))
2328 _('hg qseries [-ms]'))
2337 def series(ui, repo, **opts):
2329 def series(ui, repo, **opts):
2338 """print the entire series file
2330 """print the entire series file
2339
2331
2340 Returns 0 on success."""
2332 Returns 0 on success."""
2341 repo.mq.qseries(repo, missing=opts.get('missing'),
2333 repo.mq.qseries(repo, missing=opts.get('missing'),
2342 summary=opts.get('summary'))
2334 summary=opts.get('summary'))
2343 return 0
2335 return 0
2344
2336
2345 @command("qtop", seriesopts, _('hg qtop [-s]'))
2337 @command("qtop", seriesopts, _('hg qtop [-s]'))
2346 def top(ui, repo, **opts):
2338 def top(ui, repo, **opts):
2347 """print the name of the current patch
2339 """print the name of the current patch
2348
2340
2349 Returns 0 on success."""
2341 Returns 0 on success."""
2350 q = repo.mq
2342 q = repo.mq
2351 t = q.applied and q.seriesend(True) or 0
2343 t = q.applied and q.seriesend(True) or 0
2352 if t:
2344 if t:
2353 q.qseries(repo, start=t - 1, length=1, status='A',
2345 q.qseries(repo, start=t - 1, length=1, status='A',
2354 summary=opts.get('summary'))
2346 summary=opts.get('summary'))
2355 else:
2347 else:
2356 ui.write(_("no patches applied\n"))
2348 ui.write(_("no patches applied\n"))
2357 return 1
2349 return 1
2358
2350
2359 @command("qnext", seriesopts, _('hg qnext [-s]'))
2351 @command("qnext", seriesopts, _('hg qnext [-s]'))
2360 def next(ui, repo, **opts):
2352 def next(ui, repo, **opts):
2361 """print the name of the next pushable patch
2353 """print the name of the next pushable patch
2362
2354
2363 Returns 0 on success."""
2355 Returns 0 on success."""
2364 q = repo.mq
2356 q = repo.mq
2365 end = q.seriesend()
2357 end = q.seriesend()
2366 if end == len(q.series):
2358 if end == len(q.series):
2367 ui.write(_("all patches applied\n"))
2359 ui.write(_("all patches applied\n"))
2368 return 1
2360 return 1
2369 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2361 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2370
2362
2371 @command("qprev", seriesopts, _('hg qprev [-s]'))
2363 @command("qprev", seriesopts, _('hg qprev [-s]'))
2372 def prev(ui, repo, **opts):
2364 def prev(ui, repo, **opts):
2373 """print the name of the preceding applied patch
2365 """print the name of the preceding applied patch
2374
2366
2375 Returns 0 on success."""
2367 Returns 0 on success."""
2376 q = repo.mq
2368 q = repo.mq
2377 l = len(q.applied)
2369 l = len(q.applied)
2378 if l == 1:
2370 if l == 1:
2379 ui.write(_("only one patch applied\n"))
2371 ui.write(_("only one patch applied\n"))
2380 return 1
2372 return 1
2381 if not l:
2373 if not l:
2382 ui.write(_("no patches applied\n"))
2374 ui.write(_("no patches applied\n"))
2383 return 1
2375 return 1
2384 idx = q.series.index(q.applied[-2].name)
2376 idx = q.series.index(q.applied[-2].name)
2385 q.qseries(repo, start=idx, length=1, status='A',
2377 q.qseries(repo, start=idx, length=1, status='A',
2386 summary=opts.get('summary'))
2378 summary=opts.get('summary'))
2387
2379
2388 def setupheaderopts(ui, opts):
2380 def setupheaderopts(ui, opts):
2389 if not opts.get('user') and opts.get('currentuser'):
2381 if not opts.get('user') and opts.get('currentuser'):
2390 opts['user'] = ui.username()
2382 opts['user'] = ui.username()
2391 if not opts.get('date') and opts.get('currentdate'):
2383 if not opts.get('date') and opts.get('currentdate'):
2392 opts['date'] = "%d %d" % util.makedate()
2384 opts['date'] = "%d %d" % util.makedate()
2393
2385
2394 @command("^qnew",
2386 @command("^qnew",
2395 [('e', 'edit', None, _('edit commit message')),
2387 [('e', 'edit', None, _('edit commit message')),
2396 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2388 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2397 ('g', 'git', None, _('use git extended diff format')),
2389 ('g', 'git', None, _('use git extended diff format')),
2398 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2390 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2399 ('u', 'user', '',
2391 ('u', 'user', '',
2400 _('add "From: <USER>" to patch'), _('USER')),
2392 _('add "From: <USER>" to patch'), _('USER')),
2401 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2393 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2402 ('d', 'date', '',
2394 ('d', 'date', '',
2403 _('add "Date: <DATE>" to patch'), _('DATE'))
2395 _('add "Date: <DATE>" to patch'), _('DATE'))
2404 ] + commands.walkopts + commands.commitopts,
2396 ] + commands.walkopts + commands.commitopts,
2405 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2397 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2406 def new(ui, repo, patch, *args, **opts):
2398 def new(ui, repo, patch, *args, **opts):
2407 """create a new patch
2399 """create a new patch
2408
2400
2409 qnew creates a new patch on top of the currently-applied patch (if
2401 qnew creates a new patch on top of the currently-applied patch (if
2410 any). The patch will be initialized with any outstanding changes
2402 any). The patch will be initialized with any outstanding changes
2411 in the working directory. You may also use -I/--include,
2403 in the working directory. You may also use -I/--include,
2412 -X/--exclude, and/or a list of files after the patch name to add
2404 -X/--exclude, and/or a list of files after the patch name to add
2413 only changes to matching files to the new patch, leaving the rest
2405 only changes to matching files to the new patch, leaving the rest
2414 as uncommitted modifications.
2406 as uncommitted modifications.
2415
2407
2416 -u/--user and -d/--date can be used to set the (given) user and
2408 -u/--user and -d/--date can be used to set the (given) user and
2417 date, respectively. -U/--currentuser and -D/--currentdate set user
2409 date, respectively. -U/--currentuser and -D/--currentdate set user
2418 to current user and date to current date.
2410 to current user and date to current date.
2419
2411
2420 -e/--edit, -m/--message or -l/--logfile set the patch header as
2412 -e/--edit, -m/--message or -l/--logfile set the patch header as
2421 well as the commit message. If none is specified, the header is
2413 well as the commit message. If none is specified, the header is
2422 empty and the commit message is '[mq]: PATCH'.
2414 empty and the commit message is '[mq]: PATCH'.
2423
2415
2424 Use the -g/--git option to keep the patch in the git extended diff
2416 Use the -g/--git option to keep the patch in the git extended diff
2425 format. Read the diffs help topic for more information on why this
2417 format. Read the diffs help topic for more information on why this
2426 is important for preserving permission changes and copy/rename
2418 is important for preserving permission changes and copy/rename
2427 information.
2419 information.
2428
2420
2429 Returns 0 on successful creation of a new patch.
2421 Returns 0 on successful creation of a new patch.
2430 """
2422 """
2431 msg = cmdutil.logmessage(ui, opts)
2423 msg = cmdutil.logmessage(ui, opts)
2432 def getmsg():
2424 def getmsg():
2433 return ui.edit(msg, opts.get('user') or ui.username())
2425 return ui.edit(msg, opts.get('user') or ui.username())
2434 q = repo.mq
2426 q = repo.mq
2435 opts['msg'] = msg
2427 opts['msg'] = msg
2436 if opts.get('edit'):
2428 if opts.get('edit'):
2437 opts['msg'] = getmsg
2429 opts['msg'] = getmsg
2438 else:
2430 else:
2439 opts['msg'] = msg
2431 opts['msg'] = msg
2440 setupheaderopts(ui, opts)
2432 setupheaderopts(ui, opts)
2441 q.new(repo, patch, *args, **opts)
2433 q.new(repo, patch, *args, **opts)
2442 q.savedirty()
2434 q.savedirty()
2443 return 0
2435 return 0
2444
2436
2445 @command("^qrefresh",
2437 @command("^qrefresh",
2446 [('e', 'edit', None, _('edit commit message')),
2438 [('e', 'edit', None, _('edit commit message')),
2447 ('g', 'git', None, _('use git extended diff format')),
2439 ('g', 'git', None, _('use git extended diff format')),
2448 ('s', 'short', None,
2440 ('s', 'short', None,
2449 _('refresh only files already in the patch and specified files')),
2441 _('refresh only files already in the patch and specified files')),
2450 ('U', 'currentuser', None,
2442 ('U', 'currentuser', None,
2451 _('add/update author field in patch with current user')),
2443 _('add/update author field in patch with current user')),
2452 ('u', 'user', '',
2444 ('u', 'user', '',
2453 _('add/update author field in patch with given user'), _('USER')),
2445 _('add/update author field in patch with given user'), _('USER')),
2454 ('D', 'currentdate', None,
2446 ('D', 'currentdate', None,
2455 _('add/update date field in patch with current date')),
2447 _('add/update date field in patch with current date')),
2456 ('d', 'date', '',
2448 ('d', 'date', '',
2457 _('add/update date field in patch with given date'), _('DATE'))
2449 _('add/update date field in patch with given date'), _('DATE'))
2458 ] + commands.walkopts + commands.commitopts,
2450 ] + commands.walkopts + commands.commitopts,
2459 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2451 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2460 def refresh(ui, repo, *pats, **opts):
2452 def refresh(ui, repo, *pats, **opts):
2461 """update the current patch
2453 """update the current patch
2462
2454
2463 If any file patterns are provided, the refreshed patch will
2455 If any file patterns are provided, the refreshed patch will
2464 contain only the modifications that match those patterns; the
2456 contain only the modifications that match those patterns; the
2465 remaining modifications will remain in the working directory.
2457 remaining modifications will remain in the working directory.
2466
2458
2467 If -s/--short is specified, files currently included in the patch
2459 If -s/--short is specified, files currently included in the patch
2468 will be refreshed just like matched files and remain in the patch.
2460 will be refreshed just like matched files and remain in the patch.
2469
2461
2470 If -e/--edit is specified, Mercurial will start your configured editor for
2462 If -e/--edit is specified, Mercurial will start your configured editor for
2471 you to enter a message. In case qrefresh fails, you will find a backup of
2463 you to enter a message. In case qrefresh fails, you will find a backup of
2472 your message in ``.hg/last-message.txt``.
2464 your message in ``.hg/last-message.txt``.
2473
2465
2474 hg add/remove/copy/rename work as usual, though you might want to
2466 hg add/remove/copy/rename work as usual, though you might want to
2475 use git-style patches (-g/--git or [diff] git=1) to track copies
2467 use git-style patches (-g/--git or [diff] git=1) to track copies
2476 and renames. See the diffs help topic for more information on the
2468 and renames. See the diffs help topic for more information on the
2477 git diff format.
2469 git diff format.
2478
2470
2479 Returns 0 on success.
2471 Returns 0 on success.
2480 """
2472 """
2481 q = repo.mq
2473 q = repo.mq
2482 message = cmdutil.logmessage(ui, opts)
2474 message = cmdutil.logmessage(ui, opts)
2483 if opts.get('edit'):
2475 if opts.get('edit'):
2484 if not q.applied:
2476 if not q.applied:
2485 ui.write(_("no patches applied\n"))
2477 ui.write(_("no patches applied\n"))
2486 return 1
2478 return 1
2487 if message:
2479 if message:
2488 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2480 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2489 patch = q.applied[-1].name
2481 patch = q.applied[-1].name
2490 ph = patchheader(q.join(patch), q.plainmode)
2482 ph = patchheader(q.join(patch), q.plainmode)
2491 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2483 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2492 # We don't want to lose the patch message if qrefresh fails (issue2062)
2484 # We don't want to lose the patch message if qrefresh fails (issue2062)
2493 repo.savecommitmessage(message)
2485 repo.savecommitmessage(message)
2494 setupheaderopts(ui, opts)
2486 setupheaderopts(ui, opts)
2495 wlock = repo.wlock()
2487 wlock = repo.wlock()
2496 try:
2488 try:
2497 ret = q.refresh(repo, pats, msg=message, **opts)
2489 ret = q.refresh(repo, pats, msg=message, **opts)
2498 q.savedirty()
2490 q.savedirty()
2499 return ret
2491 return ret
2500 finally:
2492 finally:
2501 wlock.release()
2493 wlock.release()
2502
2494
2503 @command("^qdiff",
2495 @command("^qdiff",
2504 commands.diffopts + commands.diffopts2 + commands.walkopts,
2496 commands.diffopts + commands.diffopts2 + commands.walkopts,
2505 _('hg qdiff [OPTION]... [FILE]...'))
2497 _('hg qdiff [OPTION]... [FILE]...'))
2506 def diff(ui, repo, *pats, **opts):
2498 def diff(ui, repo, *pats, **opts):
2507 """diff of the current patch and subsequent modifications
2499 """diff of the current patch and subsequent modifications
2508
2500
2509 Shows a diff which includes the current patch as well as any
2501 Shows a diff which includes the current patch as well as any
2510 changes which have been made in the working directory since the
2502 changes which have been made in the working directory since the
2511 last refresh (thus showing what the current patch would become
2503 last refresh (thus showing what the current patch would become
2512 after a qrefresh).
2504 after a qrefresh).
2513
2505
2514 Use :hg:`diff` if you only want to see the changes made since the
2506 Use :hg:`diff` if you only want to see the changes made since the
2515 last qrefresh, or :hg:`export qtip` if you want to see changes
2507 last qrefresh, or :hg:`export qtip` if you want to see changes
2516 made by the current patch without including changes made since the
2508 made by the current patch without including changes made since the
2517 qrefresh.
2509 qrefresh.
2518
2510
2519 Returns 0 on success.
2511 Returns 0 on success.
2520 """
2512 """
2521 repo.mq.diff(repo, pats, opts)
2513 repo.mq.diff(repo, pats, opts)
2522 return 0
2514 return 0
2523
2515
2524 @command('qfold',
2516 @command('qfold',
2525 [('e', 'edit', None, _('edit patch header')),
2517 [('e', 'edit', None, _('edit patch header')),
2526 ('k', 'keep', None, _('keep folded patch files')),
2518 ('k', 'keep', None, _('keep folded patch files')),
2527 ] + commands.commitopts,
2519 ] + commands.commitopts,
2528 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2520 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2529 def fold(ui, repo, *files, **opts):
2521 def fold(ui, repo, *files, **opts):
2530 """fold the named patches into the current patch
2522 """fold the named patches into the current patch
2531
2523
2532 Patches must not yet be applied. Each patch will be successively
2524 Patches must not yet be applied. Each patch will be successively
2533 applied to the current patch in the order given. If all the
2525 applied to the current patch in the order given. If all the
2534 patches apply successfully, the current patch will be refreshed
2526 patches apply successfully, the current patch will be refreshed
2535 with the new cumulative patch, and the folded patches will be
2527 with the new cumulative patch, and the folded patches will be
2536 deleted. With -k/--keep, the folded patch files will not be
2528 deleted. With -k/--keep, the folded patch files will not be
2537 removed afterwards.
2529 removed afterwards.
2538
2530
2539 The header for each folded patch will be concatenated with the
2531 The header for each folded patch will be concatenated with the
2540 current patch header, separated by a line of ``* * *``.
2532 current patch header, separated by a line of ``* * *``.
2541
2533
2542 Returns 0 on success."""
2534 Returns 0 on success."""
2543 q = repo.mq
2535 q = repo.mq
2544 if not files:
2536 if not files:
2545 raise util.Abort(_('qfold requires at least one patch name'))
2537 raise util.Abort(_('qfold requires at least one patch name'))
2546 if not q.checktoppatch(repo)[0]:
2538 if not q.checktoppatch(repo)[0]:
2547 raise util.Abort(_('no patches applied'))
2539 raise util.Abort(_('no patches applied'))
2548 q.checklocalchanges(repo)
2540 q.checklocalchanges(repo)
2549
2541
2550 message = cmdutil.logmessage(ui, opts)
2542 message = cmdutil.logmessage(ui, opts)
2551 if opts.get('edit'):
2543 if opts.get('edit'):
2552 if message:
2544 if message:
2553 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2545 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2554
2546
2555 parent = q.lookup('qtip')
2547 parent = q.lookup('qtip')
2556 patches = []
2548 patches = []
2557 messages = []
2549 messages = []
2558 for f in files:
2550 for f in files:
2559 p = q.lookup(f)
2551 p = q.lookup(f)
2560 if p in patches or p == parent:
2552 if p in patches or p == parent:
2561 ui.warn(_('skipping already folded patch %s\n') % p)
2553 ui.warn(_('skipping already folded patch %s\n') % p)
2562 if q.isapplied(p):
2554 if q.isapplied(p):
2563 raise util.Abort(_('qfold cannot fold already applied patch %s')
2555 raise util.Abort(_('qfold cannot fold already applied patch %s')
2564 % p)
2556 % p)
2565 patches.append(p)
2557 patches.append(p)
2566
2558
2567 for p in patches:
2559 for p in patches:
2568 if not message:
2560 if not message:
2569 ph = patchheader(q.join(p), q.plainmode)
2561 ph = patchheader(q.join(p), q.plainmode)
2570 if ph.message:
2562 if ph.message:
2571 messages.append(ph.message)
2563 messages.append(ph.message)
2572 pf = q.join(p)
2564 pf = q.join(p)
2573 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2565 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2574 if not patchsuccess:
2566 if not patchsuccess:
2575 raise util.Abort(_('error folding patch %s') % p)
2567 raise util.Abort(_('error folding patch %s') % p)
2576
2568
2577 if not message:
2569 if not message:
2578 ph = patchheader(q.join(parent), q.plainmode)
2570 ph = patchheader(q.join(parent), q.plainmode)
2579 message, user = ph.message, ph.user
2571 message, user = ph.message, ph.user
2580 for msg in messages:
2572 for msg in messages:
2581 message.append('* * *')
2573 message.append('* * *')
2582 message.extend(msg)
2574 message.extend(msg)
2583 message = '\n'.join(message)
2575 message = '\n'.join(message)
2584
2576
2585 if opts.get('edit'):
2577 if opts.get('edit'):
2586 message = ui.edit(message, user or ui.username())
2578 message = ui.edit(message, user or ui.username())
2587
2579
2588 diffopts = q.patchopts(q.diffopts(), *patches)
2580 diffopts = q.patchopts(q.diffopts(), *patches)
2589 wlock = repo.wlock()
2581 wlock = repo.wlock()
2590 try:
2582 try:
2591 q.refresh(repo, msg=message, git=diffopts.git)
2583 q.refresh(repo, msg=message, git=diffopts.git)
2592 q.delete(repo, patches, opts)
2584 q.delete(repo, patches, opts)
2593 q.savedirty()
2585 q.savedirty()
2594 finally:
2586 finally:
2595 wlock.release()
2587 wlock.release()
2596
2588
2597 @command("qgoto",
2589 @command("qgoto",
2598 [('', 'keep-changes', None,
2590 [('', 'keep-changes', None,
2599 _('tolerate non-conflicting local changes')),
2591 _('tolerate non-conflicting local changes')),
2600 ('f', 'force', None, _('overwrite any local changes')),
2592 ('f', 'force', None, _('overwrite any local changes')),
2601 ('', 'no-backup', None, _('do not save backup copies of files'))],
2593 ('', 'no-backup', None, _('do not save backup copies of files'))],
2602 _('hg qgoto [OPTION]... PATCH'))
2594 _('hg qgoto [OPTION]... PATCH'))
2603 def goto(ui, repo, patch, **opts):
2595 def goto(ui, repo, patch, **opts):
2604 '''push or pop patches until named patch is at top of stack
2596 '''push or pop patches until named patch is at top of stack
2605
2597
2606 Returns 0 on success.'''
2598 Returns 0 on success.'''
2607 opts = fixkeepchangesopts(ui, opts)
2599 opts = fixkeepchangesopts(ui, opts)
2608 q = repo.mq
2600 q = repo.mq
2609 patch = q.lookup(patch)
2601 patch = q.lookup(patch)
2610 nobackup = opts.get('no_backup')
2602 nobackup = opts.get('no_backup')
2611 keepchanges = opts.get('keep_changes')
2603 keepchanges = opts.get('keep_changes')
2612 if q.isapplied(patch):
2604 if q.isapplied(patch):
2613 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2605 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2614 keepchanges=keepchanges)
2606 keepchanges=keepchanges)
2615 else:
2607 else:
2616 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2608 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2617 keepchanges=keepchanges)
2609 keepchanges=keepchanges)
2618 q.savedirty()
2610 q.savedirty()
2619 return ret
2611 return ret
2620
2612
2621 @command("qguard",
2613 @command("qguard",
2622 [('l', 'list', None, _('list all patches and guards')),
2614 [('l', 'list', None, _('list all patches and guards')),
2623 ('n', 'none', None, _('drop all guards'))],
2615 ('n', 'none', None, _('drop all guards'))],
2624 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2616 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2625 def guard(ui, repo, *args, **opts):
2617 def guard(ui, repo, *args, **opts):
2626 '''set or print guards for a patch
2618 '''set or print guards for a patch
2627
2619
2628 Guards control whether a patch can be pushed. A patch with no
2620 Guards control whether a patch can be pushed. A patch with no
2629 guards is always pushed. A patch with a positive guard ("+foo") is
2621 guards is always pushed. A patch with a positive guard ("+foo") is
2630 pushed only if the :hg:`qselect` command has activated it. A patch with
2622 pushed only if the :hg:`qselect` command has activated it. A patch with
2631 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2623 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2632 has activated it.
2624 has activated it.
2633
2625
2634 With no arguments, print the currently active guards.
2626 With no arguments, print the currently active guards.
2635 With arguments, set guards for the named patch.
2627 With arguments, set guards for the named patch.
2636
2628
2637 .. note::
2629 .. note::
2638 Specifying negative guards now requires '--'.
2630 Specifying negative guards now requires '--'.
2639
2631
2640 To set guards on another patch::
2632 To set guards on another patch::
2641
2633
2642 hg qguard other.patch -- +2.6.17 -stable
2634 hg qguard other.patch -- +2.6.17 -stable
2643
2635
2644 Returns 0 on success.
2636 Returns 0 on success.
2645 '''
2637 '''
2646 def status(idx):
2638 def status(idx):
2647 guards = q.seriesguards[idx] or ['unguarded']
2639 guards = q.seriesguards[idx] or ['unguarded']
2648 if q.series[idx] in applied:
2640 if q.series[idx] in applied:
2649 state = 'applied'
2641 state = 'applied'
2650 elif q.pushable(idx)[0]:
2642 elif q.pushable(idx)[0]:
2651 state = 'unapplied'
2643 state = 'unapplied'
2652 else:
2644 else:
2653 state = 'guarded'
2645 state = 'guarded'
2654 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2646 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2655 ui.write('%s: ' % ui.label(q.series[idx], label))
2647 ui.write('%s: ' % ui.label(q.series[idx], label))
2656
2648
2657 for i, guard in enumerate(guards):
2649 for i, guard in enumerate(guards):
2658 if guard.startswith('+'):
2650 if guard.startswith('+'):
2659 ui.write(guard, label='qguard.positive')
2651 ui.write(guard, label='qguard.positive')
2660 elif guard.startswith('-'):
2652 elif guard.startswith('-'):
2661 ui.write(guard, label='qguard.negative')
2653 ui.write(guard, label='qguard.negative')
2662 else:
2654 else:
2663 ui.write(guard, label='qguard.unguarded')
2655 ui.write(guard, label='qguard.unguarded')
2664 if i != len(guards) - 1:
2656 if i != len(guards) - 1:
2665 ui.write(' ')
2657 ui.write(' ')
2666 ui.write('\n')
2658 ui.write('\n')
2667 q = repo.mq
2659 q = repo.mq
2668 applied = set(p.name for p in q.applied)
2660 applied = set(p.name for p in q.applied)
2669 patch = None
2661 patch = None
2670 args = list(args)
2662 args = list(args)
2671 if opts.get('list'):
2663 if opts.get('list'):
2672 if args or opts.get('none'):
2664 if args or opts.get('none'):
2673 raise util.Abort(_('cannot mix -l/--list with options or '
2665 raise util.Abort(_('cannot mix -l/--list with options or '
2674 'arguments'))
2666 'arguments'))
2675 for i in xrange(len(q.series)):
2667 for i in xrange(len(q.series)):
2676 status(i)
2668 status(i)
2677 return
2669 return
2678 if not args or args[0][0:1] in '-+':
2670 if not args or args[0][0:1] in '-+':
2679 if not q.applied:
2671 if not q.applied:
2680 raise util.Abort(_('no patches applied'))
2672 raise util.Abort(_('no patches applied'))
2681 patch = q.applied[-1].name
2673 patch = q.applied[-1].name
2682 if patch is None and args[0][0:1] not in '-+':
2674 if patch is None and args[0][0:1] not in '-+':
2683 patch = args.pop(0)
2675 patch = args.pop(0)
2684 if patch is None:
2676 if patch is None:
2685 raise util.Abort(_('no patch to work with'))
2677 raise util.Abort(_('no patch to work with'))
2686 if args or opts.get('none'):
2678 if args or opts.get('none'):
2687 idx = q.findseries(patch)
2679 idx = q.findseries(patch)
2688 if idx is None:
2680 if idx is None:
2689 raise util.Abort(_('no patch named %s') % patch)
2681 raise util.Abort(_('no patch named %s') % patch)
2690 q.setguards(idx, args)
2682 q.setguards(idx, args)
2691 q.savedirty()
2683 q.savedirty()
2692 else:
2684 else:
2693 status(q.series.index(q.lookup(patch)))
2685 status(q.series.index(q.lookup(patch)))
2694
2686
2695 @command("qheader", [], _('hg qheader [PATCH]'))
2687 @command("qheader", [], _('hg qheader [PATCH]'))
2696 def header(ui, repo, patch=None):
2688 def header(ui, repo, patch=None):
2697 """print the header of the topmost or specified patch
2689 """print the header of the topmost or specified patch
2698
2690
2699 Returns 0 on success."""
2691 Returns 0 on success."""
2700 q = repo.mq
2692 q = repo.mq
2701
2693
2702 if patch:
2694 if patch:
2703 patch = q.lookup(patch)
2695 patch = q.lookup(patch)
2704 else:
2696 else:
2705 if not q.applied:
2697 if not q.applied:
2706 ui.write(_('no patches applied\n'))
2698 ui.write(_('no patches applied\n'))
2707 return 1
2699 return 1
2708 patch = q.lookup('qtip')
2700 patch = q.lookup('qtip')
2709 ph = patchheader(q.join(patch), q.plainmode)
2701 ph = patchheader(q.join(patch), q.plainmode)
2710
2702
2711 ui.write('\n'.join(ph.message) + '\n')
2703 ui.write('\n'.join(ph.message) + '\n')
2712
2704
2713 def lastsavename(path):
2705 def lastsavename(path):
2714 (directory, base) = os.path.split(path)
2706 (directory, base) = os.path.split(path)
2715 names = os.listdir(directory)
2707 names = os.listdir(directory)
2716 namere = re.compile("%s.([0-9]+)" % base)
2708 namere = re.compile("%s.([0-9]+)" % base)
2717 maxindex = None
2709 maxindex = None
2718 maxname = None
2710 maxname = None
2719 for f in names:
2711 for f in names:
2720 m = namere.match(f)
2712 m = namere.match(f)
2721 if m:
2713 if m:
2722 index = int(m.group(1))
2714 index = int(m.group(1))
2723 if maxindex is None or index > maxindex:
2715 if maxindex is None or index > maxindex:
2724 maxindex = index
2716 maxindex = index
2725 maxname = f
2717 maxname = f
2726 if maxname:
2718 if maxname:
2727 return (os.path.join(directory, maxname), maxindex)
2719 return (os.path.join(directory, maxname), maxindex)
2728 return (None, None)
2720 return (None, None)
2729
2721
2730 def savename(path):
2722 def savename(path):
2731 (last, index) = lastsavename(path)
2723 (last, index) = lastsavename(path)
2732 if last is None:
2724 if last is None:
2733 index = 0
2725 index = 0
2734 newpath = path + ".%d" % (index + 1)
2726 newpath = path + ".%d" % (index + 1)
2735 return newpath
2727 return newpath
2736
2728
2737 @command("^qpush",
2729 @command("^qpush",
2738 [('', 'keep-changes', None,
2730 [('', 'keep-changes', None,
2739 _('tolerate non-conflicting local changes')),
2731 _('tolerate non-conflicting local changes')),
2740 ('f', 'force', None, _('apply on top of local changes')),
2732 ('f', 'force', None, _('apply on top of local changes')),
2741 ('e', 'exact', None,
2733 ('e', 'exact', None,
2742 _('apply the target patch to its recorded parent')),
2734 _('apply the target patch to its recorded parent')),
2743 ('l', 'list', None, _('list patch name in commit text')),
2735 ('l', 'list', None, _('list patch name in commit text')),
2744 ('a', 'all', None, _('apply all patches')),
2736 ('a', 'all', None, _('apply all patches')),
2745 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2737 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2746 ('n', 'name', '',
2738 ('n', 'name', '',
2747 _('merge queue name (DEPRECATED)'), _('NAME')),
2739 _('merge queue name (DEPRECATED)'), _('NAME')),
2748 ('', 'move', None,
2740 ('', 'move', None,
2749 _('reorder patch series and apply only the patch')),
2741 _('reorder patch series and apply only the patch')),
2750 ('', 'no-backup', None, _('do not save backup copies of files'))],
2742 ('', 'no-backup', None, _('do not save backup copies of files'))],
2751 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2743 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2752 def push(ui, repo, patch=None, **opts):
2744 def push(ui, repo, patch=None, **opts):
2753 """push the next patch onto the stack
2745 """push the next patch onto the stack
2754
2746
2755 By default, abort if the working directory contains uncommitted
2747 By default, abort if the working directory contains uncommitted
2756 changes. With --keep-changes, abort only if the uncommitted files
2748 changes. With --keep-changes, abort only if the uncommitted files
2757 overlap with patched files. With -f/--force, backup and patch over
2749 overlap with patched files. With -f/--force, backup and patch over
2758 uncommitted changes.
2750 uncommitted changes.
2759
2751
2760 Return 0 on success.
2752 Return 0 on success.
2761 """
2753 """
2762 q = repo.mq
2754 q = repo.mq
2763 mergeq = None
2755 mergeq = None
2764
2756
2765 opts = fixkeepchangesopts(ui, opts)
2757 opts = fixkeepchangesopts(ui, opts)
2766 if opts.get('merge'):
2758 if opts.get('merge'):
2767 if opts.get('name'):
2759 if opts.get('name'):
2768 newpath = repo.join(opts.get('name'))
2760 newpath = repo.join(opts.get('name'))
2769 else:
2761 else:
2770 newpath, i = lastsavename(q.path)
2762 newpath, i = lastsavename(q.path)
2771 if not newpath:
2763 if not newpath:
2772 ui.warn(_("no saved queues found, please use -n\n"))
2764 ui.warn(_("no saved queues found, please use -n\n"))
2773 return 1
2765 return 1
2774 mergeq = queue(ui, repo.path, newpath)
2766 mergeq = queue(ui, repo.path, newpath)
2775 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2767 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2776 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2768 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2777 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2769 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2778 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2770 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2779 keepchanges=opts.get('keep_changes'))
2771 keepchanges=opts.get('keep_changes'))
2780 return ret
2772 return ret
2781
2773
2782 @command("^qpop",
2774 @command("^qpop",
2783 [('a', 'all', None, _('pop all patches')),
2775 [('a', 'all', None, _('pop all patches')),
2784 ('n', 'name', '',
2776 ('n', 'name', '',
2785 _('queue name to pop (DEPRECATED)'), _('NAME')),
2777 _('queue name to pop (DEPRECATED)'), _('NAME')),
2786 ('', 'keep-changes', None,
2778 ('', 'keep-changes', None,
2787 _('tolerate non-conflicting local changes')),
2779 _('tolerate non-conflicting local changes')),
2788 ('f', 'force', None, _('forget any local changes to patched files')),
2780 ('f', 'force', None, _('forget any local changes to patched files')),
2789 ('', 'no-backup', None, _('do not save backup copies of files'))],
2781 ('', 'no-backup', None, _('do not save backup copies of files'))],
2790 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2782 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2791 def pop(ui, repo, patch=None, **opts):
2783 def pop(ui, repo, patch=None, **opts):
2792 """pop the current patch off the stack
2784 """pop the current patch off the stack
2793
2785
2794 Without argument, pops off the top of the patch stack. If given a
2786 Without argument, pops off the top of the patch stack. If given a
2795 patch name, keeps popping off patches until the named patch is at
2787 patch name, keeps popping off patches until the named patch is at
2796 the top of the stack.
2788 the top of the stack.
2797
2789
2798 By default, abort if the working directory contains uncommitted
2790 By default, abort if the working directory contains uncommitted
2799 changes. With --keep-changes, abort only if the uncommitted files
2791 changes. With --keep-changes, abort only if the uncommitted files
2800 overlap with patched files. With -f/--force, backup and discard
2792 overlap with patched files. With -f/--force, backup and discard
2801 changes made to such files.
2793 changes made to such files.
2802
2794
2803 Return 0 on success.
2795 Return 0 on success.
2804 """
2796 """
2805 opts = fixkeepchangesopts(ui, opts)
2797 opts = fixkeepchangesopts(ui, opts)
2806 localupdate = True
2798 localupdate = True
2807 if opts.get('name'):
2799 if opts.get('name'):
2808 q = queue(ui, repo.path, repo.join(opts.get('name')))
2800 q = queue(ui, repo.path, repo.join(opts.get('name')))
2809 ui.warn(_('using patch queue: %s\n') % q.path)
2801 ui.warn(_('using patch queue: %s\n') % q.path)
2810 localupdate = False
2802 localupdate = False
2811 else:
2803 else:
2812 q = repo.mq
2804 q = repo.mq
2813 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2805 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2814 all=opts.get('all'), nobackup=opts.get('no_backup'),
2806 all=opts.get('all'), nobackup=opts.get('no_backup'),
2815 keepchanges=opts.get('keep_changes'))
2807 keepchanges=opts.get('keep_changes'))
2816 q.savedirty()
2808 q.savedirty()
2817 return ret
2809 return ret
2818
2810
2819 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2811 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2820 def rename(ui, repo, patch, name=None, **opts):
2812 def rename(ui, repo, patch, name=None, **opts):
2821 """rename a patch
2813 """rename a patch
2822
2814
2823 With one argument, renames the current patch to PATCH1.
2815 With one argument, renames the current patch to PATCH1.
2824 With two arguments, renames PATCH1 to PATCH2.
2816 With two arguments, renames PATCH1 to PATCH2.
2825
2817
2826 Returns 0 on success."""
2818 Returns 0 on success."""
2827 q = repo.mq
2819 q = repo.mq
2828 if not name:
2820 if not name:
2829 name = patch
2821 name = patch
2830 patch = None
2822 patch = None
2831
2823
2832 if patch:
2824 if patch:
2833 patch = q.lookup(patch)
2825 patch = q.lookup(patch)
2834 else:
2826 else:
2835 if not q.applied:
2827 if not q.applied:
2836 ui.write(_('no patches applied\n'))
2828 ui.write(_('no patches applied\n'))
2837 return
2829 return
2838 patch = q.lookup('qtip')
2830 patch = q.lookup('qtip')
2839 absdest = q.join(name)
2831 absdest = q.join(name)
2840 if os.path.isdir(absdest):
2832 if os.path.isdir(absdest):
2841 name = normname(os.path.join(name, os.path.basename(patch)))
2833 name = normname(os.path.join(name, os.path.basename(patch)))
2842 absdest = q.join(name)
2834 absdest = q.join(name)
2843 q.checkpatchname(name)
2835 q.checkpatchname(name)
2844
2836
2845 ui.note(_('renaming %s to %s\n') % (patch, name))
2837 ui.note(_('renaming %s to %s\n') % (patch, name))
2846 i = q.findseries(patch)
2838 i = q.findseries(patch)
2847 guards = q.guard_re.findall(q.fullseries[i])
2839 guards = q.guard_re.findall(q.fullseries[i])
2848 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2840 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2849 q.parseseries()
2841 q.parseseries()
2850 q.seriesdirty = True
2842 q.seriesdirty = True
2851
2843
2852 info = q.isapplied(patch)
2844 info = q.isapplied(patch)
2853 if info:
2845 if info:
2854 q.applied[info[0]] = statusentry(info[1], name)
2846 q.applied[info[0]] = statusentry(info[1], name)
2855 q.applieddirty = True
2847 q.applieddirty = True
2856
2848
2857 destdir = os.path.dirname(absdest)
2849 destdir = os.path.dirname(absdest)
2858 if not os.path.isdir(destdir):
2850 if not os.path.isdir(destdir):
2859 os.makedirs(destdir)
2851 os.makedirs(destdir)
2860 util.rename(q.join(patch), absdest)
2852 util.rename(q.join(patch), absdest)
2861 r = q.qrepo()
2853 r = q.qrepo()
2862 if r and patch in r.dirstate:
2854 if r and patch in r.dirstate:
2863 wctx = r[None]
2855 wctx = r[None]
2864 wlock = r.wlock()
2856 wlock = r.wlock()
2865 try:
2857 try:
2866 if r.dirstate[patch] == 'a':
2858 if r.dirstate[patch] == 'a':
2867 r.dirstate.drop(patch)
2859 r.dirstate.drop(patch)
2868 r.dirstate.add(name)
2860 r.dirstate.add(name)
2869 else:
2861 else:
2870 wctx.copy(patch, name)
2862 wctx.copy(patch, name)
2871 wctx.forget([patch])
2863 wctx.forget([patch])
2872 finally:
2864 finally:
2873 wlock.release()
2865 wlock.release()
2874
2866
2875 q.savedirty()
2867 q.savedirty()
2876
2868
2877 @command("qrestore",
2869 @command("qrestore",
2878 [('d', 'delete', None, _('delete save entry')),
2870 [('d', 'delete', None, _('delete save entry')),
2879 ('u', 'update', None, _('update queue working directory'))],
2871 ('u', 'update', None, _('update queue working directory'))],
2880 _('hg qrestore [-d] [-u] REV'))
2872 _('hg qrestore [-d] [-u] REV'))
2881 def restore(ui, repo, rev, **opts):
2873 def restore(ui, repo, rev, **opts):
2882 """restore the queue state saved by a revision (DEPRECATED)
2874 """restore the queue state saved by a revision (DEPRECATED)
2883
2875
2884 This command is deprecated, use :hg:`rebase` instead."""
2876 This command is deprecated, use :hg:`rebase` instead."""
2885 rev = repo.lookup(rev)
2877 rev = repo.lookup(rev)
2886 q = repo.mq
2878 q = repo.mq
2887 q.restore(repo, rev, delete=opts.get('delete'),
2879 q.restore(repo, rev, delete=opts.get('delete'),
2888 qupdate=opts.get('update'))
2880 qupdate=opts.get('update'))
2889 q.savedirty()
2881 q.savedirty()
2890 return 0
2882 return 0
2891
2883
2892 @command("qsave",
2884 @command("qsave",
2893 [('c', 'copy', None, _('copy patch directory')),
2885 [('c', 'copy', None, _('copy patch directory')),
2894 ('n', 'name', '',
2886 ('n', 'name', '',
2895 _('copy directory name'), _('NAME')),
2887 _('copy directory name'), _('NAME')),
2896 ('e', 'empty', None, _('clear queue status file')),
2888 ('e', 'empty', None, _('clear queue status file')),
2897 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2889 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2898 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2890 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2899 def save(ui, repo, **opts):
2891 def save(ui, repo, **opts):
2900 """save current queue state (DEPRECATED)
2892 """save current queue state (DEPRECATED)
2901
2893
2902 This command is deprecated, use :hg:`rebase` instead."""
2894 This command is deprecated, use :hg:`rebase` instead."""
2903 q = repo.mq
2895 q = repo.mq
2904 message = cmdutil.logmessage(ui, opts)
2896 message = cmdutil.logmessage(ui, opts)
2905 ret = q.save(repo, msg=message)
2897 ret = q.save(repo, msg=message)
2906 if ret:
2898 if ret:
2907 return ret
2899 return ret
2908 q.savedirty() # save to .hg/patches before copying
2900 q.savedirty() # save to .hg/patches before copying
2909 if opts.get('copy'):
2901 if opts.get('copy'):
2910 path = q.path
2902 path = q.path
2911 if opts.get('name'):
2903 if opts.get('name'):
2912 newpath = os.path.join(q.basepath, opts.get('name'))
2904 newpath = os.path.join(q.basepath, opts.get('name'))
2913 if os.path.exists(newpath):
2905 if os.path.exists(newpath):
2914 if not os.path.isdir(newpath):
2906 if not os.path.isdir(newpath):
2915 raise util.Abort(_('destination %s exists and is not '
2907 raise util.Abort(_('destination %s exists and is not '
2916 'a directory') % newpath)
2908 'a directory') % newpath)
2917 if not opts.get('force'):
2909 if not opts.get('force'):
2918 raise util.Abort(_('destination %s exists, '
2910 raise util.Abort(_('destination %s exists, '
2919 'use -f to force') % newpath)
2911 'use -f to force') % newpath)
2920 else:
2912 else:
2921 newpath = savename(path)
2913 newpath = savename(path)
2922 ui.warn(_("copy %s to %s\n") % (path, newpath))
2914 ui.warn(_("copy %s to %s\n") % (path, newpath))
2923 util.copyfiles(path, newpath)
2915 util.copyfiles(path, newpath)
2924 if opts.get('empty'):
2916 if opts.get('empty'):
2925 del q.applied[:]
2917 del q.applied[:]
2926 q.applieddirty = True
2918 q.applieddirty = True
2927 q.savedirty()
2919 q.savedirty()
2928 return 0
2920 return 0
2929
2921
2930 @command("strip",
2922 @command("strip",
2931 [
2923 [
2932 ('r', 'rev', [], _('strip specified revision (optional, '
2924 ('r', 'rev', [], _('strip specified revision (optional, '
2933 'can specify revisions without this '
2925 'can specify revisions without this '
2934 'option)'), _('REV')),
2926 'option)'), _('REV')),
2935 ('f', 'force', None, _('force removal of changesets, discard '
2927 ('f', 'force', None, _('force removal of changesets, discard '
2936 'uncommitted changes (no backup)')),
2928 'uncommitted changes (no backup)')),
2937 ('b', 'backup', None, _('bundle only changesets with local revision'
2929 ('b', 'backup', None, _('bundle only changesets with local revision'
2938 ' number greater than REV which are not'
2930 ' number greater than REV which are not'
2939 ' descendants of REV (DEPRECATED)')),
2931 ' descendants of REV (DEPRECATED)')),
2940 ('', 'no-backup', None, _('no backups')),
2932 ('', 'no-backup', None, _('no backups')),
2941 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2933 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2942 ('n', '', None, _('ignored (DEPRECATED)')),
2934 ('n', '', None, _('ignored (DEPRECATED)')),
2943 ('k', 'keep', None, _("do not modify working copy during strip")),
2935 ('k', 'keep', None, _("do not modify working copy during strip")),
2944 ('B', 'bookmark', '', _("remove revs only reachable from given"
2936 ('B', 'bookmark', '', _("remove revs only reachable from given"
2945 " bookmark"))],
2937 " bookmark"))],
2946 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
2938 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
2947 def strip(ui, repo, *revs, **opts):
2939 def strip(ui, repo, *revs, **opts):
2948 """strip changesets and all their descendants from the repository
2940 """strip changesets and all their descendants from the repository
2949
2941
2950 The strip command removes the specified changesets and all their
2942 The strip command removes the specified changesets and all their
2951 descendants. If the working directory has uncommitted changes, the
2943 descendants. If the working directory has uncommitted changes, the
2952 operation is aborted unless the --force flag is supplied, in which
2944 operation is aborted unless the --force flag is supplied, in which
2953 case changes will be discarded.
2945 case changes will be discarded.
2954
2946
2955 If a parent of the working directory is stripped, then the working
2947 If a parent of the working directory is stripped, then the working
2956 directory will automatically be updated to the most recent
2948 directory will automatically be updated to the most recent
2957 available ancestor of the stripped parent after the operation
2949 available ancestor of the stripped parent after the operation
2958 completes.
2950 completes.
2959
2951
2960 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2952 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2961 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2953 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2962 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2954 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2963 where BUNDLE is the bundle file created by the strip. Note that
2955 where BUNDLE is the bundle file created by the strip. Note that
2964 the local revision numbers will in general be different after the
2956 the local revision numbers will in general be different after the
2965 restore.
2957 restore.
2966
2958
2967 Use the --no-backup option to discard the backup bundle once the
2959 Use the --no-backup option to discard the backup bundle once the
2968 operation completes.
2960 operation completes.
2969
2961
2970 Strip is not a history-rewriting operation and can be used on
2962 Strip is not a history-rewriting operation and can be used on
2971 changesets in the public phase. But if the stripped changesets have
2963 changesets in the public phase. But if the stripped changesets have
2972 been pushed to a remote repository you will likely pull them again.
2964 been pushed to a remote repository you will likely pull them again.
2973
2965
2974 Return 0 on success.
2966 Return 0 on success.
2975 """
2967 """
2976 backup = 'all'
2968 backup = 'all'
2977 if opts.get('backup'):
2969 if opts.get('backup'):
2978 backup = 'strip'
2970 backup = 'strip'
2979 elif opts.get('no_backup') or opts.get('nobackup'):
2971 elif opts.get('no_backup') or opts.get('nobackup'):
2980 backup = 'none'
2972 backup = 'none'
2981
2973
2982 cl = repo.changelog
2974 cl = repo.changelog
2983 revs = list(revs) + opts.get('rev')
2975 revs = list(revs) + opts.get('rev')
2984 revs = set(scmutil.revrange(repo, revs))
2976 revs = set(scmutil.revrange(repo, revs))
2985
2977
2986 if opts.get('bookmark'):
2978 if opts.get('bookmark'):
2987 mark = opts.get('bookmark')
2979 mark = opts.get('bookmark')
2988 marks = repo._bookmarks
2980 marks = repo._bookmarks
2989 if mark not in marks:
2981 if mark not in marks:
2990 raise util.Abort(_("bookmark '%s' not found") % mark)
2982 raise util.Abort(_("bookmark '%s' not found") % mark)
2991
2983
2992 # If the requested bookmark is not the only one pointing to a
2984 # If the requested bookmark is not the only one pointing to a
2993 # a revision we have to only delete the bookmark and not strip
2985 # a revision we have to only delete the bookmark and not strip
2994 # anything. revsets cannot detect that case.
2986 # anything. revsets cannot detect that case.
2995 uniquebm = True
2987 uniquebm = True
2996 for m, n in marks.iteritems():
2988 for m, n in marks.iteritems():
2997 if m != mark and n == repo[mark].node():
2989 if m != mark and n == repo[mark].node():
2998 uniquebm = False
2990 uniquebm = False
2999 break
2991 break
3000 if uniquebm:
2992 if uniquebm:
3001 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
2993 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
3002 "ancestors(head() and not bookmark(%s)) - "
2994 "ancestors(head() and not bookmark(%s)) - "
3003 "ancestors(bookmark() and not bookmark(%s))",
2995 "ancestors(bookmark() and not bookmark(%s))",
3004 mark, mark, mark)
2996 mark, mark, mark)
3005 revs.update(set(rsrevs))
2997 revs.update(set(rsrevs))
3006 if not revs:
2998 if not revs:
3007 del marks[mark]
2999 del marks[mark]
3008 marks.write()
3000 marks.write()
3009 ui.write(_("bookmark '%s' deleted\n") % mark)
3001 ui.write(_("bookmark '%s' deleted\n") % mark)
3010
3002
3011 if not revs:
3003 if not revs:
3012 raise util.Abort(_('empty revision set'))
3004 raise util.Abort(_('empty revision set'))
3013
3005
3014 descendants = set(cl.descendants(revs))
3006 descendants = set(cl.descendants(revs))
3015 strippedrevs = revs.union(descendants)
3007 strippedrevs = revs.union(descendants)
3016 roots = revs.difference(descendants)
3008 roots = revs.difference(descendants)
3017
3009
3018 update = False
3010 update = False
3019 # if one of the wdir parent is stripped we'll need
3011 # if one of the wdir parent is stripped we'll need
3020 # to update away to an earlier revision
3012 # to update away to an earlier revision
3021 for p in repo.dirstate.parents():
3013 for p in repo.dirstate.parents():
3022 if p != nullid and cl.rev(p) in strippedrevs:
3014 if p != nullid and cl.rev(p) in strippedrevs:
3023 update = True
3015 update = True
3024 break
3016 break
3025
3017
3026 rootnodes = set(cl.node(r) for r in roots)
3018 rootnodes = set(cl.node(r) for r in roots)
3027
3019
3028 q = repo.mq
3020 q = repo.mq
3029 if q.applied:
3021 if q.applied:
3030 # refresh queue state if we're about to strip
3022 # refresh queue state if we're about to strip
3031 # applied patches
3023 # applied patches
3032 if cl.rev(repo.lookup('qtip')) in strippedrevs:
3024 if cl.rev(repo.lookup('qtip')) in strippedrevs:
3033 q.applieddirty = True
3025 q.applieddirty = True
3034 start = 0
3026 start = 0
3035 end = len(q.applied)
3027 end = len(q.applied)
3036 for i, statusentry in enumerate(q.applied):
3028 for i, statusentry in enumerate(q.applied):
3037 if statusentry.node in rootnodes:
3029 if statusentry.node in rootnodes:
3038 # if one of the stripped roots is an applied
3030 # if one of the stripped roots is an applied
3039 # patch, only part of the queue is stripped
3031 # patch, only part of the queue is stripped
3040 start = i
3032 start = i
3041 break
3033 break
3042 del q.applied[start:end]
3034 del q.applied[start:end]
3043 q.savedirty()
3035 q.savedirty()
3044
3036
3045 revs = list(rootnodes)
3037 revs = list(rootnodes)
3046 if update and opts.get('keep'):
3038 if update and opts.get('keep'):
3047 wlock = repo.wlock()
3039 wlock = repo.wlock()
3048 try:
3040 try:
3049 urev = repo.mq.qparents(repo, revs[0])
3041 urev = repo.mq.qparents(repo, revs[0])
3050 repo.dirstate.rebuild(urev, repo[urev].manifest())
3042 repo.dirstate.rebuild(urev, repo[urev].manifest())
3051 repo.dirstate.write()
3043 repo.dirstate.write()
3052 update = False
3044 update = False
3053 finally:
3045 finally:
3054 wlock.release()
3046 wlock.release()
3055
3047
3056 if opts.get('bookmark'):
3048 if opts.get('bookmark'):
3057 del marks[mark]
3049 del marks[mark]
3058 marks.write()
3050 marks.write()
3059 ui.write(_("bookmark '%s' deleted\n") % mark)
3051 ui.write(_("bookmark '%s' deleted\n") % mark)
3060
3052
3061 repo.mq.strip(repo, revs, backup=backup, update=update,
3053 repo.mq.strip(repo, revs, backup=backup, update=update,
3062 force=opts.get('force'))
3054 force=opts.get('force'))
3063
3055
3064 return 0
3056 return 0
3065
3057
3066 @command("qselect",
3058 @command("qselect",
3067 [('n', 'none', None, _('disable all guards')),
3059 [('n', 'none', None, _('disable all guards')),
3068 ('s', 'series', None, _('list all guards in series file')),
3060 ('s', 'series', None, _('list all guards in series file')),
3069 ('', 'pop', None, _('pop to before first guarded applied patch')),
3061 ('', 'pop', None, _('pop to before first guarded applied patch')),
3070 ('', 'reapply', None, _('pop, then reapply patches'))],
3062 ('', 'reapply', None, _('pop, then reapply patches'))],
3071 _('hg qselect [OPTION]... [GUARD]...'))
3063 _('hg qselect [OPTION]... [GUARD]...'))
3072 def select(ui, repo, *args, **opts):
3064 def select(ui, repo, *args, **opts):
3073 '''set or print guarded patches to push
3065 '''set or print guarded patches to push
3074
3066
3075 Use the :hg:`qguard` command to set or print guards on patch, then use
3067 Use the :hg:`qguard` command to set or print guards on patch, then use
3076 qselect to tell mq which guards to use. A patch will be pushed if
3068 qselect to tell mq which guards to use. A patch will be pushed if
3077 it has no guards or any positive guards match the currently
3069 it has no guards or any positive guards match the currently
3078 selected guard, but will not be pushed if any negative guards
3070 selected guard, but will not be pushed if any negative guards
3079 match the current guard. For example::
3071 match the current guard. For example::
3080
3072
3081 qguard foo.patch -- -stable (negative guard)
3073 qguard foo.patch -- -stable (negative guard)
3082 qguard bar.patch +stable (positive guard)
3074 qguard bar.patch +stable (positive guard)
3083 qselect stable
3075 qselect stable
3084
3076
3085 This activates the "stable" guard. mq will skip foo.patch (because
3077 This activates the "stable" guard. mq will skip foo.patch (because
3086 it has a negative match) but push bar.patch (because it has a
3078 it has a negative match) but push bar.patch (because it has a
3087 positive match).
3079 positive match).
3088
3080
3089 With no arguments, prints the currently active guards.
3081 With no arguments, prints the currently active guards.
3090 With one argument, sets the active guard.
3082 With one argument, sets the active guard.
3091
3083
3092 Use -n/--none to deactivate guards (no other arguments needed).
3084 Use -n/--none to deactivate guards (no other arguments needed).
3093 When no guards are active, patches with positive guards are
3085 When no guards are active, patches with positive guards are
3094 skipped and patches with negative guards are pushed.
3086 skipped and patches with negative guards are pushed.
3095
3087
3096 qselect can change the guards on applied patches. It does not pop
3088 qselect can change the guards on applied patches. It does not pop
3097 guarded patches by default. Use --pop to pop back to the last
3089 guarded patches by default. Use --pop to pop back to the last
3098 applied patch that is not guarded. Use --reapply (which implies
3090 applied patch that is not guarded. Use --reapply (which implies
3099 --pop) to push back to the current patch afterwards, but skip
3091 --pop) to push back to the current patch afterwards, but skip
3100 guarded patches.
3092 guarded patches.
3101
3093
3102 Use -s/--series to print a list of all guards in the series file
3094 Use -s/--series to print a list of all guards in the series file
3103 (no other arguments needed). Use -v for more information.
3095 (no other arguments needed). Use -v for more information.
3104
3096
3105 Returns 0 on success.'''
3097 Returns 0 on success.'''
3106
3098
3107 q = repo.mq
3099 q = repo.mq
3108 guards = q.active()
3100 guards = q.active()
3109 if args or opts.get('none'):
3101 if args or opts.get('none'):
3110 old_unapplied = q.unapplied(repo)
3102 old_unapplied = q.unapplied(repo)
3111 old_guarded = [i for i in xrange(len(q.applied)) if
3103 old_guarded = [i for i in xrange(len(q.applied)) if
3112 not q.pushable(i)[0]]
3104 not q.pushable(i)[0]]
3113 q.setactive(args)
3105 q.setactive(args)
3114 q.savedirty()
3106 q.savedirty()
3115 if not args:
3107 if not args:
3116 ui.status(_('guards deactivated\n'))
3108 ui.status(_('guards deactivated\n'))
3117 if not opts.get('pop') and not opts.get('reapply'):
3109 if not opts.get('pop') and not opts.get('reapply'):
3118 unapplied = q.unapplied(repo)
3110 unapplied = q.unapplied(repo)
3119 guarded = [i for i in xrange(len(q.applied))
3111 guarded = [i for i in xrange(len(q.applied))
3120 if not q.pushable(i)[0]]
3112 if not q.pushable(i)[0]]
3121 if len(unapplied) != len(old_unapplied):
3113 if len(unapplied) != len(old_unapplied):
3122 ui.status(_('number of unguarded, unapplied patches has '
3114 ui.status(_('number of unguarded, unapplied patches has '
3123 'changed from %d to %d\n') %
3115 'changed from %d to %d\n') %
3124 (len(old_unapplied), len(unapplied)))
3116 (len(old_unapplied), len(unapplied)))
3125 if len(guarded) != len(old_guarded):
3117 if len(guarded) != len(old_guarded):
3126 ui.status(_('number of guarded, applied patches has changed '
3118 ui.status(_('number of guarded, applied patches has changed '
3127 'from %d to %d\n') %
3119 'from %d to %d\n') %
3128 (len(old_guarded), len(guarded)))
3120 (len(old_guarded), len(guarded)))
3129 elif opts.get('series'):
3121 elif opts.get('series'):
3130 guards = {}
3122 guards = {}
3131 noguards = 0
3123 noguards = 0
3132 for gs in q.seriesguards:
3124 for gs in q.seriesguards:
3133 if not gs:
3125 if not gs:
3134 noguards += 1
3126 noguards += 1
3135 for g in gs:
3127 for g in gs:
3136 guards.setdefault(g, 0)
3128 guards.setdefault(g, 0)
3137 guards[g] += 1
3129 guards[g] += 1
3138 if ui.verbose:
3130 if ui.verbose:
3139 guards['NONE'] = noguards
3131 guards['NONE'] = noguards
3140 guards = guards.items()
3132 guards = guards.items()
3141 guards.sort(key=lambda x: x[0][1:])
3133 guards.sort(key=lambda x: x[0][1:])
3142 if guards:
3134 if guards:
3143 ui.note(_('guards in series file:\n'))
3135 ui.note(_('guards in series file:\n'))
3144 for guard, count in guards:
3136 for guard, count in guards:
3145 ui.note('%2d ' % count)
3137 ui.note('%2d ' % count)
3146 ui.write(guard, '\n')
3138 ui.write(guard, '\n')
3147 else:
3139 else:
3148 ui.note(_('no guards in series file\n'))
3140 ui.note(_('no guards in series file\n'))
3149 else:
3141 else:
3150 if guards:
3142 if guards:
3151 ui.note(_('active guards:\n'))
3143 ui.note(_('active guards:\n'))
3152 for g in guards:
3144 for g in guards:
3153 ui.write(g, '\n')
3145 ui.write(g, '\n')
3154 else:
3146 else:
3155 ui.write(_('no active guards\n'))
3147 ui.write(_('no active guards\n'))
3156 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3148 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3157 popped = False
3149 popped = False
3158 if opts.get('pop') or opts.get('reapply'):
3150 if opts.get('pop') or opts.get('reapply'):
3159 for i in xrange(len(q.applied)):
3151 for i in xrange(len(q.applied)):
3160 pushable, reason = q.pushable(i)
3152 pushable, reason = q.pushable(i)
3161 if not pushable:
3153 if not pushable:
3162 ui.status(_('popping guarded patches\n'))
3154 ui.status(_('popping guarded patches\n'))
3163 popped = True
3155 popped = True
3164 if i == 0:
3156 if i == 0:
3165 q.pop(repo, all=True)
3157 q.pop(repo, all=True)
3166 else:
3158 else:
3167 q.pop(repo, str(i - 1))
3159 q.pop(repo, str(i - 1))
3168 break
3160 break
3169 if popped:
3161 if popped:
3170 try:
3162 try:
3171 if reapply:
3163 if reapply:
3172 ui.status(_('reapplying unguarded patches\n'))
3164 ui.status(_('reapplying unguarded patches\n'))
3173 q.push(repo, reapply)
3165 q.push(repo, reapply)
3174 finally:
3166 finally:
3175 q.savedirty()
3167 q.savedirty()
3176
3168
3177 @command("qfinish",
3169 @command("qfinish",
3178 [('a', 'applied', None, _('finish all applied changesets'))],
3170 [('a', 'applied', None, _('finish all applied changesets'))],
3179 _('hg qfinish [-a] [REV]...'))
3171 _('hg qfinish [-a] [REV]...'))
3180 def finish(ui, repo, *revrange, **opts):
3172 def finish(ui, repo, *revrange, **opts):
3181 """move applied patches into repository history
3173 """move applied patches into repository history
3182
3174
3183 Finishes the specified revisions (corresponding to applied
3175 Finishes the specified revisions (corresponding to applied
3184 patches) by moving them out of mq control into regular repository
3176 patches) by moving them out of mq control into regular repository
3185 history.
3177 history.
3186
3178
3187 Accepts a revision range or the -a/--applied option. If --applied
3179 Accepts a revision range or the -a/--applied option. If --applied
3188 is specified, all applied mq revisions are removed from mq
3180 is specified, all applied mq revisions are removed from mq
3189 control. Otherwise, the given revisions must be at the base of the
3181 control. Otherwise, the given revisions must be at the base of the
3190 stack of applied patches.
3182 stack of applied patches.
3191
3183
3192 This can be especially useful if your changes have been applied to
3184 This can be especially useful if your changes have been applied to
3193 an upstream repository, or if you are about to push your changes
3185 an upstream repository, or if you are about to push your changes
3194 to upstream.
3186 to upstream.
3195
3187
3196 Returns 0 on success.
3188 Returns 0 on success.
3197 """
3189 """
3198 if not opts.get('applied') and not revrange:
3190 if not opts.get('applied') and not revrange:
3199 raise util.Abort(_('no revisions specified'))
3191 raise util.Abort(_('no revisions specified'))
3200 elif opts.get('applied'):
3192 elif opts.get('applied'):
3201 revrange = ('qbase::qtip',) + revrange
3193 revrange = ('qbase::qtip',) + revrange
3202
3194
3203 q = repo.mq
3195 q = repo.mq
3204 if not q.applied:
3196 if not q.applied:
3205 ui.status(_('no patches applied\n'))
3197 ui.status(_('no patches applied\n'))
3206 return 0
3198 return 0
3207
3199
3208 revs = scmutil.revrange(repo, revrange)
3200 revs = scmutil.revrange(repo, revrange)
3209 if repo['.'].rev() in revs and repo[None].files():
3201 if repo['.'].rev() in revs and repo[None].files():
3210 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3202 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3211 # queue.finish may changes phases but leave the responsibility to lock the
3203 # queue.finish may changes phases but leave the responsibility to lock the
3212 # repo to the caller to avoid deadlock with wlock. This command code is
3204 # repo to the caller to avoid deadlock with wlock. This command code is
3213 # responsibility for this locking.
3205 # responsibility for this locking.
3214 lock = repo.lock()
3206 lock = repo.lock()
3215 try:
3207 try:
3216 q.finish(repo, revs)
3208 q.finish(repo, revs)
3217 q.savedirty()
3209 q.savedirty()
3218 finally:
3210 finally:
3219 lock.release()
3211 lock.release()
3220 return 0
3212 return 0
3221
3213
3222 @command("qqueue",
3214 @command("qqueue",
3223 [('l', 'list', False, _('list all available queues')),
3215 [('l', 'list', False, _('list all available queues')),
3224 ('', 'active', False, _('print name of active queue')),
3216 ('', 'active', False, _('print name of active queue')),
3225 ('c', 'create', False, _('create new queue')),
3217 ('c', 'create', False, _('create new queue')),
3226 ('', 'rename', False, _('rename active queue')),
3218 ('', 'rename', False, _('rename active queue')),
3227 ('', 'delete', False, _('delete reference to queue')),
3219 ('', 'delete', False, _('delete reference to queue')),
3228 ('', 'purge', False, _('delete queue, and remove patch dir')),
3220 ('', 'purge', False, _('delete queue, and remove patch dir')),
3229 ],
3221 ],
3230 _('[OPTION] [QUEUE]'))
3222 _('[OPTION] [QUEUE]'))
3231 def qqueue(ui, repo, name=None, **opts):
3223 def qqueue(ui, repo, name=None, **opts):
3232 '''manage multiple patch queues
3224 '''manage multiple patch queues
3233
3225
3234 Supports switching between different patch queues, as well as creating
3226 Supports switching between different patch queues, as well as creating
3235 new patch queues and deleting existing ones.
3227 new patch queues and deleting existing ones.
3236
3228
3237 Omitting a queue name or specifying -l/--list will show you the registered
3229 Omitting a queue name or specifying -l/--list will show you the registered
3238 queues - by default the "normal" patches queue is registered. The currently
3230 queues - by default the "normal" patches queue is registered. The currently
3239 active queue will be marked with "(active)". Specifying --active will print
3231 active queue will be marked with "(active)". Specifying --active will print
3240 only the name of the active queue.
3232 only the name of the active queue.
3241
3233
3242 To create a new queue, use -c/--create. The queue is automatically made
3234 To create a new queue, use -c/--create. The queue is automatically made
3243 active, except in the case where there are applied patches from the
3235 active, except in the case where there are applied patches from the
3244 currently active queue in the repository. Then the queue will only be
3236 currently active queue in the repository. Then the queue will only be
3245 created and switching will fail.
3237 created and switching will fail.
3246
3238
3247 To delete an existing queue, use --delete. You cannot delete the currently
3239 To delete an existing queue, use --delete. You cannot delete the currently
3248 active queue.
3240 active queue.
3249
3241
3250 Returns 0 on success.
3242 Returns 0 on success.
3251 '''
3243 '''
3252 q = repo.mq
3244 q = repo.mq
3253 _defaultqueue = 'patches'
3245 _defaultqueue = 'patches'
3254 _allqueues = 'patches.queues'
3246 _allqueues = 'patches.queues'
3255 _activequeue = 'patches.queue'
3247 _activequeue = 'patches.queue'
3256
3248
3257 def _getcurrent():
3249 def _getcurrent():
3258 cur = os.path.basename(q.path)
3250 cur = os.path.basename(q.path)
3259 if cur.startswith('patches-'):
3251 if cur.startswith('patches-'):
3260 cur = cur[8:]
3252 cur = cur[8:]
3261 return cur
3253 return cur
3262
3254
3263 def _noqueues():
3255 def _noqueues():
3264 try:
3256 try:
3265 fh = repo.opener(_allqueues, 'r')
3257 fh = repo.opener(_allqueues, 'r')
3266 fh.close()
3258 fh.close()
3267 except IOError:
3259 except IOError:
3268 return True
3260 return True
3269
3261
3270 return False
3262 return False
3271
3263
3272 def _getqueues():
3264 def _getqueues():
3273 current = _getcurrent()
3265 current = _getcurrent()
3274
3266
3275 try:
3267 try:
3276 fh = repo.opener(_allqueues, 'r')
3268 fh = repo.opener(_allqueues, 'r')
3277 queues = [queue.strip() for queue in fh if queue.strip()]
3269 queues = [queue.strip() for queue in fh if queue.strip()]
3278 fh.close()
3270 fh.close()
3279 if current not in queues:
3271 if current not in queues:
3280 queues.append(current)
3272 queues.append(current)
3281 except IOError:
3273 except IOError:
3282 queues = [_defaultqueue]
3274 queues = [_defaultqueue]
3283
3275
3284 return sorted(queues)
3276 return sorted(queues)
3285
3277
3286 def _setactive(name):
3278 def _setactive(name):
3287 if q.applied:
3279 if q.applied:
3288 raise util.Abort(_('new queue created, but cannot make active '
3280 raise util.Abort(_('new queue created, but cannot make active '
3289 'as patches are applied'))
3281 'as patches are applied'))
3290 _setactivenocheck(name)
3282 _setactivenocheck(name)
3291
3283
3292 def _setactivenocheck(name):
3284 def _setactivenocheck(name):
3293 fh = repo.opener(_activequeue, 'w')
3285 fh = repo.opener(_activequeue, 'w')
3294 if name != 'patches':
3286 if name != 'patches':
3295 fh.write(name)
3287 fh.write(name)
3296 fh.close()
3288 fh.close()
3297
3289
3298 def _addqueue(name):
3290 def _addqueue(name):
3299 fh = repo.opener(_allqueues, 'a')
3291 fh = repo.opener(_allqueues, 'a')
3300 fh.write('%s\n' % (name,))
3292 fh.write('%s\n' % (name,))
3301 fh.close()
3293 fh.close()
3302
3294
3303 def _queuedir(name):
3295 def _queuedir(name):
3304 if name == 'patches':
3296 if name == 'patches':
3305 return repo.join('patches')
3297 return repo.join('patches')
3306 else:
3298 else:
3307 return repo.join('patches-' + name)
3299 return repo.join('patches-' + name)
3308
3300
3309 def _validname(name):
3301 def _validname(name):
3310 for n in name:
3302 for n in name:
3311 if n in ':\\/.':
3303 if n in ':\\/.':
3312 return False
3304 return False
3313 return True
3305 return True
3314
3306
3315 def _delete(name):
3307 def _delete(name):
3316 if name not in existing:
3308 if name not in existing:
3317 raise util.Abort(_('cannot delete queue that does not exist'))
3309 raise util.Abort(_('cannot delete queue that does not exist'))
3318
3310
3319 current = _getcurrent()
3311 current = _getcurrent()
3320
3312
3321 if name == current:
3313 if name == current:
3322 raise util.Abort(_('cannot delete currently active queue'))
3314 raise util.Abort(_('cannot delete currently active queue'))
3323
3315
3324 fh = repo.opener('patches.queues.new', 'w')
3316 fh = repo.opener('patches.queues.new', 'w')
3325 for queue in existing:
3317 for queue in existing:
3326 if queue == name:
3318 if queue == name:
3327 continue
3319 continue
3328 fh.write('%s\n' % (queue,))
3320 fh.write('%s\n' % (queue,))
3329 fh.close()
3321 fh.close()
3330 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3322 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3331
3323
3332 if not name or opts.get('list') or opts.get('active'):
3324 if not name or opts.get('list') or opts.get('active'):
3333 current = _getcurrent()
3325 current = _getcurrent()
3334 if opts.get('active'):
3326 if opts.get('active'):
3335 ui.write('%s\n' % (current,))
3327 ui.write('%s\n' % (current,))
3336 return
3328 return
3337 for queue in _getqueues():
3329 for queue in _getqueues():
3338 ui.write('%s' % (queue,))
3330 ui.write('%s' % (queue,))
3339 if queue == current and not ui.quiet:
3331 if queue == current and not ui.quiet:
3340 ui.write(_(' (active)\n'))
3332 ui.write(_(' (active)\n'))
3341 else:
3333 else:
3342 ui.write('\n')
3334 ui.write('\n')
3343 return
3335 return
3344
3336
3345 if not _validname(name):
3337 if not _validname(name):
3346 raise util.Abort(
3338 raise util.Abort(
3347 _('invalid queue name, may not contain the characters ":\\/."'))
3339 _('invalid queue name, may not contain the characters ":\\/."'))
3348
3340
3349 existing = _getqueues()
3341 existing = _getqueues()
3350
3342
3351 if opts.get('create'):
3343 if opts.get('create'):
3352 if name in existing:
3344 if name in existing:
3353 raise util.Abort(_('queue "%s" already exists') % name)
3345 raise util.Abort(_('queue "%s" already exists') % name)
3354 if _noqueues():
3346 if _noqueues():
3355 _addqueue(_defaultqueue)
3347 _addqueue(_defaultqueue)
3356 _addqueue(name)
3348 _addqueue(name)
3357 _setactive(name)
3349 _setactive(name)
3358 elif opts.get('rename'):
3350 elif opts.get('rename'):
3359 current = _getcurrent()
3351 current = _getcurrent()
3360 if name == current:
3352 if name == current:
3361 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3353 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3362 if name in existing:
3354 if name in existing:
3363 raise util.Abort(_('queue "%s" already exists') % name)
3355 raise util.Abort(_('queue "%s" already exists') % name)
3364
3356
3365 olddir = _queuedir(current)
3357 olddir = _queuedir(current)
3366 newdir = _queuedir(name)
3358 newdir = _queuedir(name)
3367
3359
3368 if os.path.exists(newdir):
3360 if os.path.exists(newdir):
3369 raise util.Abort(_('non-queue directory "%s" already exists') %
3361 raise util.Abort(_('non-queue directory "%s" already exists') %
3370 newdir)
3362 newdir)
3371
3363
3372 fh = repo.opener('patches.queues.new', 'w')
3364 fh = repo.opener('patches.queues.new', 'w')
3373 for queue in existing:
3365 for queue in existing:
3374 if queue == current:
3366 if queue == current:
3375 fh.write('%s\n' % (name,))
3367 fh.write('%s\n' % (name,))
3376 if os.path.exists(olddir):
3368 if os.path.exists(olddir):
3377 util.rename(olddir, newdir)
3369 util.rename(olddir, newdir)
3378 else:
3370 else:
3379 fh.write('%s\n' % (queue,))
3371 fh.write('%s\n' % (queue,))
3380 fh.close()
3372 fh.close()
3381 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3373 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3382 _setactivenocheck(name)
3374 _setactivenocheck(name)
3383 elif opts.get('delete'):
3375 elif opts.get('delete'):
3384 _delete(name)
3376 _delete(name)
3385 elif opts.get('purge'):
3377 elif opts.get('purge'):
3386 if name in existing:
3378 if name in existing:
3387 _delete(name)
3379 _delete(name)
3388 qdir = _queuedir(name)
3380 qdir = _queuedir(name)
3389 if os.path.exists(qdir):
3381 if os.path.exists(qdir):
3390 shutil.rmtree(qdir)
3382 shutil.rmtree(qdir)
3391 else:
3383 else:
3392 if name not in existing:
3384 if name not in existing:
3393 raise util.Abort(_('use --create to create a new queue'))
3385 raise util.Abort(_('use --create to create a new queue'))
3394 _setactive(name)
3386 _setactive(name)
3395
3387
3396 def mqphasedefaults(repo, roots):
3388 def mqphasedefaults(repo, roots):
3397 """callback used to set mq changeset as secret when no phase data exists"""
3389 """callback used to set mq changeset as secret when no phase data exists"""
3398 if repo.mq.applied:
3390 if repo.mq.applied:
3399 if repo.ui.configbool('mq', 'secret', False):
3391 if repo.ui.configbool('mq', 'secret', False):
3400 mqphase = phases.secret
3392 mqphase = phases.secret
3401 else:
3393 else:
3402 mqphase = phases.draft
3394 mqphase = phases.draft
3403 qbase = repo[repo.mq.applied[0].node]
3395 qbase = repo[repo.mq.applied[0].node]
3404 roots[mqphase].add(qbase.node())
3396 roots[mqphase].add(qbase.node())
3405 return roots
3397 return roots
3406
3398
3407 def reposetup(ui, repo):
3399 def reposetup(ui, repo):
3408 class mqrepo(repo.__class__):
3400 class mqrepo(repo.__class__):
3409 @util.propertycache
3401 @util.propertycache
3410 def mq(self):
3402 def mq(self):
3411 return queue(self.ui, self.path)
3403 return queue(self.ui, self.path)
3412
3404
3413 def abortifwdirpatched(self, errmsg, force=False):
3405 def abortifwdirpatched(self, errmsg, force=False):
3414 if self.mq.applied and not force:
3406 if self.mq.applied and not force:
3415 parents = self.dirstate.parents()
3407 parents = self.dirstate.parents()
3416 patches = [s.node for s in self.mq.applied]
3408 patches = [s.node for s in self.mq.applied]
3417 if parents[0] in patches or parents[1] in patches:
3409 if parents[0] in patches or parents[1] in patches:
3418 raise util.Abort(errmsg)
3410 raise util.Abort(errmsg)
3419
3411
3420 def commit(self, text="", user=None, date=None, match=None,
3412 def commit(self, text="", user=None, date=None, match=None,
3421 force=False, editor=False, extra={}):
3413 force=False, editor=False, extra={}):
3422 self.abortifwdirpatched(
3414 self.abortifwdirpatched(
3423 _('cannot commit over an applied mq patch'),
3415 _('cannot commit over an applied mq patch'),
3424 force)
3416 force)
3425
3417
3426 return super(mqrepo, self).commit(text, user, date, match, force,
3418 return super(mqrepo, self).commit(text, user, date, match, force,
3427 editor, extra)
3419 editor, extra)
3428
3420
3429 def checkpush(self, force, revs):
3421 def checkpush(self, force, revs):
3430 if self.mq.applied and not force:
3422 if self.mq.applied and not force:
3431 outapplied = [e.node for e in self.mq.applied]
3423 outapplied = [e.node for e in self.mq.applied]
3432 if revs:
3424 if revs:
3433 # Assume applied patches have no non-patch descendants and
3425 # Assume applied patches have no non-patch descendants and
3434 # are not on remote already. Filtering any changeset not
3426 # are not on remote already. Filtering any changeset not
3435 # pushed.
3427 # pushed.
3436 heads = set(revs)
3428 heads = set(revs)
3437 for node in reversed(outapplied):
3429 for node in reversed(outapplied):
3438 if node in heads:
3430 if node in heads:
3439 break
3431 break
3440 else:
3432 else:
3441 outapplied.pop()
3433 outapplied.pop()
3442 # looking for pushed and shared changeset
3434 # looking for pushed and shared changeset
3443 for node in outapplied:
3435 for node in outapplied:
3444 if self[node].phase() < phases.secret:
3436 if self[node].phase() < phases.secret:
3445 raise util.Abort(_('source has mq patches applied'))
3437 raise util.Abort(_('source has mq patches applied'))
3446 # no non-secret patches pushed
3438 # no non-secret patches pushed
3447 super(mqrepo, self).checkpush(force, revs)
3439 super(mqrepo, self).checkpush(force, revs)
3448
3440
3449 def _findtags(self):
3441 def _findtags(self):
3450 '''augment tags from base class with patch tags'''
3442 '''augment tags from base class with patch tags'''
3451 result = super(mqrepo, self)._findtags()
3443 result = super(mqrepo, self)._findtags()
3452
3444
3453 q = self.mq
3445 q = self.mq
3454 if not q.applied:
3446 if not q.applied:
3455 return result
3447 return result
3456
3448
3457 mqtags = [(patch.node, patch.name) for patch in q.applied]
3449 mqtags = [(patch.node, patch.name) for patch in q.applied]
3458
3450
3459 try:
3451 try:
3460 # for now ignore filtering business
3452 # for now ignore filtering business
3461 self.unfiltered().changelog.rev(mqtags[-1][0])
3453 self.unfiltered().changelog.rev(mqtags[-1][0])
3462 except error.LookupError:
3454 except error.LookupError:
3463 self.ui.warn(_('mq status file refers to unknown node %s\n')
3455 self.ui.warn(_('mq status file refers to unknown node %s\n')
3464 % short(mqtags[-1][0]))
3456 % short(mqtags[-1][0]))
3465 return result
3457 return result
3466
3458
3467 mqtags.append((mqtags[-1][0], 'qtip'))
3459 mqtags.append((mqtags[-1][0], 'qtip'))
3468 mqtags.append((mqtags[0][0], 'qbase'))
3460 mqtags.append((mqtags[0][0], 'qbase'))
3469 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3461 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3470 tags = result[0]
3462 tags = result[0]
3471 for patch in mqtags:
3463 for patch in mqtags:
3472 if patch[1] in tags:
3464 if patch[1] in tags:
3473 self.ui.warn(_('tag %s overrides mq patch of the same '
3465 self.ui.warn(_('tag %s overrides mq patch of the same '
3474 'name\n') % patch[1])
3466 'name\n') % patch[1])
3475 else:
3467 else:
3476 tags[patch[1]] = patch[0]
3468 tags[patch[1]] = patch[0]
3477
3469
3478 return result
3470 return result
3479
3471
3480 def _cacheabletip(self):
3472 def _cacheabletip(self):
3481 q = self.mq
3473 q = self.mq
3482 cl = self.changelog
3474 cl = self.changelog
3483 qbase = None
3475 qbase = None
3484 if not q.applied:
3476 if not q.applied:
3485 if getattr(self, '_committingpatch', False):
3477 if getattr(self, '_committingpatch', False):
3486 # Committing a new patch, must be tip
3478 # Committing a new patch, must be tip
3487 qbase = len(cl) - 1
3479 qbase = len(cl) - 1
3488 else:
3480 else:
3489 qbasenode = q.applied[0].node
3481 qbasenode = q.applied[0].node
3490 try:
3482 try:
3491 qbase = self.unfiltered().changelog.rev(qbasenode)
3483 qbase = self.unfiltered().changelog.rev(qbasenode)
3492 except error.LookupError:
3484 except error.LookupError:
3493 self.ui.warn(_('mq status file refers to unknown node %s\n')
3485 self.ui.warn(_('mq status file refers to unknown node %s\n')
3494 % short(qbasenode))
3486 % short(qbasenode))
3495 ret = super(mqrepo, self)._cacheabletip()
3487 ret = super(mqrepo, self)._cacheabletip()
3496 if qbase is not None:
3488 if qbase is not None:
3497 ret = min(qbase - 1, ret)
3489 ret = min(qbase - 1, ret)
3498 return ret
3490 return ret
3499
3491
3500 if repo.local():
3492 if repo.local():
3501 repo.__class__ = mqrepo
3493 repo.__class__ = mqrepo
3502
3494
3503 repo._phasedefaults.append(mqphasedefaults)
3495 repo._phasedefaults.append(mqphasedefaults)
3504
3496
3505 def mqimport(orig, ui, repo, *args, **kwargs):
3497 def mqimport(orig, ui, repo, *args, **kwargs):
3506 if (util.safehasattr(repo, 'abortifwdirpatched')
3498 if (util.safehasattr(repo, 'abortifwdirpatched')
3507 and not kwargs.get('no_commit', False)):
3499 and not kwargs.get('no_commit', False)):
3508 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3500 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3509 kwargs.get('force'))
3501 kwargs.get('force'))
3510 return orig(ui, repo, *args, **kwargs)
3502 return orig(ui, repo, *args, **kwargs)
3511
3503
3512 def mqinit(orig, ui, *args, **kwargs):
3504 def mqinit(orig, ui, *args, **kwargs):
3513 mq = kwargs.pop('mq', None)
3505 mq = kwargs.pop('mq', None)
3514
3506
3515 if not mq:
3507 if not mq:
3516 return orig(ui, *args, **kwargs)
3508 return orig(ui, *args, **kwargs)
3517
3509
3518 if args:
3510 if args:
3519 repopath = args[0]
3511 repopath = args[0]
3520 if not hg.islocal(repopath):
3512 if not hg.islocal(repopath):
3521 raise util.Abort(_('only a local queue repository '
3513 raise util.Abort(_('only a local queue repository '
3522 'may be initialized'))
3514 'may be initialized'))
3523 else:
3515 else:
3524 repopath = cmdutil.findrepo(os.getcwd())
3516 repopath = cmdutil.findrepo(os.getcwd())
3525 if not repopath:
3517 if not repopath:
3526 raise util.Abort(_('there is no Mercurial repository here '
3518 raise util.Abort(_('there is no Mercurial repository here '
3527 '(.hg not found)'))
3519 '(.hg not found)'))
3528 repo = hg.repository(ui, repopath)
3520 repo = hg.repository(ui, repopath)
3529 return qinit(ui, repo, True)
3521 return qinit(ui, repo, True)
3530
3522
3531 def mqcommand(orig, ui, repo, *args, **kwargs):
3523 def mqcommand(orig, ui, repo, *args, **kwargs):
3532 """Add --mq option to operate on patch repository instead of main"""
3524 """Add --mq option to operate on patch repository instead of main"""
3533
3525
3534 # some commands do not like getting unknown options
3526 # some commands do not like getting unknown options
3535 mq = kwargs.pop('mq', None)
3527 mq = kwargs.pop('mq', None)
3536
3528
3537 if not mq:
3529 if not mq:
3538 return orig(ui, repo, *args, **kwargs)
3530 return orig(ui, repo, *args, **kwargs)
3539
3531
3540 q = repo.mq
3532 q = repo.mq
3541 r = q.qrepo()
3533 r = q.qrepo()
3542 if not r:
3534 if not r:
3543 raise util.Abort(_('no queue repository'))
3535 raise util.Abort(_('no queue repository'))
3544 return orig(r.ui, r, *args, **kwargs)
3536 return orig(r.ui, r, *args, **kwargs)
3545
3537
3546 def summary(orig, ui, repo, *args, **kwargs):
3538 def summary(orig, ui, repo, *args, **kwargs):
3547 r = orig(ui, repo, *args, **kwargs)
3539 r = orig(ui, repo, *args, **kwargs)
3548 q = repo.mq
3540 q = repo.mq
3549 m = []
3541 m = []
3550 a, u = len(q.applied), len(q.unapplied(repo))
3542 a, u = len(q.applied), len(q.unapplied(repo))
3551 if a:
3543 if a:
3552 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3544 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3553 if u:
3545 if u:
3554 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3546 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3555 if m:
3547 if m:
3556 # i18n: column positioning for "hg summary"
3548 # i18n: column positioning for "hg summary"
3557 ui.write(_("mq: %s\n") % ', '.join(m))
3549 ui.write(_("mq: %s\n") % ', '.join(m))
3558 else:
3550 else:
3559 # i18n: column positioning for "hg summary"
3551 # i18n: column positioning for "hg summary"
3560 ui.note(_("mq: (empty queue)\n"))
3552 ui.note(_("mq: (empty queue)\n"))
3561 return r
3553 return r
3562
3554
3563 def revsetmq(repo, subset, x):
3555 def revsetmq(repo, subset, x):
3564 """``mq()``
3556 """``mq()``
3565 Changesets managed by MQ.
3557 Changesets managed by MQ.
3566 """
3558 """
3567 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3559 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3568 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3560 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3569 return [r for r in subset if r in applied]
3561 return [r for r in subset if r in applied]
3570
3562
3571 # tell hggettext to extract docstrings from these functions:
3563 # tell hggettext to extract docstrings from these functions:
3572 i18nfunctions = [revsetmq]
3564 i18nfunctions = [revsetmq]
3573
3565
3574 def extsetup(ui):
3566 def extsetup(ui):
3575 # Ensure mq wrappers are called first, regardless of extension load order by
3567 # Ensure mq wrappers are called first, regardless of extension load order by
3576 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3568 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3577 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3569 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3578
3570
3579 extensions.wrapcommand(commands.table, 'import', mqimport)
3571 extensions.wrapcommand(commands.table, 'import', mqimport)
3580 extensions.wrapcommand(commands.table, 'summary', summary)
3572 extensions.wrapcommand(commands.table, 'summary', summary)
3581
3573
3582 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3574 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3583 entry[1].extend(mqopt)
3575 entry[1].extend(mqopt)
3584
3576
3585 nowrap = set(commands.norepo.split(" "))
3577 nowrap = set(commands.norepo.split(" "))
3586
3578
3587 def dotable(cmdtable):
3579 def dotable(cmdtable):
3588 for cmd in cmdtable.keys():
3580 for cmd in cmdtable.keys():
3589 cmd = cmdutil.parsealiases(cmd)[0]
3581 cmd = cmdutil.parsealiases(cmd)[0]
3590 if cmd in nowrap:
3582 if cmd in nowrap:
3591 continue
3583 continue
3592 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3584 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3593 entry[1].extend(mqopt)
3585 entry[1].extend(mqopt)
3594
3586
3595 dotable(commands.table)
3587 dotable(commands.table)
3596
3588
3597 for extname, extmodule in extensions.extensions():
3589 for extname, extmodule in extensions.extensions():
3598 if extmodule.__file__ != __file__:
3590 if extmodule.__file__ != __file__:
3599 dotable(getattr(extmodule, 'cmdtable', {}))
3591 dotable(getattr(extmodule, 'cmdtable', {}))
3600
3592
3601 revset.symbols['mq'] = revsetmq
3593 revset.symbols['mq'] = revsetmq
3602
3594
3603 colortable = {'qguard.negative': 'red',
3595 colortable = {'qguard.negative': 'red',
3604 'qguard.positive': 'yellow',
3596 'qguard.positive': 'yellow',
3605 'qguard.unguarded': 'green',
3597 'qguard.unguarded': 'green',
3606 'qseries.applied': 'blue bold underline',
3598 'qseries.applied': 'blue bold underline',
3607 'qseries.guarded': 'black bold',
3599 'qseries.guarded': 'black bold',
3608 'qseries.missing': 'red bold',
3600 'qseries.missing': 'red bold',
3609 'qseries.unapplied': 'black bold'}
3601 'qseries.unapplied': 'black bold'}
3610
3602
3611 commands.inferrepo += " qnew qrefresh qdiff qcommit"
3603 commands.inferrepo += " qnew qrefresh qdiff qcommit"
@@ -1,6038 +1,6034 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, encoding, templatekw, discovery
13 import patch, help, 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 - skip all revisions that do not touch directories ``foo`` or ``bar``
552 - skip all revisions that do not touch directories ``foo`` or ``bar``
553
553
554 hg bisect --skip '!( file("path:foo") & file("path:bar") )'
554 hg bisect --skip '!( file("path:foo") & file("path:bar") )'
555
555
556 - forget the current bisection::
556 - forget the current bisection::
557
557
558 hg bisect --reset
558 hg bisect --reset
559
559
560 - use 'make && make tests' to automatically find the first broken
560 - use 'make && make tests' to automatically find the first broken
561 revision::
561 revision::
562
562
563 hg bisect --reset
563 hg bisect --reset
564 hg bisect --bad 34
564 hg bisect --bad 34
565 hg bisect --good 12
565 hg bisect --good 12
566 hg bisect --command 'make && make tests'
566 hg bisect --command 'make && make tests'
567
567
568 - see all changesets whose states are already known in the current
568 - see all changesets whose states are already known in the current
569 bisection::
569 bisection::
570
570
571 hg log -r "bisect(pruned)"
571 hg log -r "bisect(pruned)"
572
572
573 - see the changeset currently being bisected (especially useful
573 - see the changeset currently being bisected (especially useful
574 if running with -U/--noupdate)::
574 if running with -U/--noupdate)::
575
575
576 hg log -r "bisect(current)"
576 hg log -r "bisect(current)"
577
577
578 - see all changesets that took part in the current bisection::
578 - see all changesets that took part in the current bisection::
579
579
580 hg log -r "bisect(range)"
580 hg log -r "bisect(range)"
581
581
582 - with the graphlog extension, you can even get a nice graph::
582 - with the graphlog extension, you can even get a nice graph::
583
583
584 hg log --graph -r "bisect(range)"
584 hg log --graph -r "bisect(range)"
585
585
586 See :hg:`help revsets` for more about the `bisect()` keyword.
586 See :hg:`help revsets` for more about the `bisect()` keyword.
587
587
588 Returns 0 on success.
588 Returns 0 on success.
589 """
589 """
590 def extendbisectrange(nodes, good):
590 def extendbisectrange(nodes, good):
591 # bisect is incomplete when it ends on a merge node and
591 # bisect is incomplete when it ends on a merge node and
592 # one of the parent was not checked.
592 # one of the parent was not checked.
593 parents = repo[nodes[0]].parents()
593 parents = repo[nodes[0]].parents()
594 if len(parents) > 1:
594 if len(parents) > 1:
595 side = good and state['bad'] or state['good']
595 side = good and state['bad'] or state['good']
596 num = len(set(i.node() for i in parents) & set(side))
596 num = len(set(i.node() for i in parents) & set(side))
597 if num == 1:
597 if num == 1:
598 return parents[0].ancestor(parents[1])
598 return parents[0].ancestor(parents[1])
599 return None
599 return None
600
600
601 def print_result(nodes, good):
601 def print_result(nodes, good):
602 displayer = cmdutil.show_changeset(ui, repo, {})
602 displayer = cmdutil.show_changeset(ui, repo, {})
603 if len(nodes) == 1:
603 if len(nodes) == 1:
604 # narrowed it down to a single revision
604 # narrowed it down to a single revision
605 if good:
605 if good:
606 ui.write(_("The first good revision is:\n"))
606 ui.write(_("The first good revision is:\n"))
607 else:
607 else:
608 ui.write(_("The first bad revision is:\n"))
608 ui.write(_("The first bad revision is:\n"))
609 displayer.show(repo[nodes[0]])
609 displayer.show(repo[nodes[0]])
610 extendnode = extendbisectrange(nodes, good)
610 extendnode = extendbisectrange(nodes, good)
611 if extendnode is not None:
611 if extendnode is not None:
612 ui.write(_('Not all ancestors of this changeset have been'
612 ui.write(_('Not all ancestors of this changeset have been'
613 ' checked.\nUse bisect --extend to continue the '
613 ' checked.\nUse bisect --extend to continue the '
614 'bisection from\nthe common ancestor, %s.\n')
614 'bisection from\nthe common ancestor, %s.\n')
615 % extendnode)
615 % extendnode)
616 else:
616 else:
617 # multiple possible revisions
617 # multiple possible revisions
618 if good:
618 if good:
619 ui.write(_("Due to skipped revisions, the first "
619 ui.write(_("Due to skipped revisions, the first "
620 "good revision could be any of:\n"))
620 "good revision could be any of:\n"))
621 else:
621 else:
622 ui.write(_("Due to skipped revisions, the first "
622 ui.write(_("Due to skipped revisions, the first "
623 "bad revision could be any of:\n"))
623 "bad revision could be any of:\n"))
624 for n in nodes:
624 for n in nodes:
625 displayer.show(repo[n])
625 displayer.show(repo[n])
626 displayer.close()
626 displayer.close()
627
627
628 def check_state(state, interactive=True):
628 def check_state(state, interactive=True):
629 if not state['good'] or not state['bad']:
629 if not state['good'] or not state['bad']:
630 if (good or bad or skip or reset) and interactive:
630 if (good or bad or skip or reset) and interactive:
631 return
631 return
632 if not state['good']:
632 if not state['good']:
633 raise util.Abort(_('cannot bisect (no known good revisions)'))
633 raise util.Abort(_('cannot bisect (no known good revisions)'))
634 else:
634 else:
635 raise util.Abort(_('cannot bisect (no known bad revisions)'))
635 raise util.Abort(_('cannot bisect (no known bad revisions)'))
636 return True
636 return True
637
637
638 # backward compatibility
638 # backward compatibility
639 if rev in "good bad reset init".split():
639 if rev in "good bad reset init".split():
640 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
640 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
641 cmd, rev, extra = rev, extra, None
641 cmd, rev, extra = rev, extra, None
642 if cmd == "good":
642 if cmd == "good":
643 good = True
643 good = True
644 elif cmd == "bad":
644 elif cmd == "bad":
645 bad = True
645 bad = True
646 else:
646 else:
647 reset = True
647 reset = True
648 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
648 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
649 raise util.Abort(_('incompatible arguments'))
649 raise util.Abort(_('incompatible arguments'))
650
650
651 if reset:
651 if reset:
652 p = repo.join("bisect.state")
652 p = repo.join("bisect.state")
653 if os.path.exists(p):
653 if os.path.exists(p):
654 os.unlink(p)
654 os.unlink(p)
655 return
655 return
656
656
657 state = hbisect.load_state(repo)
657 state = hbisect.load_state(repo)
658
658
659 if command:
659 if command:
660 changesets = 1
660 changesets = 1
661 try:
661 try:
662 node = state['current'][0]
662 node = state['current'][0]
663 except LookupError:
663 except LookupError:
664 if noupdate:
664 if noupdate:
665 raise util.Abort(_('current bisect revision is unknown - '
665 raise util.Abort(_('current bisect revision is unknown - '
666 'start a new bisect to fix'))
666 'start a new bisect to fix'))
667 node, p2 = repo.dirstate.parents()
667 node, p2 = repo.dirstate.parents()
668 if p2 != nullid:
668 if p2 != nullid:
669 raise util.Abort(_('current bisect revision is a merge'))
669 raise util.Abort(_('current bisect revision is a merge'))
670 try:
670 try:
671 while changesets:
671 while changesets:
672 # update state
672 # update state
673 state['current'] = [node]
673 state['current'] = [node]
674 hbisect.save_state(repo, state)
674 hbisect.save_state(repo, state)
675 status = util.system(command,
675 status = util.system(command,
676 environ={'HG_NODE': hex(node)},
676 environ={'HG_NODE': hex(node)},
677 out=ui.fout)
677 out=ui.fout)
678 if status == 125:
678 if status == 125:
679 transition = "skip"
679 transition = "skip"
680 elif status == 0:
680 elif status == 0:
681 transition = "good"
681 transition = "good"
682 # status < 0 means process was killed
682 # status < 0 means process was killed
683 elif status == 127:
683 elif status == 127:
684 raise util.Abort(_("failed to execute %s") % command)
684 raise util.Abort(_("failed to execute %s") % command)
685 elif status < 0:
685 elif status < 0:
686 raise util.Abort(_("%s killed") % command)
686 raise util.Abort(_("%s killed") % command)
687 else:
687 else:
688 transition = "bad"
688 transition = "bad"
689 ctx = scmutil.revsingle(repo, rev, node)
689 ctx = scmutil.revsingle(repo, rev, node)
690 rev = None # clear for future iterations
690 rev = None # clear for future iterations
691 state[transition].append(ctx.node())
691 state[transition].append(ctx.node())
692 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
692 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
693 check_state(state, interactive=False)
693 check_state(state, interactive=False)
694 # bisect
694 # bisect
695 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
695 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
696 # update to next check
696 # update to next check
697 node = nodes[0]
697 node = nodes[0]
698 if not noupdate:
698 if not noupdate:
699 cmdutil.bailifchanged(repo)
699 cmdutil.bailifchanged(repo)
700 hg.clean(repo, node, show_stats=False)
700 hg.clean(repo, node, show_stats=False)
701 finally:
701 finally:
702 state['current'] = [node]
702 state['current'] = [node]
703 hbisect.save_state(repo, state)
703 hbisect.save_state(repo, state)
704 print_result(nodes, good)
704 print_result(nodes, good)
705 return
705 return
706
706
707 # update state
707 # update state
708
708
709 if rev:
709 if rev:
710 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
710 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
711 else:
711 else:
712 nodes = [repo.lookup('.')]
712 nodes = [repo.lookup('.')]
713
713
714 if good or bad or skip:
714 if good or bad or skip:
715 if good:
715 if good:
716 state['good'] += nodes
716 state['good'] += nodes
717 elif bad:
717 elif bad:
718 state['bad'] += nodes
718 state['bad'] += nodes
719 elif skip:
719 elif skip:
720 state['skip'] += nodes
720 state['skip'] += nodes
721 hbisect.save_state(repo, state)
721 hbisect.save_state(repo, state)
722
722
723 if not check_state(state):
723 if not check_state(state):
724 return
724 return
725
725
726 # actually bisect
726 # actually bisect
727 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
727 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
728 if extend:
728 if extend:
729 if not changesets:
729 if not changesets:
730 extendnode = extendbisectrange(nodes, good)
730 extendnode = extendbisectrange(nodes, good)
731 if extendnode is not None:
731 if extendnode is not None:
732 ui.write(_("Extending search to changeset %d:%s\n"
732 ui.write(_("Extending search to changeset %d:%s\n"
733 % (extendnode.rev(), extendnode)))
733 % (extendnode.rev(), extendnode)))
734 state['current'] = [extendnode.node()]
734 state['current'] = [extendnode.node()]
735 hbisect.save_state(repo, state)
735 hbisect.save_state(repo, state)
736 if noupdate:
736 if noupdate:
737 return
737 return
738 cmdutil.bailifchanged(repo)
738 cmdutil.bailifchanged(repo)
739 return hg.clean(repo, extendnode.node())
739 return hg.clean(repo, extendnode.node())
740 raise util.Abort(_("nothing to extend"))
740 raise util.Abort(_("nothing to extend"))
741
741
742 if changesets == 0:
742 if changesets == 0:
743 print_result(nodes, good)
743 print_result(nodes, good)
744 else:
744 else:
745 assert len(nodes) == 1 # only a single node can be tested next
745 assert len(nodes) == 1 # only a single node can be tested next
746 node = nodes[0]
746 node = nodes[0]
747 # compute the approximate number of remaining tests
747 # compute the approximate number of remaining tests
748 tests, size = 0, 2
748 tests, size = 0, 2
749 while size <= changesets:
749 while size <= changesets:
750 tests, size = tests + 1, size * 2
750 tests, size = tests + 1, size * 2
751 rev = repo.changelog.rev(node)
751 rev = repo.changelog.rev(node)
752 ui.write(_("Testing changeset %d:%s "
752 ui.write(_("Testing changeset %d:%s "
753 "(%d changesets remaining, ~%d tests)\n")
753 "(%d changesets remaining, ~%d tests)\n")
754 % (rev, short(node), changesets, tests))
754 % (rev, short(node), changesets, tests))
755 state['current'] = [node]
755 state['current'] = [node]
756 hbisect.save_state(repo, state)
756 hbisect.save_state(repo, state)
757 if not noupdate:
757 if not noupdate:
758 cmdutil.bailifchanged(repo)
758 cmdutil.bailifchanged(repo)
759 return hg.clean(repo, node)
759 return hg.clean(repo, node)
760
760
761 @command('bookmarks|bookmark',
761 @command('bookmarks|bookmark',
762 [('f', 'force', False, _('force')),
762 [('f', 'force', False, _('force')),
763 ('r', 'rev', '', _('revision'), _('REV')),
763 ('r', 'rev', '', _('revision'), _('REV')),
764 ('d', 'delete', False, _('delete a given bookmark')),
764 ('d', 'delete', False, _('delete a given bookmark')),
765 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
765 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
766 ('i', 'inactive', False, _('mark a bookmark inactive'))],
766 ('i', 'inactive', False, _('mark a bookmark inactive'))],
767 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
767 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
768 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
768 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
769 rename=None, inactive=False):
769 rename=None, inactive=False):
770 '''track a line of development with movable markers
770 '''track a line of development with movable markers
771
771
772 Bookmarks are pointers to certain commits that move when committing.
772 Bookmarks are pointers to certain commits that move when committing.
773 Bookmarks are local. They can be renamed, copied and deleted. It is
773 Bookmarks are local. They can be renamed, copied and deleted. It is
774 possible to use :hg:`merge NAME` to merge from a given bookmark, and
774 possible to use :hg:`merge NAME` to merge from a given bookmark, and
775 :hg:`update NAME` to update to a given bookmark.
775 :hg:`update NAME` to update to a given bookmark.
776
776
777 You can use :hg:`bookmark NAME` to set a bookmark on the working
777 You can use :hg:`bookmark NAME` to set a bookmark on the working
778 directory's parent revision with the given name. If you specify
778 directory's parent revision with the given name. If you specify
779 a revision using -r REV (where REV may be an existing bookmark),
779 a revision using -r REV (where REV may be an existing bookmark),
780 the bookmark is assigned to that revision.
780 the bookmark is assigned to that revision.
781
781
782 Bookmarks can be pushed and pulled between repositories (see :hg:`help
782 Bookmarks can be pushed and pulled between repositories (see :hg:`help
783 push` and :hg:`help pull`). This requires both the local and remote
783 push` and :hg:`help pull`). This requires both the local and remote
784 repositories to support bookmarks. For versions prior to 1.8, this means
784 repositories to support bookmarks. For versions prior to 1.8, this means
785 the bookmarks extension must be enabled.
785 the bookmarks extension must be enabled.
786
786
787 With -i/--inactive, the new bookmark will not be made the active
787 With -i/--inactive, the new bookmark will not be made the active
788 bookmark. If -r/--rev is given, the new bookmark will not be made
788 bookmark. If -r/--rev is given, the new bookmark will not be made
789 active even if -i/--inactive is not given. If no NAME is given, the
789 active even if -i/--inactive is not given. If no NAME is given, the
790 current active bookmark will be marked inactive.
790 current active bookmark will be marked inactive.
791 '''
791 '''
792 hexfn = ui.debugflag and hex or short
792 hexfn = ui.debugflag and hex or short
793 marks = repo._bookmarks
793 marks = repo._bookmarks
794 cur = repo.changectx('.').node()
794 cur = repo.changectx('.').node()
795
795
796 def checkformat(mark):
796 def checkformat(mark):
797 mark = mark.strip()
797 mark = mark.strip()
798 if not mark:
798 if not mark:
799 raise util.Abort(_("bookmark names cannot consist entirely of "
799 raise util.Abort(_("bookmark names cannot consist entirely of "
800 "whitespace"))
800 "whitespace"))
801 scmutil.checknewlabel(repo, mark, 'bookmark')
801 scmutil.checknewlabel(repo, mark, 'bookmark')
802 return mark
802 return mark
803
803
804 def checkconflict(repo, mark, force=False):
804 def checkconflict(repo, mark, force=False):
805 if mark in marks and not force:
805 if mark in marks and not force:
806 raise util.Abort(_("bookmark '%s' already exists "
806 raise util.Abort(_("bookmark '%s' already exists "
807 "(use -f to force)") % mark)
807 "(use -f to force)") % mark)
808 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
808 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
809 and not force):
809 and not force):
810 raise util.Abort(
810 raise util.Abort(
811 _("a bookmark cannot have the name of an existing branch"))
811 _("a bookmark cannot have the name of an existing branch"))
812
812
813 if delete and rename:
813 if delete and rename:
814 raise util.Abort(_("--delete and --rename are incompatible"))
814 raise util.Abort(_("--delete and --rename are incompatible"))
815 if delete and rev:
815 if delete and rev:
816 raise util.Abort(_("--rev is incompatible with --delete"))
816 raise util.Abort(_("--rev is incompatible with --delete"))
817 if rename and rev:
817 if rename and rev:
818 raise util.Abort(_("--rev is incompatible with --rename"))
818 raise util.Abort(_("--rev is incompatible with --rename"))
819 if mark is None and (delete or rev):
819 if mark is None and (delete or rev):
820 raise util.Abort(_("bookmark name required"))
820 raise util.Abort(_("bookmark name required"))
821
821
822 if delete:
822 if delete:
823 if mark not in marks:
823 if mark not in marks:
824 raise util.Abort(_("bookmark '%s' does not exist") % mark)
824 raise util.Abort(_("bookmark '%s' does not exist") % mark)
825 if mark == repo._bookmarkcurrent:
825 if mark == repo._bookmarkcurrent:
826 bookmarks.setcurrent(repo, None)
826 bookmarks.setcurrent(repo, None)
827 del marks[mark]
827 del marks[mark]
828 marks.write()
828 marks.write()
829
829
830 elif rename:
830 elif rename:
831 if mark is None:
831 if mark is None:
832 raise util.Abort(_("new bookmark name required"))
832 raise util.Abort(_("new bookmark name required"))
833 mark = checkformat(mark)
833 mark = checkformat(mark)
834 if rename not in marks:
834 if rename not in marks:
835 raise util.Abort(_("bookmark '%s' does not exist") % rename)
835 raise util.Abort(_("bookmark '%s' does not exist") % rename)
836 checkconflict(repo, mark, force)
836 checkconflict(repo, mark, force)
837 marks[mark] = marks[rename]
837 marks[mark] = marks[rename]
838 if repo._bookmarkcurrent == rename and not inactive:
838 if repo._bookmarkcurrent == rename and not inactive:
839 bookmarks.setcurrent(repo, mark)
839 bookmarks.setcurrent(repo, mark)
840 del marks[rename]
840 del marks[rename]
841 marks.write()
841 marks.write()
842
842
843 elif mark is not None:
843 elif mark is not None:
844 mark = checkformat(mark)
844 mark = checkformat(mark)
845 if inactive and mark == repo._bookmarkcurrent:
845 if inactive and mark == repo._bookmarkcurrent:
846 bookmarks.setcurrent(repo, None)
846 bookmarks.setcurrent(repo, None)
847 return
847 return
848 checkconflict(repo, mark, force)
848 checkconflict(repo, mark, force)
849 if rev:
849 if rev:
850 marks[mark] = scmutil.revsingle(repo, rev).node()
850 marks[mark] = scmutil.revsingle(repo, rev).node()
851 else:
851 else:
852 marks[mark] = cur
852 marks[mark] = cur
853 if not inactive and cur == marks[mark]:
853 if not inactive and cur == marks[mark]:
854 bookmarks.setcurrent(repo, mark)
854 bookmarks.setcurrent(repo, mark)
855 marks.write()
855 marks.write()
856
856
857 # Same message whether trying to deactivate the current bookmark (-i
857 # Same message whether trying to deactivate the current bookmark (-i
858 # with no NAME) or listing bookmarks
858 # with no NAME) or listing bookmarks
859 elif len(marks) == 0:
859 elif len(marks) == 0:
860 ui.status(_("no bookmarks set\n"))
860 ui.status(_("no bookmarks set\n"))
861
861
862 elif inactive:
862 elif inactive:
863 if not repo._bookmarkcurrent:
863 if not repo._bookmarkcurrent:
864 ui.status(_("no active bookmark\n"))
864 ui.status(_("no active bookmark\n"))
865 else:
865 else:
866 bookmarks.setcurrent(repo, None)
866 bookmarks.setcurrent(repo, None)
867
867
868 else: # show bookmarks
868 else: # show bookmarks
869 for bmark, n in sorted(marks.iteritems()):
869 for bmark, n in sorted(marks.iteritems()):
870 current = repo._bookmarkcurrent
870 current = repo._bookmarkcurrent
871 if bmark == current and n == cur:
871 if bmark == current and n == cur:
872 prefix, label = '*', 'bookmarks.current'
872 prefix, label = '*', 'bookmarks.current'
873 else:
873 else:
874 prefix, label = ' ', ''
874 prefix, label = ' ', ''
875
875
876 if ui.quiet:
876 if ui.quiet:
877 ui.write("%s\n" % bmark, label=label)
877 ui.write("%s\n" % bmark, label=label)
878 else:
878 else:
879 ui.write(" %s %-25s %d:%s\n" % (
879 ui.write(" %s %-25s %d:%s\n" % (
880 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
880 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
881 label=label)
881 label=label)
882
882
883 @command('branch',
883 @command('branch',
884 [('f', 'force', None,
884 [('f', 'force', None,
885 _('set branch name even if it shadows an existing branch')),
885 _('set branch name even if it shadows an existing branch')),
886 ('C', 'clean', None, _('reset branch name to parent branch name'))],
886 ('C', 'clean', None, _('reset branch name to parent branch name'))],
887 _('[-fC] [NAME]'))
887 _('[-fC] [NAME]'))
888 def branch(ui, repo, label=None, **opts):
888 def branch(ui, repo, label=None, **opts):
889 """set or show the current branch name
889 """set or show the current branch name
890
890
891 .. note::
891 .. note::
892 Branch names are permanent and global. Use :hg:`bookmark` to create a
892 Branch names are permanent and global. Use :hg:`bookmark` to create a
893 light-weight bookmark instead. See :hg:`help glossary` for more
893 light-weight bookmark instead. See :hg:`help glossary` for more
894 information about named branches and bookmarks.
894 information about named branches and bookmarks.
895
895
896 With no argument, show the current branch name. With one argument,
896 With no argument, show the current branch name. With one argument,
897 set the working directory branch name (the branch will not exist
897 set the working directory branch name (the branch will not exist
898 in the repository until the next commit). Standard practice
898 in the repository until the next commit). Standard practice
899 recommends that primary development take place on the 'default'
899 recommends that primary development take place on the 'default'
900 branch.
900 branch.
901
901
902 Unless -f/--force is specified, branch will not let you set a
902 Unless -f/--force is specified, branch will not let you set a
903 branch name that already exists, even if it's inactive.
903 branch name that already exists, even if it's inactive.
904
904
905 Use -C/--clean to reset the working directory branch to that of
905 Use -C/--clean to reset the working directory branch to that of
906 the parent of the working directory, negating a previous branch
906 the parent of the working directory, negating a previous branch
907 change.
907 change.
908
908
909 Use the command :hg:`update` to switch to an existing branch. Use
909 Use the command :hg:`update` to switch to an existing branch. Use
910 :hg:`commit --close-branch` to mark this branch as closed.
910 :hg:`commit --close-branch` to mark this branch as closed.
911
911
912 Returns 0 on success.
912 Returns 0 on success.
913 """
913 """
914 if not opts.get('clean') and not label:
914 if not opts.get('clean') and not label:
915 ui.write("%s\n" % repo.dirstate.branch())
915 ui.write("%s\n" % repo.dirstate.branch())
916 return
916 return
917
917
918 wlock = repo.wlock()
918 wlock = repo.wlock()
919 try:
919 try:
920 if opts.get('clean'):
920 if opts.get('clean'):
921 label = repo[None].p1().branch()
921 label = repo[None].p1().branch()
922 repo.dirstate.setbranch(label)
922 repo.dirstate.setbranch(label)
923 ui.status(_('reset working directory to branch %s\n') % label)
923 ui.status(_('reset working directory to branch %s\n') % label)
924 elif label:
924 elif label:
925 if not opts.get('force') and label in repo.branchmap():
925 if not opts.get('force') and label in repo.branchmap():
926 if label not in [p.branch() for p in repo.parents()]:
926 if label not in [p.branch() for p in repo.parents()]:
927 raise util.Abort(_('a branch of the same name already'
927 raise util.Abort(_('a branch of the same name already'
928 ' exists'),
928 ' exists'),
929 # i18n: "it" refers to an existing branch
929 # i18n: "it" refers to an existing branch
930 hint=_("use 'hg update' to switch to it"))
930 hint=_("use 'hg update' to switch to it"))
931 scmutil.checknewlabel(repo, label, 'branch')
931 scmutil.checknewlabel(repo, label, 'branch')
932 repo.dirstate.setbranch(label)
932 repo.dirstate.setbranch(label)
933 ui.status(_('marked working directory as branch %s\n') % label)
933 ui.status(_('marked working directory as branch %s\n') % label)
934 ui.status(_('(branches are permanent and global, '
934 ui.status(_('(branches are permanent and global, '
935 'did you want a bookmark?)\n'))
935 'did you want a bookmark?)\n'))
936 finally:
936 finally:
937 wlock.release()
937 wlock.release()
938
938
939 @command('branches',
939 @command('branches',
940 [('a', 'active', False, _('show only branches that have unmerged heads')),
940 [('a', 'active', False, _('show only branches that have unmerged heads')),
941 ('c', 'closed', False, _('show normal and closed branches'))],
941 ('c', 'closed', False, _('show normal and closed branches'))],
942 _('[-ac]'))
942 _('[-ac]'))
943 def branches(ui, repo, active=False, closed=False):
943 def branches(ui, repo, active=False, closed=False):
944 """list repository named branches
944 """list repository named branches
945
945
946 List the repository's named branches, indicating which ones are
946 List the repository's named branches, indicating which ones are
947 inactive. If -c/--closed is specified, also list branches which have
947 inactive. If -c/--closed is specified, also list branches which have
948 been marked closed (see :hg:`commit --close-branch`).
948 been marked closed (see :hg:`commit --close-branch`).
949
949
950 If -a/--active is specified, only show active branches. A branch
950 If -a/--active is specified, only show active branches. A branch
951 is considered active if it contains repository heads.
951 is considered active if it contains repository heads.
952
952
953 Use the command :hg:`update` to switch to an existing branch.
953 Use the command :hg:`update` to switch to an existing branch.
954
954
955 Returns 0.
955 Returns 0.
956 """
956 """
957
957
958 hexfunc = ui.debugflag and hex or short
958 hexfunc = ui.debugflag and hex or short
959
959
960 activebranches = set([repo[n].branch() for n in repo.heads()])
960 activebranches = set([repo[n].branch() for n in repo.heads()])
961 branches = []
961 branches = []
962 for tag, heads in repo.branchmap().iteritems():
962 for tag, heads in repo.branchmap().iteritems():
963 for h in reversed(heads):
963 for h in reversed(heads):
964 ctx = repo[h]
964 ctx = repo[h]
965 isopen = not ctx.closesbranch()
965 isopen = not ctx.closesbranch()
966 if isopen:
966 if isopen:
967 tip = ctx
967 tip = ctx
968 break
968 break
969 else:
969 else:
970 tip = repo[heads[-1]]
970 tip = repo[heads[-1]]
971 isactive = tag in activebranches and isopen
971 isactive = tag in activebranches and isopen
972 branches.append((tip, isactive, isopen))
972 branches.append((tip, isactive, isopen))
973 branches.sort(key=lambda i: (i[1], i[0].rev(), i[0].branch(), i[2]),
973 branches.sort(key=lambda i: (i[1], i[0].rev(), i[0].branch(), i[2]),
974 reverse=True)
974 reverse=True)
975
975
976 for ctx, isactive, isopen in branches:
976 for ctx, isactive, isopen in branches:
977 if (not active) or isactive:
977 if (not active) or isactive:
978 if isactive:
978 if isactive:
979 label = 'branches.active'
979 label = 'branches.active'
980 notice = ''
980 notice = ''
981 elif not isopen:
981 elif not isopen:
982 if not closed:
982 if not closed:
983 continue
983 continue
984 label = 'branches.closed'
984 label = 'branches.closed'
985 notice = _(' (closed)')
985 notice = _(' (closed)')
986 else:
986 else:
987 label = 'branches.inactive'
987 label = 'branches.inactive'
988 notice = _(' (inactive)')
988 notice = _(' (inactive)')
989 if ctx.branch() == repo.dirstate.branch():
989 if ctx.branch() == repo.dirstate.branch():
990 label = 'branches.current'
990 label = 'branches.current'
991 rev = str(ctx.rev()).rjust(31 - encoding.colwidth(ctx.branch()))
991 rev = str(ctx.rev()).rjust(31 - encoding.colwidth(ctx.branch()))
992 rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
992 rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
993 'log.changeset changeset.%s' % ctx.phasestr())
993 'log.changeset changeset.%s' % ctx.phasestr())
994 tag = ui.label(ctx.branch(), label)
994 tag = ui.label(ctx.branch(), label)
995 if ui.quiet:
995 if ui.quiet:
996 ui.write("%s\n" % tag)
996 ui.write("%s\n" % tag)
997 else:
997 else:
998 ui.write("%s %s%s\n" % (tag, rev, notice))
998 ui.write("%s %s%s\n" % (tag, rev, notice))
999
999
1000 @command('bundle',
1000 @command('bundle',
1001 [('f', 'force', None, _('run even when the destination is unrelated')),
1001 [('f', 'force', None, _('run even when the destination is unrelated')),
1002 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1002 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1003 _('REV')),
1003 _('REV')),
1004 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1004 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1005 _('BRANCH')),
1005 _('BRANCH')),
1006 ('', 'base', [],
1006 ('', 'base', [],
1007 _('a base changeset assumed to be available at the destination'),
1007 _('a base changeset assumed to be available at the destination'),
1008 _('REV')),
1008 _('REV')),
1009 ('a', 'all', None, _('bundle all changesets in the repository')),
1009 ('a', 'all', None, _('bundle all changesets in the repository')),
1010 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1010 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1011 ] + remoteopts,
1011 ] + remoteopts,
1012 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1012 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1013 def bundle(ui, repo, fname, dest=None, **opts):
1013 def bundle(ui, repo, fname, dest=None, **opts):
1014 """create a changegroup file
1014 """create a changegroup file
1015
1015
1016 Generate a compressed changegroup file collecting changesets not
1016 Generate a compressed changegroup file collecting changesets not
1017 known to be in another repository.
1017 known to be in another repository.
1018
1018
1019 If you omit the destination repository, then hg assumes the
1019 If you omit the destination repository, then hg assumes the
1020 destination will have all the nodes you specify with --base
1020 destination will have all the nodes you specify with --base
1021 parameters. To create a bundle containing all changesets, use
1021 parameters. To create a bundle containing all changesets, use
1022 -a/--all (or --base null).
1022 -a/--all (or --base null).
1023
1023
1024 You can change compression method with the -t/--type option.
1024 You can change compression method with the -t/--type option.
1025 The available compression methods are: none, bzip2, and
1025 The available compression methods are: none, bzip2, and
1026 gzip (by default, bundles are compressed using bzip2).
1026 gzip (by default, bundles are compressed using bzip2).
1027
1027
1028 The bundle file can then be transferred using conventional means
1028 The bundle file can then be transferred using conventional means
1029 and applied to another repository with the unbundle or pull
1029 and applied to another repository with the unbundle or pull
1030 command. This is useful when direct push and pull are not
1030 command. This is useful when direct push and pull are not
1031 available or when exporting an entire repository is undesirable.
1031 available or when exporting an entire repository is undesirable.
1032
1032
1033 Applying bundles preserves all changeset contents including
1033 Applying bundles preserves all changeset contents including
1034 permissions, copy/rename information, and revision history.
1034 permissions, copy/rename information, and revision history.
1035
1035
1036 Returns 0 on success, 1 if no changes found.
1036 Returns 0 on success, 1 if no changes found.
1037 """
1037 """
1038 revs = None
1038 revs = None
1039 if 'rev' in opts:
1039 if 'rev' in opts:
1040 revs = scmutil.revrange(repo, opts['rev'])
1040 revs = scmutil.revrange(repo, opts['rev'])
1041
1041
1042 bundletype = opts.get('type', 'bzip2').lower()
1042 bundletype = opts.get('type', 'bzip2').lower()
1043 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1043 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1044 bundletype = btypes.get(bundletype)
1044 bundletype = btypes.get(bundletype)
1045 if bundletype not in changegroup.bundletypes:
1045 if bundletype not in changegroup.bundletypes:
1046 raise util.Abort(_('unknown bundle type specified with --type'))
1046 raise util.Abort(_('unknown bundle type specified with --type'))
1047
1047
1048 if opts.get('all'):
1048 if opts.get('all'):
1049 base = ['null']
1049 base = ['null']
1050 else:
1050 else:
1051 base = scmutil.revrange(repo, opts.get('base'))
1051 base = scmutil.revrange(repo, opts.get('base'))
1052 if base:
1052 if base:
1053 if dest:
1053 if dest:
1054 raise util.Abort(_("--base is incompatible with specifying "
1054 raise util.Abort(_("--base is incompatible with specifying "
1055 "a destination"))
1055 "a destination"))
1056 common = [repo.lookup(rev) for rev in base]
1056 common = [repo.lookup(rev) for rev in base]
1057 heads = revs and map(repo.lookup, revs) or revs
1057 heads = revs and map(repo.lookup, revs) or revs
1058 cg = repo.getbundle('bundle', heads=heads, common=common)
1058 cg = repo.getbundle('bundle', heads=heads, common=common)
1059 outgoing = None
1059 outgoing = None
1060 else:
1060 else:
1061 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1061 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1062 dest, branches = hg.parseurl(dest, opts.get('branch'))
1062 dest, branches = hg.parseurl(dest, opts.get('branch'))
1063 other = hg.peer(repo, opts, dest)
1063 other = hg.peer(repo, opts, dest)
1064 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
1064 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
1065 heads = revs and map(repo.lookup, revs) or revs
1065 heads = revs and map(repo.lookup, revs) or revs
1066 outgoing = discovery.findcommonoutgoing(repo, other,
1066 outgoing = discovery.findcommonoutgoing(repo, other,
1067 onlyheads=heads,
1067 onlyheads=heads,
1068 force=opts.get('force'),
1068 force=opts.get('force'),
1069 portable=True)
1069 portable=True)
1070 cg = repo.getlocalbundle('bundle', outgoing)
1070 cg = repo.getlocalbundle('bundle', outgoing)
1071 if not cg:
1071 if not cg:
1072 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1072 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1073 return 1
1073 return 1
1074
1074
1075 changegroup.writebundle(cg, fname, bundletype)
1075 changegroup.writebundle(cg, fname, bundletype)
1076
1076
1077 @command('cat',
1077 @command('cat',
1078 [('o', 'output', '',
1078 [('o', 'output', '',
1079 _('print output to file with formatted name'), _('FORMAT')),
1079 _('print output to file with formatted name'), _('FORMAT')),
1080 ('r', 'rev', '', _('print the given revision'), _('REV')),
1080 ('r', 'rev', '', _('print the given revision'), _('REV')),
1081 ('', 'decode', None, _('apply any matching decode filter')),
1081 ('', 'decode', None, _('apply any matching decode filter')),
1082 ] + walkopts,
1082 ] + walkopts,
1083 _('[OPTION]... FILE...'))
1083 _('[OPTION]... FILE...'))
1084 def cat(ui, repo, file1, *pats, **opts):
1084 def cat(ui, repo, file1, *pats, **opts):
1085 """output the current or given revision of files
1085 """output the current or given revision of files
1086
1086
1087 Print the specified files as they were at the given revision. If
1087 Print the specified files as they were at the given revision. If
1088 no revision is given, the parent of the working directory is used,
1088 no revision is given, the parent of the working directory is used,
1089 or tip if no revision is checked out.
1089 or tip if no revision is checked out.
1090
1090
1091 Output may be to a file, in which case the name of the file is
1091 Output may be to a file, in which case the name of the file is
1092 given using a format string. The formatting rules are the same as
1092 given using a format string. The formatting rules are the same as
1093 for the export command, with the following additions:
1093 for the export command, with the following additions:
1094
1094
1095 :``%s``: basename of file being printed
1095 :``%s``: basename of file being printed
1096 :``%d``: dirname of file being printed, or '.' if in repository root
1096 :``%d``: dirname of file being printed, or '.' if in repository root
1097 :``%p``: root-relative path name of file being printed
1097 :``%p``: root-relative path name of file being printed
1098
1098
1099 Returns 0 on success.
1099 Returns 0 on success.
1100 """
1100 """
1101 ctx = scmutil.revsingle(repo, opts.get('rev'))
1101 ctx = scmutil.revsingle(repo, opts.get('rev'))
1102 err = 1
1102 err = 1
1103 m = scmutil.match(ctx, (file1,) + pats, opts)
1103 m = scmutil.match(ctx, (file1,) + pats, opts)
1104 for abs in ctx.walk(m):
1104 for abs in ctx.walk(m):
1105 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1105 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1106 pathname=abs)
1106 pathname=abs)
1107 data = ctx[abs].data()
1107 data = ctx[abs].data()
1108 if opts.get('decode'):
1108 if opts.get('decode'):
1109 data = repo.wwritedata(abs, data)
1109 data = repo.wwritedata(abs, data)
1110 fp.write(data)
1110 fp.write(data)
1111 fp.close()
1111 fp.close()
1112 err = 0
1112 err = 0
1113 return err
1113 return err
1114
1114
1115 @command('^clone',
1115 @command('^clone',
1116 [('U', 'noupdate', None,
1116 [('U', 'noupdate', None,
1117 _('the clone will include an empty working copy (only a repository)')),
1117 _('the clone will include an empty working copy (only a repository)')),
1118 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1118 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1119 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1119 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1120 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1120 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1121 ('', 'pull', None, _('use pull protocol to copy metadata')),
1121 ('', 'pull', None, _('use pull protocol to copy metadata')),
1122 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1122 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1123 ] + remoteopts,
1123 ] + remoteopts,
1124 _('[OPTION]... SOURCE [DEST]'))
1124 _('[OPTION]... SOURCE [DEST]'))
1125 def clone(ui, source, dest=None, **opts):
1125 def clone(ui, source, dest=None, **opts):
1126 """make a copy of an existing repository
1126 """make a copy of an existing repository
1127
1127
1128 Create a copy of an existing repository in a new directory.
1128 Create a copy of an existing repository in a new directory.
1129
1129
1130 If no destination directory name is specified, it defaults to the
1130 If no destination directory name is specified, it defaults to the
1131 basename of the source.
1131 basename of the source.
1132
1132
1133 The location of the source is added to the new repository's
1133 The location of the source is added to the new repository's
1134 ``.hg/hgrc`` file, as the default to be used for future pulls.
1134 ``.hg/hgrc`` file, as the default to be used for future pulls.
1135
1135
1136 Only local paths and ``ssh://`` URLs are supported as
1136 Only local paths and ``ssh://`` URLs are supported as
1137 destinations. For ``ssh://`` destinations, no working directory or
1137 destinations. For ``ssh://`` destinations, no working directory or
1138 ``.hg/hgrc`` will be created on the remote side.
1138 ``.hg/hgrc`` will be created on the remote side.
1139
1139
1140 To pull only a subset of changesets, specify one or more revisions
1140 To pull only a subset of changesets, specify one or more revisions
1141 identifiers with -r/--rev or branches with -b/--branch. The
1141 identifiers with -r/--rev or branches with -b/--branch. The
1142 resulting clone will contain only the specified changesets and
1142 resulting clone will contain only the specified changesets and
1143 their ancestors. These options (or 'clone src#rev dest') imply
1143 their ancestors. These options (or 'clone src#rev dest') imply
1144 --pull, even for local source repositories. Note that specifying a
1144 --pull, even for local source repositories. Note that specifying a
1145 tag will include the tagged changeset but not the changeset
1145 tag will include the tagged changeset but not the changeset
1146 containing the tag.
1146 containing the tag.
1147
1147
1148 To check out a particular version, use -u/--update, or
1148 To check out a particular version, use -u/--update, or
1149 -U/--noupdate to create a clone with no working directory.
1149 -U/--noupdate to create a clone with no working directory.
1150
1150
1151 .. container:: verbose
1151 .. container:: verbose
1152
1152
1153 For efficiency, hardlinks are used for cloning whenever the
1153 For efficiency, hardlinks are used for cloning whenever the
1154 source and destination are on the same filesystem (note this
1154 source and destination are on the same filesystem (note this
1155 applies only to the repository data, not to the working
1155 applies only to the repository data, not to the working
1156 directory). Some filesystems, such as AFS, implement hardlinking
1156 directory). Some filesystems, such as AFS, implement hardlinking
1157 incorrectly, but do not report errors. In these cases, use the
1157 incorrectly, but do not report errors. In these cases, use the
1158 --pull option to avoid hardlinking.
1158 --pull option to avoid hardlinking.
1159
1159
1160 In some cases, you can clone repositories and the working
1160 In some cases, you can clone repositories and the working
1161 directory using full hardlinks with ::
1161 directory using full hardlinks with ::
1162
1162
1163 $ cp -al REPO REPOCLONE
1163 $ cp -al REPO REPOCLONE
1164
1164
1165 This is the fastest way to clone, but it is not always safe. The
1165 This is the fastest way to clone, but it is not always safe. The
1166 operation is not atomic (making sure REPO is not modified during
1166 operation is not atomic (making sure REPO is not modified during
1167 the operation is up to you) and you have to make sure your
1167 the operation is up to you) and you have to make sure your
1168 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1168 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1169 so). Also, this is not compatible with certain extensions that
1169 so). Also, this is not compatible with certain extensions that
1170 place their metadata under the .hg directory, such as mq.
1170 place their metadata under the .hg directory, such as mq.
1171
1171
1172 Mercurial will update the working directory to the first applicable
1172 Mercurial will update the working directory to the first applicable
1173 revision from this list:
1173 revision from this list:
1174
1174
1175 a) null if -U or the source repository has no changesets
1175 a) null if -U or the source repository has no changesets
1176 b) if -u . and the source repository is local, the first parent of
1176 b) if -u . and the source repository is local, the first parent of
1177 the source repository's working directory
1177 the source repository's working directory
1178 c) the changeset specified with -u (if a branch name, this means the
1178 c) the changeset specified with -u (if a branch name, this means the
1179 latest head of that branch)
1179 latest head of that branch)
1180 d) the changeset specified with -r
1180 d) the changeset specified with -r
1181 e) the tipmost head specified with -b
1181 e) the tipmost head specified with -b
1182 f) the tipmost head specified with the url#branch source syntax
1182 f) the tipmost head specified with the url#branch source syntax
1183 g) the tipmost head of the default branch
1183 g) the tipmost head of the default branch
1184 h) tip
1184 h) tip
1185
1185
1186 Examples:
1186 Examples:
1187
1187
1188 - clone a remote repository to a new directory named hg/::
1188 - clone a remote repository to a new directory named hg/::
1189
1189
1190 hg clone http://selenic.com/hg
1190 hg clone http://selenic.com/hg
1191
1191
1192 - create a lightweight local clone::
1192 - create a lightweight local clone::
1193
1193
1194 hg clone project/ project-feature/
1194 hg clone project/ project-feature/
1195
1195
1196 - clone from an absolute path on an ssh server (note double-slash)::
1196 - clone from an absolute path on an ssh server (note double-slash)::
1197
1197
1198 hg clone ssh://user@server//home/projects/alpha/
1198 hg clone ssh://user@server//home/projects/alpha/
1199
1199
1200 - do a high-speed clone over a LAN while checking out a
1200 - do a high-speed clone over a LAN while checking out a
1201 specified version::
1201 specified version::
1202
1202
1203 hg clone --uncompressed http://server/repo -u 1.5
1203 hg clone --uncompressed http://server/repo -u 1.5
1204
1204
1205 - create a repository without changesets after a particular revision::
1205 - create a repository without changesets after a particular revision::
1206
1206
1207 hg clone -r 04e544 experimental/ good/
1207 hg clone -r 04e544 experimental/ good/
1208
1208
1209 - clone (and track) a particular named branch::
1209 - clone (and track) a particular named branch::
1210
1210
1211 hg clone http://selenic.com/hg#stable
1211 hg clone http://selenic.com/hg#stable
1212
1212
1213 See :hg:`help urls` for details on specifying URLs.
1213 See :hg:`help urls` for details on specifying URLs.
1214
1214
1215 Returns 0 on success.
1215 Returns 0 on success.
1216 """
1216 """
1217 if opts.get('noupdate') and opts.get('updaterev'):
1217 if opts.get('noupdate') and opts.get('updaterev'):
1218 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1218 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1219
1219
1220 r = hg.clone(ui, opts, source, dest,
1220 r = hg.clone(ui, opts, source, dest,
1221 pull=opts.get('pull'),
1221 pull=opts.get('pull'),
1222 stream=opts.get('uncompressed'),
1222 stream=opts.get('uncompressed'),
1223 rev=opts.get('rev'),
1223 rev=opts.get('rev'),
1224 update=opts.get('updaterev') or not opts.get('noupdate'),
1224 update=opts.get('updaterev') or not opts.get('noupdate'),
1225 branch=opts.get('branch'))
1225 branch=opts.get('branch'))
1226
1226
1227 return r is None
1227 return r is None
1228
1228
1229 @command('^commit|ci',
1229 @command('^commit|ci',
1230 [('A', 'addremove', None,
1230 [('A', 'addremove', None,
1231 _('mark new/missing files as added/removed before committing')),
1231 _('mark new/missing files as added/removed before committing')),
1232 ('', 'close-branch', None,
1232 ('', 'close-branch', None,
1233 _('mark a branch as closed, hiding it from the branch list')),
1233 _('mark a branch as closed, hiding it from the branch list')),
1234 ('', 'amend', None, _('amend the parent of the working dir')),
1234 ('', 'amend', None, _('amend the parent of the working dir')),
1235 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1235 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1236 _('[OPTION]... [FILE]...'))
1236 _('[OPTION]... [FILE]...'))
1237 def commit(ui, repo, *pats, **opts):
1237 def commit(ui, repo, *pats, **opts):
1238 """commit the specified files or all outstanding changes
1238 """commit the specified files or all outstanding changes
1239
1239
1240 Commit changes to the given files into the repository. Unlike a
1240 Commit changes to the given files into the repository. Unlike a
1241 centralized SCM, this operation is a local operation. See
1241 centralized SCM, this operation is a local operation. See
1242 :hg:`push` for a way to actively distribute your changes.
1242 :hg:`push` for a way to actively distribute your changes.
1243
1243
1244 If a list of files is omitted, all changes reported by :hg:`status`
1244 If a list of files is omitted, all changes reported by :hg:`status`
1245 will be committed.
1245 will be committed.
1246
1246
1247 If you are committing the result of a merge, do not provide any
1247 If you are committing the result of a merge, do not provide any
1248 filenames or -I/-X filters.
1248 filenames or -I/-X filters.
1249
1249
1250 If no commit message is specified, Mercurial starts your
1250 If no commit message is specified, Mercurial starts your
1251 configured editor where you can enter a message. In case your
1251 configured editor where you can enter a message. In case your
1252 commit fails, you will find a backup of your message in
1252 commit fails, you will find a backup of your message in
1253 ``.hg/last-message.txt``.
1253 ``.hg/last-message.txt``.
1254
1254
1255 The --amend flag can be used to amend the parent of the
1255 The --amend flag can be used to amend the parent of the
1256 working directory with a new commit that contains the changes
1256 working directory with a new commit that contains the changes
1257 in the parent in addition to those currently reported by :hg:`status`,
1257 in the parent in addition to those currently reported by :hg:`status`,
1258 if there are any. The old commit is stored in a backup bundle in
1258 if there are any. The old commit is stored in a backup bundle in
1259 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1259 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1260 on how to restore it).
1260 on how to restore it).
1261
1261
1262 Message, user and date are taken from the amended commit unless
1262 Message, user and date are taken from the amended commit unless
1263 specified. When a message isn't specified on the command line,
1263 specified. When a message isn't specified on the command line,
1264 the editor will open with the message of the amended commit.
1264 the editor will open with the message of the amended commit.
1265
1265
1266 It is not possible to amend public changesets (see :hg:`help phases`)
1266 It is not possible to amend public changesets (see :hg:`help phases`)
1267 or changesets that have children.
1267 or changesets that have children.
1268
1268
1269 See :hg:`help dates` for a list of formats valid for -d/--date.
1269 See :hg:`help dates` for a list of formats valid for -d/--date.
1270
1270
1271 Returns 0 on success, 1 if nothing changed.
1271 Returns 0 on success, 1 if nothing changed.
1272 """
1272 """
1273 if opts.get('subrepos'):
1273 if opts.get('subrepos'):
1274 # Let --subrepos on the command line override config setting.
1274 # Let --subrepos on the command line override config setting.
1275 ui.setconfig('ui', 'commitsubrepos', True)
1275 ui.setconfig('ui', 'commitsubrepos', True)
1276
1276
1277 extra = {}
1277 extra = {}
1278 if opts.get('close_branch'):
1278 if opts.get('close_branch'):
1279 if repo['.'].node() not in repo.branchheads():
1279 if repo['.'].node() not in repo.branchheads():
1280 # The topo heads set is included in the branch heads set of the
1280 # The topo heads set is included in the branch heads set of the
1281 # current branch, so it's sufficient to test branchheads
1281 # current branch, so it's sufficient to test branchheads
1282 raise util.Abort(_('can only close branch heads'))
1282 raise util.Abort(_('can only close branch heads'))
1283 extra['close'] = 1
1283 extra['close'] = 1
1284
1284
1285 branch = repo[None].branch()
1285 branch = repo[None].branch()
1286 bheads = repo.branchheads(branch)
1286 bheads = repo.branchheads(branch)
1287
1287
1288 if opts.get('amend'):
1288 if opts.get('amend'):
1289 if ui.configbool('ui', 'commitsubrepos'):
1289 if ui.configbool('ui', 'commitsubrepos'):
1290 raise util.Abort(_('cannot amend recursively'))
1290 raise util.Abort(_('cannot amend recursively'))
1291
1291
1292 old = repo['.']
1292 old = repo['.']
1293 if old.phase() == phases.public:
1293 if old.phase() == phases.public:
1294 raise util.Abort(_('cannot amend public changesets'))
1294 raise util.Abort(_('cannot amend public changesets'))
1295 if len(old.parents()) > 1:
1295 if len(old.parents()) > 1:
1296 raise util.Abort(_('cannot amend merge changesets'))
1296 raise util.Abort(_('cannot amend merge changesets'))
1297 if len(repo[None].parents()) > 1:
1297 if len(repo[None].parents()) > 1:
1298 raise util.Abort(_('cannot amend while merging'))
1298 raise util.Abort(_('cannot amend while merging'))
1299 if old.children():
1299 if old.children():
1300 raise util.Abort(_('cannot amend changeset with children'))
1300 raise util.Abort(_('cannot amend changeset with children'))
1301
1301
1302 e = cmdutil.commiteditor
1302 e = cmdutil.commiteditor
1303 if opts.get('force_editor'):
1303 if opts.get('force_editor'):
1304 e = cmdutil.commitforceeditor
1304 e = cmdutil.commitforceeditor
1305
1305
1306 def commitfunc(ui, repo, message, match, opts):
1306 def commitfunc(ui, repo, message, match, opts):
1307 editor = e
1307 editor = e
1308 # message contains text from -m or -l, if it's empty,
1308 # message contains text from -m or -l, if it's empty,
1309 # open the editor with the old message
1309 # open the editor with the old message
1310 if not message:
1310 if not message:
1311 message = old.description()
1311 message = old.description()
1312 editor = cmdutil.commitforceeditor
1312 editor = cmdutil.commitforceeditor
1313 return repo.commit(message,
1313 return repo.commit(message,
1314 opts.get('user') or old.user(),
1314 opts.get('user') or old.user(),
1315 opts.get('date') or old.date(),
1315 opts.get('date') or old.date(),
1316 match,
1316 match,
1317 editor=editor,
1317 editor=editor,
1318 extra=extra)
1318 extra=extra)
1319
1319
1320 current = repo._bookmarkcurrent
1320 current = repo._bookmarkcurrent
1321 marks = old.bookmarks()
1321 marks = old.bookmarks()
1322 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1322 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1323 if node == old.node():
1323 if node == old.node():
1324 ui.status(_("nothing changed\n"))
1324 ui.status(_("nothing changed\n"))
1325 return 1
1325 return 1
1326 elif marks:
1326 elif marks:
1327 ui.debug('moving bookmarks %r from %s to %s\n' %
1327 ui.debug('moving bookmarks %r from %s to %s\n' %
1328 (marks, old.hex(), hex(node)))
1328 (marks, old.hex(), hex(node)))
1329 newmarks = repo._bookmarks
1329 newmarks = repo._bookmarks
1330 for bm in marks:
1330 for bm in marks:
1331 newmarks[bm] = node
1331 newmarks[bm] = node
1332 if bm == current:
1332 if bm == current:
1333 bookmarks.setcurrent(repo, bm)
1333 bookmarks.setcurrent(repo, bm)
1334 newmarks.write()
1334 newmarks.write()
1335 else:
1335 else:
1336 e = cmdutil.commiteditor
1336 e = cmdutil.commiteditor
1337 if opts.get('force_editor'):
1337 if opts.get('force_editor'):
1338 e = cmdutil.commitforceeditor
1338 e = cmdutil.commitforceeditor
1339
1339
1340 def commitfunc(ui, repo, message, match, opts):
1340 def commitfunc(ui, repo, message, match, opts):
1341 return repo.commit(message, opts.get('user'), opts.get('date'),
1341 return repo.commit(message, opts.get('user'), opts.get('date'),
1342 match, editor=e, extra=extra)
1342 match, editor=e, extra=extra)
1343
1343
1344 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1344 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1345
1345
1346 if not node:
1346 if not node:
1347 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1347 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1348 if stat[3]:
1348 if stat[3]:
1349 ui.status(_("nothing changed (%d missing files, see "
1349 ui.status(_("nothing changed (%d missing files, see "
1350 "'hg status')\n") % len(stat[3]))
1350 "'hg status')\n") % len(stat[3]))
1351 else:
1351 else:
1352 ui.status(_("nothing changed\n"))
1352 ui.status(_("nothing changed\n"))
1353 return 1
1353 return 1
1354
1354
1355 ctx = repo[node]
1355 ctx = repo[node]
1356 parents = ctx.parents()
1356 parents = ctx.parents()
1357
1357
1358 if (not opts.get('amend') and bheads and node not in bheads and not
1358 if (not opts.get('amend') and bheads and node not in bheads and not
1359 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1359 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1360 ui.status(_('created new head\n'))
1360 ui.status(_('created new head\n'))
1361 # The message is not printed for initial roots. For the other
1361 # The message is not printed for initial roots. For the other
1362 # changesets, it is printed in the following situations:
1362 # changesets, it is printed in the following situations:
1363 #
1363 #
1364 # Par column: for the 2 parents with ...
1364 # Par column: for the 2 parents with ...
1365 # N: null or no parent
1365 # N: null or no parent
1366 # B: parent is on another named branch
1366 # B: parent is on another named branch
1367 # C: parent is a regular non head changeset
1367 # C: parent is a regular non head changeset
1368 # H: parent was a branch head of the current branch
1368 # H: parent was a branch head of the current branch
1369 # Msg column: whether we print "created new head" message
1369 # Msg column: whether we print "created new head" message
1370 # In the following, it is assumed that there already exists some
1370 # In the following, it is assumed that there already exists some
1371 # initial branch heads of the current branch, otherwise nothing is
1371 # initial branch heads of the current branch, otherwise nothing is
1372 # printed anyway.
1372 # printed anyway.
1373 #
1373 #
1374 # Par Msg Comment
1374 # Par Msg Comment
1375 # N N y additional topo root
1375 # N N y additional topo root
1376 #
1376 #
1377 # B N y additional branch root
1377 # B N y additional branch root
1378 # C N y additional topo head
1378 # C N y additional topo head
1379 # H N n usual case
1379 # H N n usual case
1380 #
1380 #
1381 # B B y weird additional branch root
1381 # B B y weird additional branch root
1382 # C B y branch merge
1382 # C B y branch merge
1383 # H B n merge with named branch
1383 # H B n merge with named branch
1384 #
1384 #
1385 # C C y additional head from merge
1385 # C C y additional head from merge
1386 # C H n merge with a head
1386 # C H n merge with a head
1387 #
1387 #
1388 # H H n head merge: head count decreases
1388 # H H n head merge: head count decreases
1389
1389
1390 if not opts.get('close_branch'):
1390 if not opts.get('close_branch'):
1391 for r in parents:
1391 for r in parents:
1392 if r.closesbranch() and r.branch() == branch:
1392 if r.closesbranch() and r.branch() == branch:
1393 ui.status(_('reopening closed branch head %d\n') % r)
1393 ui.status(_('reopening closed branch head %d\n') % r)
1394
1394
1395 if ui.debugflag:
1395 if ui.debugflag:
1396 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1396 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1397 elif ui.verbose:
1397 elif ui.verbose:
1398 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1398 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1399
1399
1400 @command('copy|cp',
1400 @command('copy|cp',
1401 [('A', 'after', None, _('record a copy that has already occurred')),
1401 [('A', 'after', None, _('record a copy that has already occurred')),
1402 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1402 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1403 ] + walkopts + dryrunopts,
1403 ] + walkopts + dryrunopts,
1404 _('[OPTION]... [SOURCE]... DEST'))
1404 _('[OPTION]... [SOURCE]... DEST'))
1405 def copy(ui, repo, *pats, **opts):
1405 def copy(ui, repo, *pats, **opts):
1406 """mark files as copied for the next commit
1406 """mark files as copied for the next commit
1407
1407
1408 Mark dest as having copies of source files. If dest is a
1408 Mark dest as having copies of source files. If dest is a
1409 directory, copies are put in that directory. If dest is a file,
1409 directory, copies are put in that directory. If dest is a file,
1410 the source must be a single file.
1410 the source must be a single file.
1411
1411
1412 By default, this command copies the contents of files as they
1412 By default, this command copies the contents of files as they
1413 exist in the working directory. If invoked with -A/--after, the
1413 exist in the working directory. If invoked with -A/--after, the
1414 operation is recorded, but no copying is performed.
1414 operation is recorded, but no copying is performed.
1415
1415
1416 This command takes effect with the next commit. To undo a copy
1416 This command takes effect with the next commit. To undo a copy
1417 before that, see :hg:`revert`.
1417 before that, see :hg:`revert`.
1418
1418
1419 Returns 0 on success, 1 if errors are encountered.
1419 Returns 0 on success, 1 if errors are encountered.
1420 """
1420 """
1421 wlock = repo.wlock(False)
1421 wlock = repo.wlock(False)
1422 try:
1422 try:
1423 return cmdutil.copy(ui, repo, pats, opts)
1423 return cmdutil.copy(ui, repo, pats, opts)
1424 finally:
1424 finally:
1425 wlock.release()
1425 wlock.release()
1426
1426
1427 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1427 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1428 def debugancestor(ui, repo, *args):
1428 def debugancestor(ui, repo, *args):
1429 """find the ancestor revision of two revisions in a given index"""
1429 """find the ancestor revision of two revisions in a given index"""
1430 if len(args) == 3:
1430 if len(args) == 3:
1431 index, rev1, rev2 = args
1431 index, rev1, rev2 = args
1432 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1432 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1433 lookup = r.lookup
1433 lookup = r.lookup
1434 elif len(args) == 2:
1434 elif len(args) == 2:
1435 if not repo:
1435 if not repo:
1436 raise util.Abort(_("there is no Mercurial repository here "
1436 raise util.Abort(_("there is no Mercurial repository here "
1437 "(.hg not found)"))
1437 "(.hg not found)"))
1438 rev1, rev2 = args
1438 rev1, rev2 = args
1439 r = repo.changelog
1439 r = repo.changelog
1440 lookup = repo.lookup
1440 lookup = repo.lookup
1441 else:
1441 else:
1442 raise util.Abort(_('either two or three arguments required'))
1442 raise util.Abort(_('either two or three arguments required'))
1443 a = r.ancestor(lookup(rev1), lookup(rev2))
1443 a = r.ancestor(lookup(rev1), lookup(rev2))
1444 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1444 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1445
1445
1446 @command('debugbuilddag',
1446 @command('debugbuilddag',
1447 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1447 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1448 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1448 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1449 ('n', 'new-file', None, _('add new file at each rev'))],
1449 ('n', 'new-file', None, _('add new file at each rev'))],
1450 _('[OPTION]... [TEXT]'))
1450 _('[OPTION]... [TEXT]'))
1451 def debugbuilddag(ui, repo, text=None,
1451 def debugbuilddag(ui, repo, text=None,
1452 mergeable_file=False,
1452 mergeable_file=False,
1453 overwritten_file=False,
1453 overwritten_file=False,
1454 new_file=False):
1454 new_file=False):
1455 """builds a repo with a given DAG from scratch in the current empty repo
1455 """builds a repo with a given DAG from scratch in the current empty repo
1456
1456
1457 The description of the DAG is read from stdin if not given on the
1457 The description of the DAG is read from stdin if not given on the
1458 command line.
1458 command line.
1459
1459
1460 Elements:
1460 Elements:
1461
1461
1462 - "+n" is a linear run of n nodes based on the current default parent
1462 - "+n" is a linear run of n nodes based on the current default parent
1463 - "." is a single node based on the current default parent
1463 - "." is a single node based on the current default parent
1464 - "$" resets the default parent to null (implied at the start);
1464 - "$" resets the default parent to null (implied at the start);
1465 otherwise the default parent is always the last node created
1465 otherwise the default parent is always the last node created
1466 - "<p" sets the default parent to the backref p
1466 - "<p" sets the default parent to the backref p
1467 - "*p" is a fork at parent p, which is a backref
1467 - "*p" is a fork at parent p, which is a backref
1468 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1468 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1469 - "/p2" is a merge of the preceding node and p2
1469 - "/p2" is a merge of the preceding node and p2
1470 - ":tag" defines a local tag for the preceding node
1470 - ":tag" defines a local tag for the preceding node
1471 - "@branch" sets the named branch for subsequent nodes
1471 - "@branch" sets the named branch for subsequent nodes
1472 - "#...\\n" is a comment up to the end of the line
1472 - "#...\\n" is a comment up to the end of the line
1473
1473
1474 Whitespace between the above elements is ignored.
1474 Whitespace between the above elements is ignored.
1475
1475
1476 A backref is either
1476 A backref is either
1477
1477
1478 - a number n, which references the node curr-n, where curr is the current
1478 - a number n, which references the node curr-n, where curr is the current
1479 node, or
1479 node, or
1480 - the name of a local tag you placed earlier using ":tag", or
1480 - the name of a local tag you placed earlier using ":tag", or
1481 - empty to denote the default parent.
1481 - empty to denote the default parent.
1482
1482
1483 All string valued-elements are either strictly alphanumeric, or must
1483 All string valued-elements are either strictly alphanumeric, or must
1484 be enclosed in double quotes ("..."), with "\\" as escape character.
1484 be enclosed in double quotes ("..."), with "\\" as escape character.
1485 """
1485 """
1486
1486
1487 if text is None:
1487 if text is None:
1488 ui.status(_("reading DAG from stdin\n"))
1488 ui.status(_("reading DAG from stdin\n"))
1489 text = ui.fin.read()
1489 text = ui.fin.read()
1490
1490
1491 cl = repo.changelog
1491 cl = repo.changelog
1492 if len(cl) > 0:
1492 if len(cl) > 0:
1493 raise util.Abort(_('repository is not empty'))
1493 raise util.Abort(_('repository is not empty'))
1494
1494
1495 # determine number of revs in DAG
1495 # determine number of revs in DAG
1496 total = 0
1496 total = 0
1497 for type, data in dagparser.parsedag(text):
1497 for type, data in dagparser.parsedag(text):
1498 if type == 'n':
1498 if type == 'n':
1499 total += 1
1499 total += 1
1500
1500
1501 if mergeable_file:
1501 if mergeable_file:
1502 linesperrev = 2
1502 linesperrev = 2
1503 # make a file with k lines per rev
1503 # make a file with k lines per rev
1504 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1504 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1505 initialmergedlines.append("")
1505 initialmergedlines.append("")
1506
1506
1507 tags = []
1507 tags = []
1508
1508
1509 lock = tr = None
1509 lock = tr = None
1510 try:
1510 try:
1511 lock = repo.lock()
1511 lock = repo.lock()
1512 tr = repo.transaction("builddag")
1512 tr = repo.transaction("builddag")
1513
1513
1514 at = -1
1514 at = -1
1515 atbranch = 'default'
1515 atbranch = 'default'
1516 nodeids = []
1516 nodeids = []
1517 id = 0
1517 id = 0
1518 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1518 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1519 for type, data in dagparser.parsedag(text):
1519 for type, data in dagparser.parsedag(text):
1520 if type == 'n':
1520 if type == 'n':
1521 ui.note(('node %s\n' % str(data)))
1521 ui.note(('node %s\n' % str(data)))
1522 id, ps = data
1522 id, ps = data
1523
1523
1524 files = []
1524 files = []
1525 fctxs = {}
1525 fctxs = {}
1526
1526
1527 p2 = None
1527 p2 = None
1528 if mergeable_file:
1528 if mergeable_file:
1529 fn = "mf"
1529 fn = "mf"
1530 p1 = repo[ps[0]]
1530 p1 = repo[ps[0]]
1531 if len(ps) > 1:
1531 if len(ps) > 1:
1532 p2 = repo[ps[1]]
1532 p2 = repo[ps[1]]
1533 pa = p1.ancestor(p2)
1533 pa = p1.ancestor(p2)
1534 base, local, other = [x[fn].data() for x in pa, p1, p2]
1534 base, local, other = [x[fn].data() for x in pa, p1, p2]
1535 m3 = simplemerge.Merge3Text(base, local, other)
1535 m3 = simplemerge.Merge3Text(base, local, other)
1536 ml = [l.strip() for l in m3.merge_lines()]
1536 ml = [l.strip() for l in m3.merge_lines()]
1537 ml.append("")
1537 ml.append("")
1538 elif at > 0:
1538 elif at > 0:
1539 ml = p1[fn].data().split("\n")
1539 ml = p1[fn].data().split("\n")
1540 else:
1540 else:
1541 ml = initialmergedlines
1541 ml = initialmergedlines
1542 ml[id * linesperrev] += " r%i" % id
1542 ml[id * linesperrev] += " r%i" % id
1543 mergedtext = "\n".join(ml)
1543 mergedtext = "\n".join(ml)
1544 files.append(fn)
1544 files.append(fn)
1545 fctxs[fn] = context.memfilectx(fn, mergedtext)
1545 fctxs[fn] = context.memfilectx(fn, mergedtext)
1546
1546
1547 if overwritten_file:
1547 if overwritten_file:
1548 fn = "of"
1548 fn = "of"
1549 files.append(fn)
1549 files.append(fn)
1550 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1550 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1551
1551
1552 if new_file:
1552 if new_file:
1553 fn = "nf%i" % id
1553 fn = "nf%i" % id
1554 files.append(fn)
1554 files.append(fn)
1555 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1555 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1556 if len(ps) > 1:
1556 if len(ps) > 1:
1557 if not p2:
1557 if not p2:
1558 p2 = repo[ps[1]]
1558 p2 = repo[ps[1]]
1559 for fn in p2:
1559 for fn in p2:
1560 if fn.startswith("nf"):
1560 if fn.startswith("nf"):
1561 files.append(fn)
1561 files.append(fn)
1562 fctxs[fn] = p2[fn]
1562 fctxs[fn] = p2[fn]
1563
1563
1564 def fctxfn(repo, cx, path):
1564 def fctxfn(repo, cx, path):
1565 return fctxs.get(path)
1565 return fctxs.get(path)
1566
1566
1567 if len(ps) == 0 or ps[0] < 0:
1567 if len(ps) == 0 or ps[0] < 0:
1568 pars = [None, None]
1568 pars = [None, None]
1569 elif len(ps) == 1:
1569 elif len(ps) == 1:
1570 pars = [nodeids[ps[0]], None]
1570 pars = [nodeids[ps[0]], None]
1571 else:
1571 else:
1572 pars = [nodeids[p] for p in ps]
1572 pars = [nodeids[p] for p in ps]
1573 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1573 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1574 date=(id, 0),
1574 date=(id, 0),
1575 user="debugbuilddag",
1575 user="debugbuilddag",
1576 extra={'branch': atbranch})
1576 extra={'branch': atbranch})
1577 nodeid = repo.commitctx(cx)
1577 nodeid = repo.commitctx(cx)
1578 nodeids.append(nodeid)
1578 nodeids.append(nodeid)
1579 at = id
1579 at = id
1580 elif type == 'l':
1580 elif type == 'l':
1581 id, name = data
1581 id, name = data
1582 ui.note(('tag %s\n' % name))
1582 ui.note(('tag %s\n' % name))
1583 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1583 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1584 elif type == 'a':
1584 elif type == 'a':
1585 ui.note(('branch %s\n' % data))
1585 ui.note(('branch %s\n' % data))
1586 atbranch = data
1586 atbranch = data
1587 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1587 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1588 tr.close()
1588 tr.close()
1589
1589
1590 if tags:
1590 if tags:
1591 repo.opener.write("localtags", "".join(tags))
1591 repo.opener.write("localtags", "".join(tags))
1592 finally:
1592 finally:
1593 ui.progress(_('building'), None)
1593 ui.progress(_('building'), None)
1594 release(tr, lock)
1594 release(tr, lock)
1595
1595
1596 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1596 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1597 def debugbundle(ui, bundlepath, all=None, **opts):
1597 def debugbundle(ui, bundlepath, all=None, **opts):
1598 """lists the contents of a bundle"""
1598 """lists the contents of a bundle"""
1599 f = hg.openpath(ui, bundlepath)
1599 f = hg.openpath(ui, bundlepath)
1600 try:
1600 try:
1601 gen = changegroup.readbundle(f, bundlepath)
1601 gen = changegroup.readbundle(f, bundlepath)
1602 if all:
1602 if all:
1603 ui.write(("format: id, p1, p2, cset, delta base, len(delta)\n"))
1603 ui.write(("format: id, p1, p2, cset, delta base, len(delta)\n"))
1604
1604
1605 def showchunks(named):
1605 def showchunks(named):
1606 ui.write("\n%s\n" % named)
1606 ui.write("\n%s\n" % named)
1607 chain = None
1607 chain = None
1608 while True:
1608 while True:
1609 chunkdata = gen.deltachunk(chain)
1609 chunkdata = gen.deltachunk(chain)
1610 if not chunkdata:
1610 if not chunkdata:
1611 break
1611 break
1612 node = chunkdata['node']
1612 node = chunkdata['node']
1613 p1 = chunkdata['p1']
1613 p1 = chunkdata['p1']
1614 p2 = chunkdata['p2']
1614 p2 = chunkdata['p2']
1615 cs = chunkdata['cs']
1615 cs = chunkdata['cs']
1616 deltabase = chunkdata['deltabase']
1616 deltabase = chunkdata['deltabase']
1617 delta = chunkdata['delta']
1617 delta = chunkdata['delta']
1618 ui.write("%s %s %s %s %s %s\n" %
1618 ui.write("%s %s %s %s %s %s\n" %
1619 (hex(node), hex(p1), hex(p2),
1619 (hex(node), hex(p1), hex(p2),
1620 hex(cs), hex(deltabase), len(delta)))
1620 hex(cs), hex(deltabase), len(delta)))
1621 chain = node
1621 chain = node
1622
1622
1623 chunkdata = gen.changelogheader()
1623 chunkdata = gen.changelogheader()
1624 showchunks("changelog")
1624 showchunks("changelog")
1625 chunkdata = gen.manifestheader()
1625 chunkdata = gen.manifestheader()
1626 showchunks("manifest")
1626 showchunks("manifest")
1627 while True:
1627 while True:
1628 chunkdata = gen.filelogheader()
1628 chunkdata = gen.filelogheader()
1629 if not chunkdata:
1629 if not chunkdata:
1630 break
1630 break
1631 fname = chunkdata['filename']
1631 fname = chunkdata['filename']
1632 showchunks(fname)
1632 showchunks(fname)
1633 else:
1633 else:
1634 chunkdata = gen.changelogheader()
1634 chunkdata = gen.changelogheader()
1635 chain = None
1635 chain = None
1636 while True:
1636 while True:
1637 chunkdata = gen.deltachunk(chain)
1637 chunkdata = gen.deltachunk(chain)
1638 if not chunkdata:
1638 if not chunkdata:
1639 break
1639 break
1640 node = chunkdata['node']
1640 node = chunkdata['node']
1641 ui.write("%s\n" % hex(node))
1641 ui.write("%s\n" % hex(node))
1642 chain = node
1642 chain = node
1643 finally:
1643 finally:
1644 f.close()
1644 f.close()
1645
1645
1646 @command('debugcheckstate', [], '')
1646 @command('debugcheckstate', [], '')
1647 def debugcheckstate(ui, repo):
1647 def debugcheckstate(ui, repo):
1648 """validate the correctness of the current dirstate"""
1648 """validate the correctness of the current dirstate"""
1649 parent1, parent2 = repo.dirstate.parents()
1649 parent1, parent2 = repo.dirstate.parents()
1650 m1 = repo[parent1].manifest()
1650 m1 = repo[parent1].manifest()
1651 m2 = repo[parent2].manifest()
1651 m2 = repo[parent2].manifest()
1652 errors = 0
1652 errors = 0
1653 for f in repo.dirstate:
1653 for f in repo.dirstate:
1654 state = repo.dirstate[f]
1654 state = repo.dirstate[f]
1655 if state in "nr" and f not in m1:
1655 if state in "nr" and f not in m1:
1656 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1656 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1657 errors += 1
1657 errors += 1
1658 if state in "a" and f in m1:
1658 if state in "a" and f in m1:
1659 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1659 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1660 errors += 1
1660 errors += 1
1661 if state in "m" and f not in m1 and f not in m2:
1661 if state in "m" and f not in m1 and f not in m2:
1662 ui.warn(_("%s in state %s, but not in either manifest\n") %
1662 ui.warn(_("%s in state %s, but not in either manifest\n") %
1663 (f, state))
1663 (f, state))
1664 errors += 1
1664 errors += 1
1665 for f in m1:
1665 for f in m1:
1666 state = repo.dirstate[f]
1666 state = repo.dirstate[f]
1667 if state not in "nrm":
1667 if state not in "nrm":
1668 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1668 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1669 errors += 1
1669 errors += 1
1670 if errors:
1670 if errors:
1671 error = _(".hg/dirstate inconsistent with current parent's manifest")
1671 error = _(".hg/dirstate inconsistent with current parent's manifest")
1672 raise util.Abort(error)
1672 raise util.Abort(error)
1673
1673
1674 @command('debugcommands', [], _('[COMMAND]'))
1674 @command('debugcommands', [], _('[COMMAND]'))
1675 def debugcommands(ui, cmd='', *args):
1675 def debugcommands(ui, cmd='', *args):
1676 """list all available commands and options"""
1676 """list all available commands and options"""
1677 for cmd, vals in sorted(table.iteritems()):
1677 for cmd, vals in sorted(table.iteritems()):
1678 cmd = cmd.split('|')[0].strip('^')
1678 cmd = cmd.split('|')[0].strip('^')
1679 opts = ', '.join([i[1] for i in vals[1]])
1679 opts = ', '.join([i[1] for i in vals[1]])
1680 ui.write('%s: %s\n' % (cmd, opts))
1680 ui.write('%s: %s\n' % (cmd, opts))
1681
1681
1682 @command('debugcomplete',
1682 @command('debugcomplete',
1683 [('o', 'options', None, _('show the command options'))],
1683 [('o', 'options', None, _('show the command options'))],
1684 _('[-o] CMD'))
1684 _('[-o] CMD'))
1685 def debugcomplete(ui, cmd='', **opts):
1685 def debugcomplete(ui, cmd='', **opts):
1686 """returns the completion list associated with the given command"""
1686 """returns the completion list associated with the given command"""
1687
1687
1688 if opts.get('options'):
1688 if opts.get('options'):
1689 options = []
1689 options = []
1690 otables = [globalopts]
1690 otables = [globalopts]
1691 if cmd:
1691 if cmd:
1692 aliases, entry = cmdutil.findcmd(cmd, table, False)
1692 aliases, entry = cmdutil.findcmd(cmd, table, False)
1693 otables.append(entry[1])
1693 otables.append(entry[1])
1694 for t in otables:
1694 for t in otables:
1695 for o in t:
1695 for o in t:
1696 if "(DEPRECATED)" in o[3]:
1696 if "(DEPRECATED)" in o[3]:
1697 continue
1697 continue
1698 if o[0]:
1698 if o[0]:
1699 options.append('-%s' % o[0])
1699 options.append('-%s' % o[0])
1700 options.append('--%s' % o[1])
1700 options.append('--%s' % o[1])
1701 ui.write("%s\n" % "\n".join(options))
1701 ui.write("%s\n" % "\n".join(options))
1702 return
1702 return
1703
1703
1704 cmdlist = cmdutil.findpossible(cmd, table)
1704 cmdlist = cmdutil.findpossible(cmd, table)
1705 if ui.verbose:
1705 if ui.verbose:
1706 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1706 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1707 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1707 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1708
1708
1709 @command('debugdag',
1709 @command('debugdag',
1710 [('t', 'tags', None, _('use tags as labels')),
1710 [('t', 'tags', None, _('use tags as labels')),
1711 ('b', 'branches', None, _('annotate with branch names')),
1711 ('b', 'branches', None, _('annotate with branch names')),
1712 ('', 'dots', None, _('use dots for runs')),
1712 ('', 'dots', None, _('use dots for runs')),
1713 ('s', 'spaces', None, _('separate elements by spaces'))],
1713 ('s', 'spaces', None, _('separate elements by spaces'))],
1714 _('[OPTION]... [FILE [REV]...]'))
1714 _('[OPTION]... [FILE [REV]...]'))
1715 def debugdag(ui, repo, file_=None, *revs, **opts):
1715 def debugdag(ui, repo, file_=None, *revs, **opts):
1716 """format the changelog or an index DAG as a concise textual description
1716 """format the changelog or an index DAG as a concise textual description
1717
1717
1718 If you pass a revlog index, the revlog's DAG is emitted. If you list
1718 If you pass a revlog index, the revlog's DAG is emitted. If you list
1719 revision numbers, they get labeled in the output as rN.
1719 revision numbers, they get labeled in the output as rN.
1720
1720
1721 Otherwise, the changelog DAG of the current repo is emitted.
1721 Otherwise, the changelog DAG of the current repo is emitted.
1722 """
1722 """
1723 spaces = opts.get('spaces')
1723 spaces = opts.get('spaces')
1724 dots = opts.get('dots')
1724 dots = opts.get('dots')
1725 if file_:
1725 if file_:
1726 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1726 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1727 revs = set((int(r) for r in revs))
1727 revs = set((int(r) for r in revs))
1728 def events():
1728 def events():
1729 for r in rlog:
1729 for r in rlog:
1730 yield 'n', (r, list(set(p for p in rlog.parentrevs(r)
1730 yield 'n', (r, list(set(p for p in rlog.parentrevs(r)
1731 if p != -1)))
1731 if p != -1)))
1732 if r in revs:
1732 if r in revs:
1733 yield 'l', (r, "r%i" % r)
1733 yield 'l', (r, "r%i" % r)
1734 elif repo:
1734 elif repo:
1735 cl = repo.changelog
1735 cl = repo.changelog
1736 tags = opts.get('tags')
1736 tags = opts.get('tags')
1737 branches = opts.get('branches')
1737 branches = opts.get('branches')
1738 if tags:
1738 if tags:
1739 labels = {}
1739 labels = {}
1740 for l, n in repo.tags().items():
1740 for l, n in repo.tags().items():
1741 labels.setdefault(cl.rev(n), []).append(l)
1741 labels.setdefault(cl.rev(n), []).append(l)
1742 def events():
1742 def events():
1743 b = "default"
1743 b = "default"
1744 for r in cl:
1744 for r in cl:
1745 if branches:
1745 if branches:
1746 newb = cl.read(cl.node(r))[5]['branch']
1746 newb = cl.read(cl.node(r))[5]['branch']
1747 if newb != b:
1747 if newb != b:
1748 yield 'a', newb
1748 yield 'a', newb
1749 b = newb
1749 b = newb
1750 yield 'n', (r, list(set(p for p in cl.parentrevs(r)
1750 yield 'n', (r, list(set(p for p in cl.parentrevs(r)
1751 if p != -1)))
1751 if p != -1)))
1752 if tags:
1752 if tags:
1753 ls = labels.get(r)
1753 ls = labels.get(r)
1754 if ls:
1754 if ls:
1755 for l in ls:
1755 for l in ls:
1756 yield 'l', (r, l)
1756 yield 'l', (r, l)
1757 else:
1757 else:
1758 raise util.Abort(_('need repo for changelog dag'))
1758 raise util.Abort(_('need repo for changelog dag'))
1759
1759
1760 for line in dagparser.dagtextlines(events(),
1760 for line in dagparser.dagtextlines(events(),
1761 addspaces=spaces,
1761 addspaces=spaces,
1762 wraplabels=True,
1762 wraplabels=True,
1763 wrapannotations=True,
1763 wrapannotations=True,
1764 wrapnonlinear=dots,
1764 wrapnonlinear=dots,
1765 usedots=dots,
1765 usedots=dots,
1766 maxlinewidth=70):
1766 maxlinewidth=70):
1767 ui.write(line)
1767 ui.write(line)
1768 ui.write("\n")
1768 ui.write("\n")
1769
1769
1770 @command('debugdata',
1770 @command('debugdata',
1771 [('c', 'changelog', False, _('open changelog')),
1771 [('c', 'changelog', False, _('open changelog')),
1772 ('m', 'manifest', False, _('open manifest'))],
1772 ('m', 'manifest', False, _('open manifest'))],
1773 _('-c|-m|FILE REV'))
1773 _('-c|-m|FILE REV'))
1774 def debugdata(ui, repo, file_, rev = None, **opts):
1774 def debugdata(ui, repo, file_, rev = None, **opts):
1775 """dump the contents of a data file revision"""
1775 """dump the contents of a data file revision"""
1776 if opts.get('changelog') or opts.get('manifest'):
1776 if opts.get('changelog') or opts.get('manifest'):
1777 file_, rev = None, file_
1777 file_, rev = None, file_
1778 elif rev is None:
1778 elif rev is None:
1779 raise error.CommandError('debugdata', _('invalid arguments'))
1779 raise error.CommandError('debugdata', _('invalid arguments'))
1780 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1780 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1781 try:
1781 try:
1782 ui.write(r.revision(r.lookup(rev)))
1782 ui.write(r.revision(r.lookup(rev)))
1783 except KeyError:
1783 except KeyError:
1784 raise util.Abort(_('invalid revision identifier %s') % rev)
1784 raise util.Abort(_('invalid revision identifier %s') % rev)
1785
1785
1786 @command('debugdate',
1786 @command('debugdate',
1787 [('e', 'extended', None, _('try extended date formats'))],
1787 [('e', 'extended', None, _('try extended date formats'))],
1788 _('[-e] DATE [RANGE]'))
1788 _('[-e] DATE [RANGE]'))
1789 def debugdate(ui, date, range=None, **opts):
1789 def debugdate(ui, date, range=None, **opts):
1790 """parse and display a date"""
1790 """parse and display a date"""
1791 if opts["extended"]:
1791 if opts["extended"]:
1792 d = util.parsedate(date, util.extendeddateformats)
1792 d = util.parsedate(date, util.extendeddateformats)
1793 else:
1793 else:
1794 d = util.parsedate(date)
1794 d = util.parsedate(date)
1795 ui.write(("internal: %s %s\n") % d)
1795 ui.write(("internal: %s %s\n") % d)
1796 ui.write(("standard: %s\n") % util.datestr(d))
1796 ui.write(("standard: %s\n") % util.datestr(d))
1797 if range:
1797 if range:
1798 m = util.matchdate(range)
1798 m = util.matchdate(range)
1799 ui.write(("match: %s\n") % m(d[0]))
1799 ui.write(("match: %s\n") % m(d[0]))
1800
1800
1801 @command('debugdiscovery',
1801 @command('debugdiscovery',
1802 [('', 'old', None, _('use old-style discovery')),
1802 [('', 'old', None, _('use old-style discovery')),
1803 ('', 'nonheads', None,
1803 ('', 'nonheads', None,
1804 _('use old-style discovery with non-heads included')),
1804 _('use old-style discovery with non-heads included')),
1805 ] + remoteopts,
1805 ] + remoteopts,
1806 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1806 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1807 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1807 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1808 """runs the changeset discovery protocol in isolation"""
1808 """runs the changeset discovery protocol in isolation"""
1809 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1809 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1810 opts.get('branch'))
1810 opts.get('branch'))
1811 remote = hg.peer(repo, opts, remoteurl)
1811 remote = hg.peer(repo, opts, remoteurl)
1812 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1812 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1813
1813
1814 # make sure tests are repeatable
1814 # make sure tests are repeatable
1815 random.seed(12323)
1815 random.seed(12323)
1816
1816
1817 def doit(localheads, remoteheads, remote=remote):
1817 def doit(localheads, remoteheads, remote=remote):
1818 if opts.get('old'):
1818 if opts.get('old'):
1819 if localheads:
1819 if localheads:
1820 raise util.Abort('cannot use localheads with old style '
1820 raise util.Abort('cannot use localheads with old style '
1821 'discovery')
1821 'discovery')
1822 if not util.safehasattr(remote, 'branches'):
1822 if not util.safehasattr(remote, 'branches'):
1823 # enable in-client legacy support
1823 # enable in-client legacy support
1824 remote = localrepo.locallegacypeer(remote.local())
1824 remote = localrepo.locallegacypeer(remote.local())
1825 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1825 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1826 force=True)
1826 force=True)
1827 common = set(common)
1827 common = set(common)
1828 if not opts.get('nonheads'):
1828 if not opts.get('nonheads'):
1829 ui.write(("unpruned common: %s\n") % " ".join([short(n)
1829 ui.write(("unpruned common: %s\n") % " ".join([short(n)
1830 for n in common]))
1830 for n in common]))
1831 dag = dagutil.revlogdag(repo.changelog)
1831 dag = dagutil.revlogdag(repo.changelog)
1832 all = dag.ancestorset(dag.internalizeall(common))
1832 all = dag.ancestorset(dag.internalizeall(common))
1833 common = dag.externalizeall(dag.headsetofconnecteds(all))
1833 common = dag.externalizeall(dag.headsetofconnecteds(all))
1834 else:
1834 else:
1835 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1835 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1836 common = set(common)
1836 common = set(common)
1837 rheads = set(hds)
1837 rheads = set(hds)
1838 lheads = set(repo.heads())
1838 lheads = set(repo.heads())
1839 ui.write(("common heads: %s\n") % " ".join([short(n) for n in common]))
1839 ui.write(("common heads: %s\n") % " ".join([short(n) for n in common]))
1840 if lheads <= common:
1840 if lheads <= common:
1841 ui.write(("local is subset\n"))
1841 ui.write(("local is subset\n"))
1842 elif rheads <= common:
1842 elif rheads <= common:
1843 ui.write(("remote is subset\n"))
1843 ui.write(("remote is subset\n"))
1844
1844
1845 serverlogs = opts.get('serverlog')
1845 serverlogs = opts.get('serverlog')
1846 if serverlogs:
1846 if serverlogs:
1847 for filename in serverlogs:
1847 for filename in serverlogs:
1848 logfile = open(filename, 'r')
1848 logfile = open(filename, 'r')
1849 try:
1849 try:
1850 line = logfile.readline()
1850 line = logfile.readline()
1851 while line:
1851 while line:
1852 parts = line.strip().split(';')
1852 parts = line.strip().split(';')
1853 op = parts[1]
1853 op = parts[1]
1854 if op == 'cg':
1854 if op == 'cg':
1855 pass
1855 pass
1856 elif op == 'cgss':
1856 elif op == 'cgss':
1857 doit(parts[2].split(' '), parts[3].split(' '))
1857 doit(parts[2].split(' '), parts[3].split(' '))
1858 elif op == 'unb':
1858 elif op == 'unb':
1859 doit(parts[3].split(' '), parts[2].split(' '))
1859 doit(parts[3].split(' '), parts[2].split(' '))
1860 line = logfile.readline()
1860 line = logfile.readline()
1861 finally:
1861 finally:
1862 logfile.close()
1862 logfile.close()
1863
1863
1864 else:
1864 else:
1865 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1865 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1866 opts.get('remote_head'))
1866 opts.get('remote_head'))
1867 localrevs = opts.get('local_head')
1867 localrevs = opts.get('local_head')
1868 doit(localrevs, remoterevs)
1868 doit(localrevs, remoterevs)
1869
1869
1870 @command('debugfileset',
1870 @command('debugfileset',
1871 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
1871 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
1872 _('[-r REV] FILESPEC'))
1872 _('[-r REV] FILESPEC'))
1873 def debugfileset(ui, repo, expr, **opts):
1873 def debugfileset(ui, repo, expr, **opts):
1874 '''parse and apply a fileset specification'''
1874 '''parse and apply a fileset specification'''
1875 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
1875 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
1876 if ui.verbose:
1876 if ui.verbose:
1877 tree = fileset.parse(expr)[0]
1877 tree = fileset.parse(expr)[0]
1878 ui.note(tree, "\n")
1878 ui.note(tree, "\n")
1879
1879
1880 for f in fileset.getfileset(ctx, expr):
1880 for f in fileset.getfileset(ctx, expr):
1881 ui.write("%s\n" % f)
1881 ui.write("%s\n" % f)
1882
1882
1883 @command('debugfsinfo', [], _('[PATH]'))
1883 @command('debugfsinfo', [], _('[PATH]'))
1884 def debugfsinfo(ui, path = "."):
1884 def debugfsinfo(ui, path = "."):
1885 """show information detected about current filesystem"""
1885 """show information detected about current filesystem"""
1886 util.writefile('.debugfsinfo', '')
1886 util.writefile('.debugfsinfo', '')
1887 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
1887 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
1888 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
1888 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
1889 ui.write(('case-sensitive: %s\n') % (util.checkcase('.debugfsinfo')
1889 ui.write(('case-sensitive: %s\n') % (util.checkcase('.debugfsinfo')
1890 and 'yes' or 'no'))
1890 and 'yes' or 'no'))
1891 os.unlink('.debugfsinfo')
1891 os.unlink('.debugfsinfo')
1892
1892
1893 @command('debuggetbundle',
1893 @command('debuggetbundle',
1894 [('H', 'head', [], _('id of head node'), _('ID')),
1894 [('H', 'head', [], _('id of head node'), _('ID')),
1895 ('C', 'common', [], _('id of common node'), _('ID')),
1895 ('C', 'common', [], _('id of common node'), _('ID')),
1896 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1896 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1897 _('REPO FILE [-H|-C ID]...'))
1897 _('REPO FILE [-H|-C ID]...'))
1898 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1898 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1899 """retrieves a bundle from a repo
1899 """retrieves a bundle from a repo
1900
1900
1901 Every ID must be a full-length hex node id string. Saves the bundle to the
1901 Every ID must be a full-length hex node id string. Saves the bundle to the
1902 given file.
1902 given file.
1903 """
1903 """
1904 repo = hg.peer(ui, opts, repopath)
1904 repo = hg.peer(ui, opts, repopath)
1905 if not repo.capable('getbundle'):
1905 if not repo.capable('getbundle'):
1906 raise util.Abort("getbundle() not supported by target repository")
1906 raise util.Abort("getbundle() not supported by target repository")
1907 args = {}
1907 args = {}
1908 if common:
1908 if common:
1909 args['common'] = [bin(s) for s in common]
1909 args['common'] = [bin(s) for s in common]
1910 if head:
1910 if head:
1911 args['heads'] = [bin(s) for s in head]
1911 args['heads'] = [bin(s) for s in head]
1912 bundle = repo.getbundle('debug', **args)
1912 bundle = repo.getbundle('debug', **args)
1913
1913
1914 bundletype = opts.get('type', 'bzip2').lower()
1914 bundletype = opts.get('type', 'bzip2').lower()
1915 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1915 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1916 bundletype = btypes.get(bundletype)
1916 bundletype = btypes.get(bundletype)
1917 if bundletype not in changegroup.bundletypes:
1917 if bundletype not in changegroup.bundletypes:
1918 raise util.Abort(_('unknown bundle type specified with --type'))
1918 raise util.Abort(_('unknown bundle type specified with --type'))
1919 changegroup.writebundle(bundle, bundlepath, bundletype)
1919 changegroup.writebundle(bundle, bundlepath, bundletype)
1920
1920
1921 @command('debugignore', [], '')
1921 @command('debugignore', [], '')
1922 def debugignore(ui, repo, *values, **opts):
1922 def debugignore(ui, repo, *values, **opts):
1923 """display the combined ignore pattern"""
1923 """display the combined ignore pattern"""
1924 ignore = repo.dirstate._ignore
1924 ignore = repo.dirstate._ignore
1925 includepat = getattr(ignore, 'includepat', None)
1925 includepat = getattr(ignore, 'includepat', None)
1926 if includepat is not None:
1926 if includepat is not None:
1927 ui.write("%s\n" % includepat)
1927 ui.write("%s\n" % includepat)
1928 else:
1928 else:
1929 raise util.Abort(_("no ignore patterns found"))
1929 raise util.Abort(_("no ignore patterns found"))
1930
1930
1931 @command('debugindex',
1931 @command('debugindex',
1932 [('c', 'changelog', False, _('open changelog')),
1932 [('c', 'changelog', False, _('open changelog')),
1933 ('m', 'manifest', False, _('open manifest')),
1933 ('m', 'manifest', False, _('open manifest')),
1934 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1934 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1935 _('[-f FORMAT] -c|-m|FILE'))
1935 _('[-f FORMAT] -c|-m|FILE'))
1936 def debugindex(ui, repo, file_ = None, **opts):
1936 def debugindex(ui, repo, file_ = None, **opts):
1937 """dump the contents of an index file"""
1937 """dump the contents of an index file"""
1938 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1938 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1939 format = opts.get('format', 0)
1939 format = opts.get('format', 0)
1940 if format not in (0, 1):
1940 if format not in (0, 1):
1941 raise util.Abort(_("unknown format %d") % format)
1941 raise util.Abort(_("unknown format %d") % format)
1942
1942
1943 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1943 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1944 if generaldelta:
1944 if generaldelta:
1945 basehdr = ' delta'
1945 basehdr = ' delta'
1946 else:
1946 else:
1947 basehdr = ' base'
1947 basehdr = ' base'
1948
1948
1949 if format == 0:
1949 if format == 0:
1950 ui.write(" rev offset length " + basehdr + " linkrev"
1950 ui.write(" rev offset length " + basehdr + " linkrev"
1951 " nodeid p1 p2\n")
1951 " nodeid p1 p2\n")
1952 elif format == 1:
1952 elif format == 1:
1953 ui.write(" rev flag offset length"
1953 ui.write(" rev flag offset length"
1954 " size " + basehdr + " link p1 p2"
1954 " size " + basehdr + " link p1 p2"
1955 " nodeid\n")
1955 " nodeid\n")
1956
1956
1957 for i in r:
1957 for i in r:
1958 node = r.node(i)
1958 node = r.node(i)
1959 if generaldelta:
1959 if generaldelta:
1960 base = r.deltaparent(i)
1960 base = r.deltaparent(i)
1961 else:
1961 else:
1962 base = r.chainbase(i)
1962 base = r.chainbase(i)
1963 if format == 0:
1963 if format == 0:
1964 try:
1964 try:
1965 pp = r.parents(node)
1965 pp = r.parents(node)
1966 except Exception:
1966 except Exception:
1967 pp = [nullid, nullid]
1967 pp = [nullid, nullid]
1968 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1968 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1969 i, r.start(i), r.length(i), base, r.linkrev(i),
1969 i, r.start(i), r.length(i), base, r.linkrev(i),
1970 short(node), short(pp[0]), short(pp[1])))
1970 short(node), short(pp[0]), short(pp[1])))
1971 elif format == 1:
1971 elif format == 1:
1972 pr = r.parentrevs(i)
1972 pr = r.parentrevs(i)
1973 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1973 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1974 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1974 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1975 base, r.linkrev(i), pr[0], pr[1], short(node)))
1975 base, r.linkrev(i), pr[0], pr[1], short(node)))
1976
1976
1977 @command('debugindexdot', [], _('FILE'))
1977 @command('debugindexdot', [], _('FILE'))
1978 def debugindexdot(ui, repo, file_):
1978 def debugindexdot(ui, repo, file_):
1979 """dump an index DAG as a graphviz dot file"""
1979 """dump an index DAG as a graphviz dot file"""
1980 r = None
1980 r = None
1981 if repo:
1981 if repo:
1982 filelog = repo.file(file_)
1982 filelog = repo.file(file_)
1983 if len(filelog):
1983 if len(filelog):
1984 r = filelog
1984 r = filelog
1985 if not r:
1985 if not r:
1986 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1986 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1987 ui.write(("digraph G {\n"))
1987 ui.write(("digraph G {\n"))
1988 for i in r:
1988 for i in r:
1989 node = r.node(i)
1989 node = r.node(i)
1990 pp = r.parents(node)
1990 pp = r.parents(node)
1991 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1991 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1992 if pp[1] != nullid:
1992 if pp[1] != nullid:
1993 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1993 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1994 ui.write("}\n")
1994 ui.write("}\n")
1995
1995
1996 @command('debuginstall', [], '')
1996 @command('debuginstall', [], '')
1997 def debuginstall(ui):
1997 def debuginstall(ui):
1998 '''test Mercurial installation
1998 '''test Mercurial installation
1999
1999
2000 Returns 0 on success.
2000 Returns 0 on success.
2001 '''
2001 '''
2002
2002
2003 def writetemp(contents):
2003 def writetemp(contents):
2004 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2004 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2005 f = os.fdopen(fd, "wb")
2005 f = os.fdopen(fd, "wb")
2006 f.write(contents)
2006 f.write(contents)
2007 f.close()
2007 f.close()
2008 return name
2008 return name
2009
2009
2010 problems = 0
2010 problems = 0
2011
2011
2012 # encoding
2012 # encoding
2013 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
2013 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
2014 try:
2014 try:
2015 encoding.fromlocal("test")
2015 encoding.fromlocal("test")
2016 except util.Abort, inst:
2016 except util.Abort, inst:
2017 ui.write(" %s\n" % inst)
2017 ui.write(" %s\n" % inst)
2018 ui.write(_(" (check that your locale is properly set)\n"))
2018 ui.write(_(" (check that your locale is properly set)\n"))
2019 problems += 1
2019 problems += 1
2020
2020
2021 # Python lib
2021 # Python lib
2022 ui.status(_("checking Python lib (%s)...\n")
2022 ui.status(_("checking Python lib (%s)...\n")
2023 % os.path.dirname(os.__file__))
2023 % os.path.dirname(os.__file__))
2024
2024
2025 # compiled modules
2025 # compiled modules
2026 ui.status(_("checking installed modules (%s)...\n")
2026 ui.status(_("checking installed modules (%s)...\n")
2027 % os.path.dirname(__file__))
2027 % os.path.dirname(__file__))
2028 try:
2028 try:
2029 import bdiff, mpatch, base85, osutil
2029 import bdiff, mpatch, base85, osutil
2030 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2030 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2031 except Exception, inst:
2031 except Exception, inst:
2032 ui.write(" %s\n" % inst)
2032 ui.write(" %s\n" % inst)
2033 ui.write(_(" One or more extensions could not be found"))
2033 ui.write(_(" One or more extensions could not be found"))
2034 ui.write(_(" (check that you compiled the extensions)\n"))
2034 ui.write(_(" (check that you compiled the extensions)\n"))
2035 problems += 1
2035 problems += 1
2036
2036
2037 # templates
2037 # templates
2038 import templater
2038 import templater
2039 p = templater.templatepath()
2039 p = templater.templatepath()
2040 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2040 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2041 try:
2041 try:
2042 templater.templater(templater.templatepath("map-cmdline.default"))
2042 templater.templater(templater.templatepath("map-cmdline.default"))
2043 except Exception, inst:
2043 except Exception, inst:
2044 ui.write(" %s\n" % inst)
2044 ui.write(" %s\n" % inst)
2045 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2045 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2046 problems += 1
2046 problems += 1
2047
2047
2048 # editor
2048 # editor
2049 ui.status(_("checking commit editor...\n"))
2049 ui.status(_("checking commit editor...\n"))
2050 editor = ui.geteditor()
2050 editor = ui.geteditor()
2051 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
2051 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
2052 if not cmdpath:
2052 if not cmdpath:
2053 if editor == 'vi':
2053 if editor == 'vi':
2054 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2054 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2055 ui.write(_(" (specify a commit editor in your configuration"
2055 ui.write(_(" (specify a commit editor in your configuration"
2056 " file)\n"))
2056 " file)\n"))
2057 else:
2057 else:
2058 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2058 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2059 ui.write(_(" (specify a commit editor in your configuration"
2059 ui.write(_(" (specify a commit editor in your configuration"
2060 " file)\n"))
2060 " file)\n"))
2061 problems += 1
2061 problems += 1
2062
2062
2063 # check username
2063 # check username
2064 ui.status(_("checking username...\n"))
2064 ui.status(_("checking username...\n"))
2065 try:
2065 try:
2066 ui.username()
2066 ui.username()
2067 except util.Abort, e:
2067 except util.Abort, e:
2068 ui.write(" %s\n" % e)
2068 ui.write(" %s\n" % e)
2069 ui.write(_(" (specify a username in your configuration file)\n"))
2069 ui.write(_(" (specify a username in your configuration file)\n"))
2070 problems += 1
2070 problems += 1
2071
2071
2072 if not problems:
2072 if not problems:
2073 ui.status(_("no problems detected\n"))
2073 ui.status(_("no problems detected\n"))
2074 else:
2074 else:
2075 ui.write(_("%s problems detected,"
2075 ui.write(_("%s problems detected,"
2076 " please check your install!\n") % problems)
2076 " please check your install!\n") % problems)
2077
2077
2078 return problems
2078 return problems
2079
2079
2080 @command('debugknown', [], _('REPO ID...'))
2080 @command('debugknown', [], _('REPO ID...'))
2081 def debugknown(ui, repopath, *ids, **opts):
2081 def debugknown(ui, repopath, *ids, **opts):
2082 """test whether node ids are known to a repo
2082 """test whether node ids are known to a repo
2083
2083
2084 Every ID must be a full-length hex node id string. Returns a list of 0s
2084 Every ID must be a full-length hex node id string. Returns a list of 0s
2085 and 1s indicating unknown/known.
2085 and 1s indicating unknown/known.
2086 """
2086 """
2087 repo = hg.peer(ui, opts, repopath)
2087 repo = hg.peer(ui, opts, repopath)
2088 if not repo.capable('known'):
2088 if not repo.capable('known'):
2089 raise util.Abort("known() not supported by target repository")
2089 raise util.Abort("known() not supported by target repository")
2090 flags = repo.known([bin(s) for s in ids])
2090 flags = repo.known([bin(s) for s in ids])
2091 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2091 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2092
2092
2093 @command('debugobsolete',
2093 @command('debugobsolete',
2094 [('', 'flags', 0, _('markers flag')),
2094 [('', 'flags', 0, _('markers flag')),
2095 ] + commitopts2,
2095 ] + commitopts2,
2096 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2096 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2097 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2097 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2098 """create arbitrary obsolete marker"""
2098 """create arbitrary obsolete marker"""
2099 def parsenodeid(s):
2099 def parsenodeid(s):
2100 try:
2100 try:
2101 # We do not use revsingle/revrange functions here to accept
2101 # We do not use revsingle/revrange functions here to accept
2102 # arbitrary node identifiers, possibly not present in the
2102 # arbitrary node identifiers, possibly not present in the
2103 # local repository.
2103 # local repository.
2104 n = bin(s)
2104 n = bin(s)
2105 if len(n) != len(nullid):
2105 if len(n) != len(nullid):
2106 raise TypeError()
2106 raise TypeError()
2107 return n
2107 return n
2108 except TypeError:
2108 except TypeError:
2109 raise util.Abort('changeset references must be full hexadecimal '
2109 raise util.Abort('changeset references must be full hexadecimal '
2110 'node identifiers')
2110 'node identifiers')
2111
2111
2112 if precursor is not None:
2112 if precursor is not None:
2113 metadata = {}
2113 metadata = {}
2114 if 'date' in opts:
2114 if 'date' in opts:
2115 metadata['date'] = opts['date']
2115 metadata['date'] = opts['date']
2116 metadata['user'] = opts['user'] or ui.username()
2116 metadata['user'] = opts['user'] or ui.username()
2117 succs = tuple(parsenodeid(succ) for succ in successors)
2117 succs = tuple(parsenodeid(succ) for succ in successors)
2118 l = repo.lock()
2118 l = repo.lock()
2119 try:
2119 try:
2120 tr = repo.transaction('debugobsolete')
2120 tr = repo.transaction('debugobsolete')
2121 try:
2121 try:
2122 repo.obsstore.create(tr, parsenodeid(precursor), succs,
2122 repo.obsstore.create(tr, parsenodeid(precursor), succs,
2123 opts['flags'], metadata)
2123 opts['flags'], metadata)
2124 tr.close()
2124 tr.close()
2125 finally:
2125 finally:
2126 tr.release()
2126 tr.release()
2127 finally:
2127 finally:
2128 l.release()
2128 l.release()
2129 else:
2129 else:
2130 for m in obsolete.allmarkers(repo):
2130 for m in obsolete.allmarkers(repo):
2131 ui.write(hex(m.precnode()))
2131 ui.write(hex(m.precnode()))
2132 for repl in m.succnodes():
2132 for repl in m.succnodes():
2133 ui.write(' ')
2133 ui.write(' ')
2134 ui.write(hex(repl))
2134 ui.write(hex(repl))
2135 ui.write(' %X ' % m._data[2])
2135 ui.write(' %X ' % m._data[2])
2136 ui.write(m.metadata())
2136 ui.write(m.metadata())
2137 ui.write('\n')
2137 ui.write('\n')
2138
2138
2139 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2139 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2140 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2140 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2141 '''access the pushkey key/value protocol
2141 '''access the pushkey key/value protocol
2142
2142
2143 With two args, list the keys in the given namespace.
2143 With two args, list the keys in the given namespace.
2144
2144
2145 With five args, set a key to new if it currently is set to old.
2145 With five args, set a key to new if it currently is set to old.
2146 Reports success or failure.
2146 Reports success or failure.
2147 '''
2147 '''
2148
2148
2149 target = hg.peer(ui, {}, repopath)
2149 target = hg.peer(ui, {}, repopath)
2150 if keyinfo:
2150 if keyinfo:
2151 key, old, new = keyinfo
2151 key, old, new = keyinfo
2152 r = target.pushkey(namespace, key, old, new)
2152 r = target.pushkey(namespace, key, old, new)
2153 ui.status(str(r) + '\n')
2153 ui.status(str(r) + '\n')
2154 return not r
2154 return not r
2155 else:
2155 else:
2156 for k, v in target.listkeys(namespace).iteritems():
2156 for k, v in target.listkeys(namespace).iteritems():
2157 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2157 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2158 v.encode('string-escape')))
2158 v.encode('string-escape')))
2159
2159
2160 @command('debugpvec', [], _('A B'))
2160 @command('debugpvec', [], _('A B'))
2161 def debugpvec(ui, repo, a, b=None):
2161 def debugpvec(ui, repo, a, b=None):
2162 ca = scmutil.revsingle(repo, a)
2162 ca = scmutil.revsingle(repo, a)
2163 cb = scmutil.revsingle(repo, b)
2163 cb = scmutil.revsingle(repo, b)
2164 pa = pvec.ctxpvec(ca)
2164 pa = pvec.ctxpvec(ca)
2165 pb = pvec.ctxpvec(cb)
2165 pb = pvec.ctxpvec(cb)
2166 if pa == pb:
2166 if pa == pb:
2167 rel = "="
2167 rel = "="
2168 elif pa > pb:
2168 elif pa > pb:
2169 rel = ">"
2169 rel = ">"
2170 elif pa < pb:
2170 elif pa < pb:
2171 rel = "<"
2171 rel = "<"
2172 elif pa | pb:
2172 elif pa | pb:
2173 rel = "|"
2173 rel = "|"
2174 ui.write(_("a: %s\n") % pa)
2174 ui.write(_("a: %s\n") % pa)
2175 ui.write(_("b: %s\n") % pb)
2175 ui.write(_("b: %s\n") % pb)
2176 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2176 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2177 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2177 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2178 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2178 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2179 pa.distance(pb), rel))
2179 pa.distance(pb), rel))
2180
2180
2181 @command('debugrebuildstate',
2181 @command('debugrebuildstate',
2182 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2182 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2183 _('[-r REV] [REV]'))
2183 _('[-r REV] [REV]'))
2184 def debugrebuildstate(ui, repo, rev="tip"):
2184 def debugrebuildstate(ui, repo, rev="tip"):
2185 """rebuild the dirstate as it would look like for the given revision"""
2185 """rebuild the dirstate as it would look like for the given revision"""
2186 ctx = scmutil.revsingle(repo, rev)
2186 ctx = scmutil.revsingle(repo, rev)
2187 wlock = repo.wlock()
2187 wlock = repo.wlock()
2188 try:
2188 try:
2189 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2189 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2190 finally:
2190 finally:
2191 wlock.release()
2191 wlock.release()
2192
2192
2193 @command('debugrename',
2193 @command('debugrename',
2194 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2194 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2195 _('[-r REV] FILE'))
2195 _('[-r REV] FILE'))
2196 def debugrename(ui, repo, file1, *pats, **opts):
2196 def debugrename(ui, repo, file1, *pats, **opts):
2197 """dump rename information"""
2197 """dump rename information"""
2198
2198
2199 ctx = scmutil.revsingle(repo, opts.get('rev'))
2199 ctx = scmutil.revsingle(repo, opts.get('rev'))
2200 m = scmutil.match(ctx, (file1,) + pats, opts)
2200 m = scmutil.match(ctx, (file1,) + pats, opts)
2201 for abs in ctx.walk(m):
2201 for abs in ctx.walk(m):
2202 fctx = ctx[abs]
2202 fctx = ctx[abs]
2203 o = fctx.filelog().renamed(fctx.filenode())
2203 o = fctx.filelog().renamed(fctx.filenode())
2204 rel = m.rel(abs)
2204 rel = m.rel(abs)
2205 if o:
2205 if o:
2206 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2206 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2207 else:
2207 else:
2208 ui.write(_("%s not renamed\n") % rel)
2208 ui.write(_("%s not renamed\n") % rel)
2209
2209
2210 @command('debugrevlog',
2210 @command('debugrevlog',
2211 [('c', 'changelog', False, _('open changelog')),
2211 [('c', 'changelog', False, _('open changelog')),
2212 ('m', 'manifest', False, _('open manifest')),
2212 ('m', 'manifest', False, _('open manifest')),
2213 ('d', 'dump', False, _('dump index data'))],
2213 ('d', 'dump', False, _('dump index data'))],
2214 _('-c|-m|FILE'))
2214 _('-c|-m|FILE'))
2215 def debugrevlog(ui, repo, file_ = None, **opts):
2215 def debugrevlog(ui, repo, file_ = None, **opts):
2216 """show data and statistics about a revlog"""
2216 """show data and statistics about a revlog"""
2217 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2217 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2218
2218
2219 if opts.get("dump"):
2219 if opts.get("dump"):
2220 numrevs = len(r)
2220 numrevs = len(r)
2221 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2221 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2222 " rawsize totalsize compression heads\n")
2222 " rawsize totalsize compression heads\n")
2223 ts = 0
2223 ts = 0
2224 heads = set()
2224 heads = set()
2225 for rev in xrange(numrevs):
2225 for rev in xrange(numrevs):
2226 dbase = r.deltaparent(rev)
2226 dbase = r.deltaparent(rev)
2227 if dbase == -1:
2227 if dbase == -1:
2228 dbase = rev
2228 dbase = rev
2229 cbase = r.chainbase(rev)
2229 cbase = r.chainbase(rev)
2230 p1, p2 = r.parentrevs(rev)
2230 p1, p2 = r.parentrevs(rev)
2231 rs = r.rawsize(rev)
2231 rs = r.rawsize(rev)
2232 ts = ts + rs
2232 ts = ts + rs
2233 heads -= set(r.parentrevs(rev))
2233 heads -= set(r.parentrevs(rev))
2234 heads.add(rev)
2234 heads.add(rev)
2235 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2235 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2236 (rev, p1, p2, r.start(rev), r.end(rev),
2236 (rev, p1, p2, r.start(rev), r.end(rev),
2237 r.start(dbase), r.start(cbase),
2237 r.start(dbase), r.start(cbase),
2238 r.start(p1), r.start(p2),
2238 r.start(p1), r.start(p2),
2239 rs, ts, ts / r.end(rev), len(heads)))
2239 rs, ts, ts / r.end(rev), len(heads)))
2240 return 0
2240 return 0
2241
2241
2242 v = r.version
2242 v = r.version
2243 format = v & 0xFFFF
2243 format = v & 0xFFFF
2244 flags = []
2244 flags = []
2245 gdelta = False
2245 gdelta = False
2246 if v & revlog.REVLOGNGINLINEDATA:
2246 if v & revlog.REVLOGNGINLINEDATA:
2247 flags.append('inline')
2247 flags.append('inline')
2248 if v & revlog.REVLOGGENERALDELTA:
2248 if v & revlog.REVLOGGENERALDELTA:
2249 gdelta = True
2249 gdelta = True
2250 flags.append('generaldelta')
2250 flags.append('generaldelta')
2251 if not flags:
2251 if not flags:
2252 flags = ['(none)']
2252 flags = ['(none)']
2253
2253
2254 nummerges = 0
2254 nummerges = 0
2255 numfull = 0
2255 numfull = 0
2256 numprev = 0
2256 numprev = 0
2257 nump1 = 0
2257 nump1 = 0
2258 nump2 = 0
2258 nump2 = 0
2259 numother = 0
2259 numother = 0
2260 nump1prev = 0
2260 nump1prev = 0
2261 nump2prev = 0
2261 nump2prev = 0
2262 chainlengths = []
2262 chainlengths = []
2263
2263
2264 datasize = [None, 0, 0L]
2264 datasize = [None, 0, 0L]
2265 fullsize = [None, 0, 0L]
2265 fullsize = [None, 0, 0L]
2266 deltasize = [None, 0, 0L]
2266 deltasize = [None, 0, 0L]
2267
2267
2268 def addsize(size, l):
2268 def addsize(size, l):
2269 if l[0] is None or size < l[0]:
2269 if l[0] is None or size < l[0]:
2270 l[0] = size
2270 l[0] = size
2271 if size > l[1]:
2271 if size > l[1]:
2272 l[1] = size
2272 l[1] = size
2273 l[2] += size
2273 l[2] += size
2274
2274
2275 numrevs = len(r)
2275 numrevs = len(r)
2276 for rev in xrange(numrevs):
2276 for rev in xrange(numrevs):
2277 p1, p2 = r.parentrevs(rev)
2277 p1, p2 = r.parentrevs(rev)
2278 delta = r.deltaparent(rev)
2278 delta = r.deltaparent(rev)
2279 if format > 0:
2279 if format > 0:
2280 addsize(r.rawsize(rev), datasize)
2280 addsize(r.rawsize(rev), datasize)
2281 if p2 != nullrev:
2281 if p2 != nullrev:
2282 nummerges += 1
2282 nummerges += 1
2283 size = r.length(rev)
2283 size = r.length(rev)
2284 if delta == nullrev:
2284 if delta == nullrev:
2285 chainlengths.append(0)
2285 chainlengths.append(0)
2286 numfull += 1
2286 numfull += 1
2287 addsize(size, fullsize)
2287 addsize(size, fullsize)
2288 else:
2288 else:
2289 chainlengths.append(chainlengths[delta] + 1)
2289 chainlengths.append(chainlengths[delta] + 1)
2290 addsize(size, deltasize)
2290 addsize(size, deltasize)
2291 if delta == rev - 1:
2291 if delta == rev - 1:
2292 numprev += 1
2292 numprev += 1
2293 if delta == p1:
2293 if delta == p1:
2294 nump1prev += 1
2294 nump1prev += 1
2295 elif delta == p2:
2295 elif delta == p2:
2296 nump2prev += 1
2296 nump2prev += 1
2297 elif delta == p1:
2297 elif delta == p1:
2298 nump1 += 1
2298 nump1 += 1
2299 elif delta == p2:
2299 elif delta == p2:
2300 nump2 += 1
2300 nump2 += 1
2301 elif delta != nullrev:
2301 elif delta != nullrev:
2302 numother += 1
2302 numother += 1
2303
2303
2304 # Adjust size min value for empty cases
2304 # Adjust size min value for empty cases
2305 for size in (datasize, fullsize, deltasize):
2305 for size in (datasize, fullsize, deltasize):
2306 if size[0] is None:
2306 if size[0] is None:
2307 size[0] = 0
2307 size[0] = 0
2308
2308
2309 numdeltas = numrevs - numfull
2309 numdeltas = numrevs - numfull
2310 numoprev = numprev - nump1prev - nump2prev
2310 numoprev = numprev - nump1prev - nump2prev
2311 totalrawsize = datasize[2]
2311 totalrawsize = datasize[2]
2312 datasize[2] /= numrevs
2312 datasize[2] /= numrevs
2313 fulltotal = fullsize[2]
2313 fulltotal = fullsize[2]
2314 fullsize[2] /= numfull
2314 fullsize[2] /= numfull
2315 deltatotal = deltasize[2]
2315 deltatotal = deltasize[2]
2316 if numrevs - numfull > 0:
2316 if numrevs - numfull > 0:
2317 deltasize[2] /= numrevs - numfull
2317 deltasize[2] /= numrevs - numfull
2318 totalsize = fulltotal + deltatotal
2318 totalsize = fulltotal + deltatotal
2319 avgchainlen = sum(chainlengths) / numrevs
2319 avgchainlen = sum(chainlengths) / numrevs
2320 compratio = totalrawsize / totalsize
2320 compratio = totalrawsize / totalsize
2321
2321
2322 basedfmtstr = '%%%dd\n'
2322 basedfmtstr = '%%%dd\n'
2323 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2323 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2324
2324
2325 def dfmtstr(max):
2325 def dfmtstr(max):
2326 return basedfmtstr % len(str(max))
2326 return basedfmtstr % len(str(max))
2327 def pcfmtstr(max, padding=0):
2327 def pcfmtstr(max, padding=0):
2328 return basepcfmtstr % (len(str(max)), ' ' * padding)
2328 return basepcfmtstr % (len(str(max)), ' ' * padding)
2329
2329
2330 def pcfmt(value, total):
2330 def pcfmt(value, total):
2331 return (value, 100 * float(value) / total)
2331 return (value, 100 * float(value) / total)
2332
2332
2333 ui.write(('format : %d\n') % format)
2333 ui.write(('format : %d\n') % format)
2334 ui.write(('flags : %s\n') % ', '.join(flags))
2334 ui.write(('flags : %s\n') % ', '.join(flags))
2335
2335
2336 ui.write('\n')
2336 ui.write('\n')
2337 fmt = pcfmtstr(totalsize)
2337 fmt = pcfmtstr(totalsize)
2338 fmt2 = dfmtstr(totalsize)
2338 fmt2 = dfmtstr(totalsize)
2339 ui.write(('revisions : ') + fmt2 % numrevs)
2339 ui.write(('revisions : ') + fmt2 % numrevs)
2340 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2340 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2341 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2341 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2342 ui.write(('revisions : ') + fmt2 % numrevs)
2342 ui.write(('revisions : ') + fmt2 % numrevs)
2343 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2343 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2344 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2344 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2345 ui.write(('revision size : ') + fmt2 % totalsize)
2345 ui.write(('revision size : ') + fmt2 % totalsize)
2346 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2346 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2347 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2347 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2348
2348
2349 ui.write('\n')
2349 ui.write('\n')
2350 fmt = dfmtstr(max(avgchainlen, compratio))
2350 fmt = dfmtstr(max(avgchainlen, compratio))
2351 ui.write(('avg chain length : ') + fmt % avgchainlen)
2351 ui.write(('avg chain length : ') + fmt % avgchainlen)
2352 ui.write(('compression ratio : ') + fmt % compratio)
2352 ui.write(('compression ratio : ') + fmt % compratio)
2353
2353
2354 if format > 0:
2354 if format > 0:
2355 ui.write('\n')
2355 ui.write('\n')
2356 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2356 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2357 % tuple(datasize))
2357 % tuple(datasize))
2358 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2358 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2359 % tuple(fullsize))
2359 % tuple(fullsize))
2360 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2360 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2361 % tuple(deltasize))
2361 % tuple(deltasize))
2362
2362
2363 if numdeltas > 0:
2363 if numdeltas > 0:
2364 ui.write('\n')
2364 ui.write('\n')
2365 fmt = pcfmtstr(numdeltas)
2365 fmt = pcfmtstr(numdeltas)
2366 fmt2 = pcfmtstr(numdeltas, 4)
2366 fmt2 = pcfmtstr(numdeltas, 4)
2367 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2367 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2368 if numprev > 0:
2368 if numprev > 0:
2369 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2369 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2370 numprev))
2370 numprev))
2371 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2371 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2372 numprev))
2372 numprev))
2373 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2373 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2374 numprev))
2374 numprev))
2375 if gdelta:
2375 if gdelta:
2376 ui.write(('deltas against p1 : ')
2376 ui.write(('deltas against p1 : ')
2377 + fmt % pcfmt(nump1, numdeltas))
2377 + fmt % pcfmt(nump1, numdeltas))
2378 ui.write(('deltas against p2 : ')
2378 ui.write(('deltas against p2 : ')
2379 + fmt % pcfmt(nump2, numdeltas))
2379 + fmt % pcfmt(nump2, numdeltas))
2380 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2380 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2381 numdeltas))
2381 numdeltas))
2382
2382
2383 @command('debugrevspec', [], ('REVSPEC'))
2383 @command('debugrevspec', [], ('REVSPEC'))
2384 def debugrevspec(ui, repo, expr):
2384 def debugrevspec(ui, repo, expr):
2385 """parse and apply a revision specification
2385 """parse and apply a revision specification
2386
2386
2387 Use --verbose to print the parsed tree before and after aliases
2387 Use --verbose to print the parsed tree before and after aliases
2388 expansion.
2388 expansion.
2389 """
2389 """
2390 if ui.verbose:
2390 if ui.verbose:
2391 tree = revset.parse(expr)[0]
2391 tree = revset.parse(expr)[0]
2392 ui.note(revset.prettyformat(tree), "\n")
2392 ui.note(revset.prettyformat(tree), "\n")
2393 newtree = revset.findaliases(ui, tree)
2393 newtree = revset.findaliases(ui, tree)
2394 if newtree != tree:
2394 if newtree != tree:
2395 ui.note(revset.prettyformat(newtree), "\n")
2395 ui.note(revset.prettyformat(newtree), "\n")
2396 func = revset.match(ui, expr)
2396 func = revset.match(ui, expr)
2397 for c in func(repo, range(len(repo))):
2397 for c in func(repo, range(len(repo))):
2398 ui.write("%s\n" % c)
2398 ui.write("%s\n" % c)
2399
2399
2400 @command('debugsetparents', [], _('REV1 [REV2]'))
2400 @command('debugsetparents', [], _('REV1 [REV2]'))
2401 def debugsetparents(ui, repo, rev1, rev2=None):
2401 def debugsetparents(ui, repo, rev1, rev2=None):
2402 """manually set the parents of the current working directory
2402 """manually set the parents of the current working directory
2403
2403
2404 This is useful for writing repository conversion tools, but should
2404 This is useful for writing repository conversion tools, but should
2405 be used with care.
2405 be used with care.
2406
2406
2407 Returns 0 on success.
2407 Returns 0 on success.
2408 """
2408 """
2409
2409
2410 r1 = scmutil.revsingle(repo, rev1).node()
2410 r1 = scmutil.revsingle(repo, rev1).node()
2411 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2411 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2412
2412
2413 wlock = repo.wlock()
2413 wlock = repo.wlock()
2414 try:
2414 try:
2415 repo.setparents(r1, r2)
2415 repo.setparents(r1, r2)
2416 finally:
2416 finally:
2417 wlock.release()
2417 wlock.release()
2418
2418
2419 @command('debugstate',
2419 @command('debugstate',
2420 [('', 'nodates', None, _('do not display the saved mtime')),
2420 [('', 'nodates', None, _('do not display the saved mtime')),
2421 ('', 'datesort', None, _('sort by saved mtime'))],
2421 ('', 'datesort', None, _('sort by saved mtime'))],
2422 _('[OPTION]...'))
2422 _('[OPTION]...'))
2423 def debugstate(ui, repo, nodates=None, datesort=None):
2423 def debugstate(ui, repo, nodates=None, datesort=None):
2424 """show the contents of the current dirstate"""
2424 """show the contents of the current dirstate"""
2425 timestr = ""
2425 timestr = ""
2426 showdate = not nodates
2426 showdate = not nodates
2427 if datesort:
2427 if datesort:
2428 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2428 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2429 else:
2429 else:
2430 keyfunc = None # sort by filename
2430 keyfunc = None # sort by filename
2431 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2431 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2432 if showdate:
2432 if showdate:
2433 if ent[3] == -1:
2433 if ent[3] == -1:
2434 # Pad or slice to locale representation
2434 # Pad or slice to locale representation
2435 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2435 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2436 time.localtime(0)))
2436 time.localtime(0)))
2437 timestr = 'unset'
2437 timestr = 'unset'
2438 timestr = (timestr[:locale_len] +
2438 timestr = (timestr[:locale_len] +
2439 ' ' * (locale_len - len(timestr)))
2439 ' ' * (locale_len - len(timestr)))
2440 else:
2440 else:
2441 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2441 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2442 time.localtime(ent[3]))
2442 time.localtime(ent[3]))
2443 if ent[1] & 020000:
2443 if ent[1] & 020000:
2444 mode = 'lnk'
2444 mode = 'lnk'
2445 else:
2445 else:
2446 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2446 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2447 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2447 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2448 for f in repo.dirstate.copies():
2448 for f in repo.dirstate.copies():
2449 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2449 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2450
2450
2451 @command('debugsub',
2451 @command('debugsub',
2452 [('r', 'rev', '',
2452 [('r', 'rev', '',
2453 _('revision to check'), _('REV'))],
2453 _('revision to check'), _('REV'))],
2454 _('[-r REV] [REV]'))
2454 _('[-r REV] [REV]'))
2455 def debugsub(ui, repo, rev=None):
2455 def debugsub(ui, repo, rev=None):
2456 ctx = scmutil.revsingle(repo, rev, None)
2456 ctx = scmutil.revsingle(repo, rev, None)
2457 for k, v in sorted(ctx.substate.items()):
2457 for k, v in sorted(ctx.substate.items()):
2458 ui.write(('path %s\n') % k)
2458 ui.write(('path %s\n') % k)
2459 ui.write((' source %s\n') % v[0])
2459 ui.write((' source %s\n') % v[0])
2460 ui.write((' revision %s\n') % v[1])
2460 ui.write((' revision %s\n') % v[1])
2461
2461
2462 @command('debugsuccessorssets',
2462 @command('debugsuccessorssets',
2463 [],
2463 [],
2464 _('[REV]'))
2464 _('[REV]'))
2465 def debugsuccessorssets(ui, repo, *revs):
2465 def debugsuccessorssets(ui, repo, *revs):
2466 """show set of successors for revision
2466 """show set of successors for revision
2467
2467
2468 A successors set of changeset A is a consistent group of revisions that
2468 A successors set of changeset A is a consistent group of revisions that
2469 succeed A. It contains non-obsolete changesets only.
2469 succeed A. It contains non-obsolete changesets only.
2470
2470
2471 In most cases a changeset A has a single successors set containing a single
2471 In most cases a changeset A has a single successors set containing a single
2472 successors (changeset A replaced by A').
2472 successors (changeset A replaced by A').
2473
2473
2474 A changeset that is made obsolete with no successors are called "pruned".
2474 A changeset that is made obsolete with no successors are called "pruned".
2475 Such changesets have no successors sets at all.
2475 Such changesets have no successors sets at all.
2476
2476
2477 A changeset that has been "split" will have a successors set containing
2477 A changeset that has been "split" will have a successors set containing
2478 more than one successors.
2478 more than one successors.
2479
2479
2480 A changeset that has been rewritten in multiple different ways is called
2480 A changeset that has been rewritten in multiple different ways is called
2481 "divergent". Such changesets have multiple successor sets (each of which
2481 "divergent". Such changesets have multiple successor sets (each of which
2482 may also be split, i.e. have multiple successors).
2482 may also be split, i.e. have multiple successors).
2483
2483
2484 Results are displayed as follows::
2484 Results are displayed as follows::
2485
2485
2486 <rev1>
2486 <rev1>
2487 <successors-1A>
2487 <successors-1A>
2488 <rev2>
2488 <rev2>
2489 <successors-2A>
2489 <successors-2A>
2490 <successors-2B1> <successors-2B2> <successors-2B3>
2490 <successors-2B1> <successors-2B2> <successors-2B3>
2491
2491
2492 Here rev2 has two possible (i.e. divergent) successors sets. The first
2492 Here rev2 has two possible (i.e. divergent) successors sets. The first
2493 holds one element, whereas the second holds three (i.e. the changeset has
2493 holds one element, whereas the second holds three (i.e. the changeset has
2494 been split).
2494 been split).
2495 """
2495 """
2496 # passed to successorssets caching computation from one call to another
2496 # passed to successorssets caching computation from one call to another
2497 cache = {}
2497 cache = {}
2498 ctx2str = str
2498 ctx2str = str
2499 node2str = short
2499 node2str = short
2500 if ui.debug():
2500 if ui.debug():
2501 def ctx2str(ctx):
2501 def ctx2str(ctx):
2502 return ctx.hex()
2502 return ctx.hex()
2503 node2str = hex
2503 node2str = hex
2504 for rev in scmutil.revrange(repo, revs):
2504 for rev in scmutil.revrange(repo, revs):
2505 ctx = repo[rev]
2505 ctx = repo[rev]
2506 ui.write('%s\n'% ctx2str(ctx))
2506 ui.write('%s\n'% ctx2str(ctx))
2507 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
2507 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
2508 if succsset:
2508 if succsset:
2509 ui.write(' ')
2509 ui.write(' ')
2510 ui.write(node2str(succsset[0]))
2510 ui.write(node2str(succsset[0]))
2511 for node in succsset[1:]:
2511 for node in succsset[1:]:
2512 ui.write(' ')
2512 ui.write(' ')
2513 ui.write(node2str(node))
2513 ui.write(node2str(node))
2514 ui.write('\n')
2514 ui.write('\n')
2515
2515
2516 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2516 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2517 def debugwalk(ui, repo, *pats, **opts):
2517 def debugwalk(ui, repo, *pats, **opts):
2518 """show how files match on given patterns"""
2518 """show how files match on given patterns"""
2519 m = scmutil.match(repo[None], pats, opts)
2519 m = scmutil.match(repo[None], pats, opts)
2520 items = list(repo.walk(m))
2520 items = list(repo.walk(m))
2521 if not items:
2521 if not items:
2522 return
2522 return
2523 f = lambda fn: fn
2523 f = lambda fn: fn
2524 if ui.configbool('ui', 'slash') and os.sep != '/':
2524 if ui.configbool('ui', 'slash') and os.sep != '/':
2525 f = lambda fn: util.normpath(fn)
2525 f = lambda fn: util.normpath(fn)
2526 fmt = 'f %%-%ds %%-%ds %%s' % (
2526 fmt = 'f %%-%ds %%-%ds %%s' % (
2527 max([len(abs) for abs in items]),
2527 max([len(abs) for abs in items]),
2528 max([len(m.rel(abs)) for abs in items]))
2528 max([len(m.rel(abs)) for abs in items]))
2529 for abs in items:
2529 for abs in items:
2530 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2530 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2531 ui.write("%s\n" % line.rstrip())
2531 ui.write("%s\n" % line.rstrip())
2532
2532
2533 @command('debugwireargs',
2533 @command('debugwireargs',
2534 [('', 'three', '', 'three'),
2534 [('', 'three', '', 'three'),
2535 ('', 'four', '', 'four'),
2535 ('', 'four', '', 'four'),
2536 ('', 'five', '', 'five'),
2536 ('', 'five', '', 'five'),
2537 ] + remoteopts,
2537 ] + remoteopts,
2538 _('REPO [OPTIONS]... [ONE [TWO]]'))
2538 _('REPO [OPTIONS]... [ONE [TWO]]'))
2539 def debugwireargs(ui, repopath, *vals, **opts):
2539 def debugwireargs(ui, repopath, *vals, **opts):
2540 repo = hg.peer(ui, opts, repopath)
2540 repo = hg.peer(ui, opts, repopath)
2541 for opt in remoteopts:
2541 for opt in remoteopts:
2542 del opts[opt[1]]
2542 del opts[opt[1]]
2543 args = {}
2543 args = {}
2544 for k, v in opts.iteritems():
2544 for k, v in opts.iteritems():
2545 if v:
2545 if v:
2546 args[k] = v
2546 args[k] = v
2547 # run twice to check that we don't mess up the stream for the next command
2547 # run twice to check that we don't mess up the stream for the next command
2548 res1 = repo.debugwireargs(*vals, **args)
2548 res1 = repo.debugwireargs(*vals, **args)
2549 res2 = repo.debugwireargs(*vals, **args)
2549 res2 = repo.debugwireargs(*vals, **args)
2550 ui.write("%s\n" % res1)
2550 ui.write("%s\n" % res1)
2551 if res1 != res2:
2551 if res1 != res2:
2552 ui.warn("%s\n" % res2)
2552 ui.warn("%s\n" % res2)
2553
2553
2554 @command('^diff',
2554 @command('^diff',
2555 [('r', 'rev', [], _('revision'), _('REV')),
2555 [('r', 'rev', [], _('revision'), _('REV')),
2556 ('c', 'change', '', _('change made by revision'), _('REV'))
2556 ('c', 'change', '', _('change made by revision'), _('REV'))
2557 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2557 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2558 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2558 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2559 def diff(ui, repo, *pats, **opts):
2559 def diff(ui, repo, *pats, **opts):
2560 """diff repository (or selected files)
2560 """diff repository (or selected files)
2561
2561
2562 Show differences between revisions for the specified files.
2562 Show differences between revisions for the specified files.
2563
2563
2564 Differences between files are shown using the unified diff format.
2564 Differences between files are shown using the unified diff format.
2565
2565
2566 .. note::
2566 .. note::
2567 diff may generate unexpected results for merges, as it will
2567 diff may generate unexpected results for merges, as it will
2568 default to comparing against the working directory's first
2568 default to comparing against the working directory's first
2569 parent changeset if no revisions are specified.
2569 parent changeset if no revisions are specified.
2570
2570
2571 When two revision arguments are given, then changes are shown
2571 When two revision arguments are given, then changes are shown
2572 between those revisions. If only one revision is specified then
2572 between those revisions. If only one revision is specified then
2573 that revision is compared to the working directory, and, when no
2573 that revision is compared to the working directory, and, when no
2574 revisions are specified, the working directory files are compared
2574 revisions are specified, the working directory files are compared
2575 to its parent.
2575 to its parent.
2576
2576
2577 Alternatively you can specify -c/--change with a revision to see
2577 Alternatively you can specify -c/--change with a revision to see
2578 the changes in that changeset relative to its first parent.
2578 the changes in that changeset relative to its first parent.
2579
2579
2580 Without the -a/--text option, diff will avoid generating diffs of
2580 Without the -a/--text option, diff will avoid generating diffs of
2581 files it detects as binary. With -a, diff will generate a diff
2581 files it detects as binary. With -a, diff will generate a diff
2582 anyway, probably with undesirable results.
2582 anyway, probably with undesirable results.
2583
2583
2584 Use the -g/--git option to generate diffs in the git extended diff
2584 Use the -g/--git option to generate diffs in the git extended diff
2585 format. For more information, read :hg:`help diffs`.
2585 format. For more information, read :hg:`help diffs`.
2586
2586
2587 .. container:: verbose
2587 .. container:: verbose
2588
2588
2589 Examples:
2589 Examples:
2590
2590
2591 - compare a file in the current working directory to its parent::
2591 - compare a file in the current working directory to its parent::
2592
2592
2593 hg diff foo.c
2593 hg diff foo.c
2594
2594
2595 - compare two historical versions of a directory, with rename info::
2595 - compare two historical versions of a directory, with rename info::
2596
2596
2597 hg diff --git -r 1.0:1.2 lib/
2597 hg diff --git -r 1.0:1.2 lib/
2598
2598
2599 - get change stats relative to the last change on some date::
2599 - get change stats relative to the last change on some date::
2600
2600
2601 hg diff --stat -r "date('may 2')"
2601 hg diff --stat -r "date('may 2')"
2602
2602
2603 - diff all newly-added files that contain a keyword::
2603 - diff all newly-added files that contain a keyword::
2604
2604
2605 hg diff "set:added() and grep(GNU)"
2605 hg diff "set:added() and grep(GNU)"
2606
2606
2607 - compare a revision and its parents::
2607 - compare a revision and its parents::
2608
2608
2609 hg diff -c 9353 # compare against first parent
2609 hg diff -c 9353 # compare against first parent
2610 hg diff -r 9353^:9353 # same using revset syntax
2610 hg diff -r 9353^:9353 # same using revset syntax
2611 hg diff -r 9353^2:9353 # compare against the second parent
2611 hg diff -r 9353^2:9353 # compare against the second parent
2612
2612
2613 Returns 0 on success.
2613 Returns 0 on success.
2614 """
2614 """
2615
2615
2616 revs = opts.get('rev')
2616 revs = opts.get('rev')
2617 change = opts.get('change')
2617 change = opts.get('change')
2618 stat = opts.get('stat')
2618 stat = opts.get('stat')
2619 reverse = opts.get('reverse')
2619 reverse = opts.get('reverse')
2620
2620
2621 if revs and change:
2621 if revs and change:
2622 msg = _('cannot specify --rev and --change at the same time')
2622 msg = _('cannot specify --rev and --change at the same time')
2623 raise util.Abort(msg)
2623 raise util.Abort(msg)
2624 elif change:
2624 elif change:
2625 node2 = scmutil.revsingle(repo, change, None).node()
2625 node2 = scmutil.revsingle(repo, change, None).node()
2626 node1 = repo[node2].p1().node()
2626 node1 = repo[node2].p1().node()
2627 else:
2627 else:
2628 node1, node2 = scmutil.revpair(repo, revs)
2628 node1, node2 = scmutil.revpair(repo, revs)
2629
2629
2630 if reverse:
2630 if reverse:
2631 node1, node2 = node2, node1
2631 node1, node2 = node2, node1
2632
2632
2633 diffopts = patch.diffopts(ui, opts)
2633 diffopts = patch.diffopts(ui, opts)
2634 m = scmutil.match(repo[node2], pats, opts)
2634 m = scmutil.match(repo[node2], pats, opts)
2635 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2635 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2636 listsubrepos=opts.get('subrepos'))
2636 listsubrepos=opts.get('subrepos'))
2637
2637
2638 @command('^export',
2638 @command('^export',
2639 [('o', 'output', '',
2639 [('o', 'output', '',
2640 _('print output to file with formatted name'), _('FORMAT')),
2640 _('print output to file with formatted name'), _('FORMAT')),
2641 ('', 'switch-parent', None, _('diff against the second parent')),
2641 ('', 'switch-parent', None, _('diff against the second parent')),
2642 ('r', 'rev', [], _('revisions to export'), _('REV')),
2642 ('r', 'rev', [], _('revisions to export'), _('REV')),
2643 ] + diffopts,
2643 ] + diffopts,
2644 _('[OPTION]... [-o OUTFILESPEC] [-r] REV...'))
2644 _('[OPTION]... [-o OUTFILESPEC] [-r] REV...'))
2645 def export(ui, repo, *changesets, **opts):
2645 def export(ui, repo, *changesets, **opts):
2646 """dump the header and diffs for one or more changesets
2646 """dump the header and diffs for one or more changesets
2647
2647
2648 Print the changeset header and diffs for one or more revisions.
2648 Print the changeset header and diffs for one or more revisions.
2649
2649
2650 The information shown in the changeset header is: author, date,
2650 The information shown in the changeset header is: author, date,
2651 branch name (if non-default), changeset hash, parent(s) and commit
2651 branch name (if non-default), changeset hash, parent(s) and commit
2652 comment.
2652 comment.
2653
2653
2654 .. note::
2654 .. note::
2655 export may generate unexpected diff output for merge
2655 export may generate unexpected diff output for merge
2656 changesets, as it will compare the merge changeset against its
2656 changesets, as it will compare the merge changeset against its
2657 first parent only.
2657 first parent only.
2658
2658
2659 Output may be to a file, in which case the name of the file is
2659 Output may be to a file, in which case the name of the file is
2660 given using a format string. The formatting rules are as follows:
2660 given using a format string. The formatting rules are as follows:
2661
2661
2662 :``%%``: literal "%" character
2662 :``%%``: literal "%" character
2663 :``%H``: changeset hash (40 hexadecimal digits)
2663 :``%H``: changeset hash (40 hexadecimal digits)
2664 :``%N``: number of patches being generated
2664 :``%N``: number of patches being generated
2665 :``%R``: changeset revision number
2665 :``%R``: changeset revision number
2666 :``%b``: basename of the exporting repository
2666 :``%b``: basename of the exporting repository
2667 :``%h``: short-form changeset hash (12 hexadecimal digits)
2667 :``%h``: short-form changeset hash (12 hexadecimal digits)
2668 :``%m``: first line of the commit message (only alphanumeric characters)
2668 :``%m``: first line of the commit message (only alphanumeric characters)
2669 :``%n``: zero-padded sequence number, starting at 1
2669 :``%n``: zero-padded sequence number, starting at 1
2670 :``%r``: zero-padded changeset revision number
2670 :``%r``: zero-padded changeset revision number
2671
2671
2672 Without the -a/--text option, export will avoid generating diffs
2672 Without the -a/--text option, export will avoid generating diffs
2673 of files it detects as binary. With -a, export will generate a
2673 of files it detects as binary. With -a, export will generate a
2674 diff anyway, probably with undesirable results.
2674 diff anyway, probably with undesirable results.
2675
2675
2676 Use the -g/--git option to generate diffs in the git extended diff
2676 Use the -g/--git option to generate diffs in the git extended diff
2677 format. See :hg:`help diffs` for more information.
2677 format. See :hg:`help diffs` for more information.
2678
2678
2679 With the --switch-parent option, the diff will be against the
2679 With the --switch-parent option, the diff will be against the
2680 second parent. It can be useful to review a merge.
2680 second parent. It can be useful to review a merge.
2681
2681
2682 .. container:: verbose
2682 .. container:: verbose
2683
2683
2684 Examples:
2684 Examples:
2685
2685
2686 - use export and import to transplant a bugfix to the current
2686 - use export and import to transplant a bugfix to the current
2687 branch::
2687 branch::
2688
2688
2689 hg export -r 9353 | hg import -
2689 hg export -r 9353 | hg import -
2690
2690
2691 - export all the changesets between two revisions to a file with
2691 - export all the changesets between two revisions to a file with
2692 rename information::
2692 rename information::
2693
2693
2694 hg export --git -r 123:150 > changes.txt
2694 hg export --git -r 123:150 > changes.txt
2695
2695
2696 - split outgoing changes into a series of patches with
2696 - split outgoing changes into a series of patches with
2697 descriptive names::
2697 descriptive names::
2698
2698
2699 hg export -r "outgoing()" -o "%n-%m.patch"
2699 hg export -r "outgoing()" -o "%n-%m.patch"
2700
2700
2701 Returns 0 on success.
2701 Returns 0 on success.
2702 """
2702 """
2703 changesets += tuple(opts.get('rev', []))
2703 changesets += tuple(opts.get('rev', []))
2704 revs = scmutil.revrange(repo, changesets)
2704 revs = scmutil.revrange(repo, changesets)
2705 if not revs:
2705 if not revs:
2706 raise util.Abort(_("export requires at least one changeset"))
2706 raise util.Abort(_("export requires at least one changeset"))
2707 if len(revs) > 1:
2707 if len(revs) > 1:
2708 ui.note(_('exporting patches:\n'))
2708 ui.note(_('exporting patches:\n'))
2709 else:
2709 else:
2710 ui.note(_('exporting patch:\n'))
2710 ui.note(_('exporting patch:\n'))
2711 cmdutil.export(repo, revs, template=opts.get('output'),
2711 cmdutil.export(repo, revs, template=opts.get('output'),
2712 switch_parent=opts.get('switch_parent'),
2712 switch_parent=opts.get('switch_parent'),
2713 opts=patch.diffopts(ui, opts))
2713 opts=patch.diffopts(ui, opts))
2714
2714
2715 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2715 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2716 def forget(ui, repo, *pats, **opts):
2716 def forget(ui, repo, *pats, **opts):
2717 """forget the specified files on the next commit
2717 """forget the specified files on the next commit
2718
2718
2719 Mark the specified files so they will no longer be tracked
2719 Mark the specified files so they will no longer be tracked
2720 after the next commit.
2720 after the next commit.
2721
2721
2722 This only removes files from the current branch, not from the
2722 This only removes files from the current branch, not from the
2723 entire project history, and it does not delete them from the
2723 entire project history, and it does not delete them from the
2724 working directory.
2724 working directory.
2725
2725
2726 To undo a forget before the next commit, see :hg:`add`.
2726 To undo a forget before the next commit, see :hg:`add`.
2727
2727
2728 .. container:: verbose
2728 .. container:: verbose
2729
2729
2730 Examples:
2730 Examples:
2731
2731
2732 - forget newly-added binary files::
2732 - forget newly-added binary files::
2733
2733
2734 hg forget "set:added() and binary()"
2734 hg forget "set:added() and binary()"
2735
2735
2736 - forget files that would be excluded by .hgignore::
2736 - forget files that would be excluded by .hgignore::
2737
2737
2738 hg forget "set:hgignore()"
2738 hg forget "set:hgignore()"
2739
2739
2740 Returns 0 on success.
2740 Returns 0 on success.
2741 """
2741 """
2742
2742
2743 if not pats:
2743 if not pats:
2744 raise util.Abort(_('no files specified'))
2744 raise util.Abort(_('no files specified'))
2745
2745
2746 m = scmutil.match(repo[None], pats, opts)
2746 m = scmutil.match(repo[None], pats, opts)
2747 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2747 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2748 return rejected and 1 or 0
2748 return rejected and 1 or 0
2749
2749
2750 @command(
2750 @command(
2751 'graft',
2751 'graft',
2752 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2752 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2753 ('c', 'continue', False, _('resume interrupted graft')),
2753 ('c', 'continue', False, _('resume interrupted graft')),
2754 ('e', 'edit', False, _('invoke editor on commit messages')),
2754 ('e', 'edit', False, _('invoke editor on commit messages')),
2755 ('', 'log', None, _('append graft info to log message')),
2755 ('', 'log', None, _('append graft info to log message')),
2756 ('D', 'currentdate', False,
2756 ('D', 'currentdate', False,
2757 _('record the current date as commit date')),
2757 _('record the current date as commit date')),
2758 ('U', 'currentuser', False,
2758 ('U', 'currentuser', False,
2759 _('record the current user as committer'), _('DATE'))]
2759 _('record the current user as committer'), _('DATE'))]
2760 + commitopts2 + mergetoolopts + dryrunopts,
2760 + commitopts2 + mergetoolopts + dryrunopts,
2761 _('[OPTION]... [-r] REV...'))
2761 _('[OPTION]... [-r] REV...'))
2762 def graft(ui, repo, *revs, **opts):
2762 def graft(ui, repo, *revs, **opts):
2763 '''copy changes from other branches onto the current branch
2763 '''copy changes from other branches onto the current branch
2764
2764
2765 This command uses Mercurial's merge logic to copy individual
2765 This command uses Mercurial's merge logic to copy individual
2766 changes from other branches without merging branches in the
2766 changes from other branches without merging branches in the
2767 history graph. This is sometimes known as 'backporting' or
2767 history graph. This is sometimes known as 'backporting' or
2768 'cherry-picking'. By default, graft will copy user, date, and
2768 'cherry-picking'. By default, graft will copy user, date, and
2769 description from the source changesets.
2769 description from the source changesets.
2770
2770
2771 Changesets that are ancestors of the current revision, that have
2771 Changesets that are ancestors of the current revision, that have
2772 already been grafted, or that are merges will be skipped.
2772 already been grafted, or that are merges will be skipped.
2773
2773
2774 If --log is specified, log messages will have a comment appended
2774 If --log is specified, log messages will have a comment appended
2775 of the form::
2775 of the form::
2776
2776
2777 (grafted from CHANGESETHASH)
2777 (grafted from CHANGESETHASH)
2778
2778
2779 If a graft merge results in conflicts, the graft process is
2779 If a graft merge results in conflicts, the graft process is
2780 interrupted so that the current merge can be manually resolved.
2780 interrupted so that the current merge can be manually resolved.
2781 Once all conflicts are addressed, the graft process can be
2781 Once all conflicts are addressed, the graft process can be
2782 continued with the -c/--continue option.
2782 continued with the -c/--continue option.
2783
2783
2784 .. note::
2784 .. note::
2785 The -c/--continue option does not reapply earlier options.
2785 The -c/--continue option does not reapply earlier options.
2786
2786
2787 .. container:: verbose
2787 .. container:: verbose
2788
2788
2789 Examples:
2789 Examples:
2790
2790
2791 - copy a single change to the stable branch and edit its description::
2791 - copy a single change to the stable branch and edit its description::
2792
2792
2793 hg update stable
2793 hg update stable
2794 hg graft --edit 9393
2794 hg graft --edit 9393
2795
2795
2796 - graft a range of changesets with one exception, updating dates::
2796 - graft a range of changesets with one exception, updating dates::
2797
2797
2798 hg graft -D "2085::2093 and not 2091"
2798 hg graft -D "2085::2093 and not 2091"
2799
2799
2800 - continue a graft after resolving conflicts::
2800 - continue a graft after resolving conflicts::
2801
2801
2802 hg graft -c
2802 hg graft -c
2803
2803
2804 - show the source of a grafted changeset::
2804 - show the source of a grafted changeset::
2805
2805
2806 hg log --debug -r tip
2806 hg log --debug -r tip
2807
2807
2808 Returns 0 on successful completion.
2808 Returns 0 on successful completion.
2809 '''
2809 '''
2810
2810
2811 revs = list(revs)
2811 revs = list(revs)
2812 revs.extend(opts['rev'])
2812 revs.extend(opts['rev'])
2813
2813
2814 if not opts.get('user') and opts.get('currentuser'):
2814 if not opts.get('user') and opts.get('currentuser'):
2815 opts['user'] = ui.username()
2815 opts['user'] = ui.username()
2816 if not opts.get('date') and opts.get('currentdate'):
2816 if not opts.get('date') and opts.get('currentdate'):
2817 opts['date'] = "%d %d" % util.makedate()
2817 opts['date'] = "%d %d" % util.makedate()
2818
2818
2819 editor = None
2819 editor = None
2820 if opts.get('edit'):
2820 if opts.get('edit'):
2821 editor = cmdutil.commitforceeditor
2821 editor = cmdutil.commitforceeditor
2822
2822
2823 cont = False
2823 cont = False
2824 if opts['continue']:
2824 if opts['continue']:
2825 cont = True
2825 cont = True
2826 if revs:
2826 if revs:
2827 raise util.Abort(_("can't specify --continue and revisions"))
2827 raise util.Abort(_("can't specify --continue and revisions"))
2828 # read in unfinished revisions
2828 # read in unfinished revisions
2829 try:
2829 try:
2830 nodes = repo.opener.read('graftstate').splitlines()
2830 nodes = repo.opener.read('graftstate').splitlines()
2831 revs = [repo[node].rev() for node in nodes]
2831 revs = [repo[node].rev() for node in nodes]
2832 except IOError, inst:
2832 except IOError, inst:
2833 if inst.errno != errno.ENOENT:
2833 if inst.errno != errno.ENOENT:
2834 raise
2834 raise
2835 raise util.Abort(_("no graft state found, can't continue"))
2835 raise util.Abort(_("no graft state found, can't continue"))
2836 else:
2836 else:
2837 cmdutil.bailifchanged(repo)
2837 cmdutil.bailifchanged(repo)
2838 if not revs:
2838 if not revs:
2839 raise util.Abort(_('no revisions specified'))
2839 raise util.Abort(_('no revisions specified'))
2840 revs = scmutil.revrange(repo, revs)
2840 revs = scmutil.revrange(repo, revs)
2841
2841
2842 # check for merges
2842 # check for merges
2843 for rev in repo.revs('%ld and merge()', revs):
2843 for rev in repo.revs('%ld and merge()', revs):
2844 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2844 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2845 revs.remove(rev)
2845 revs.remove(rev)
2846 if not revs:
2846 if not revs:
2847 return -1
2847 return -1
2848
2848
2849 # check for ancestors of dest branch
2849 # check for ancestors of dest branch
2850 for rev in repo.revs('::. and %ld', revs):
2850 for rev in repo.revs('::. and %ld', revs):
2851 ui.warn(_('skipping ancestor revision %s\n') % rev)
2851 ui.warn(_('skipping ancestor revision %s\n') % rev)
2852 revs.remove(rev)
2852 revs.remove(rev)
2853 if not revs:
2853 if not revs:
2854 return -1
2854 return -1
2855
2855
2856 # analyze revs for earlier grafts
2856 # analyze revs for earlier grafts
2857 ids = {}
2857 ids = {}
2858 for ctx in repo.set("%ld", revs):
2858 for ctx in repo.set("%ld", revs):
2859 ids[ctx.hex()] = ctx.rev()
2859 ids[ctx.hex()] = ctx.rev()
2860 n = ctx.extra().get('source')
2860 n = ctx.extra().get('source')
2861 if n:
2861 if n:
2862 ids[n] = ctx.rev()
2862 ids[n] = ctx.rev()
2863
2863
2864 # check ancestors for earlier grafts
2864 # check ancestors for earlier grafts
2865 ui.debug('scanning for duplicate grafts\n')
2865 ui.debug('scanning for duplicate grafts\n')
2866 for ctx in repo.set("::. - ::%ld", revs):
2866 for ctx in repo.set("::. - ::%ld", revs):
2867 n = ctx.extra().get('source')
2867 n = ctx.extra().get('source')
2868 if n in ids:
2868 if n in ids:
2869 r = repo[n].rev()
2869 r = repo[n].rev()
2870 if r in revs:
2870 if r in revs:
2871 ui.warn(_('skipping already grafted revision %s\n') % r)
2871 ui.warn(_('skipping already grafted revision %s\n') % r)
2872 revs.remove(r)
2872 revs.remove(r)
2873 elif ids[n] in revs:
2873 elif ids[n] in revs:
2874 ui.warn(_('skipping already grafted revision %s '
2874 ui.warn(_('skipping already grafted revision %s '
2875 '(same origin %d)\n') % (ids[n], r))
2875 '(same origin %d)\n') % (ids[n], r))
2876 revs.remove(ids[n])
2876 revs.remove(ids[n])
2877 elif ctx.hex() in ids:
2877 elif ctx.hex() in ids:
2878 r = ids[ctx.hex()]
2878 r = ids[ctx.hex()]
2879 ui.warn(_('skipping already grafted revision %s '
2879 ui.warn(_('skipping already grafted revision %s '
2880 '(was grafted from %d)\n') % (r, ctx.rev()))
2880 '(was grafted from %d)\n') % (r, ctx.rev()))
2881 revs.remove(r)
2881 revs.remove(r)
2882 if not revs:
2882 if not revs:
2883 return -1
2883 return -1
2884
2884
2885 wlock = repo.wlock()
2885 wlock = repo.wlock()
2886 try:
2886 try:
2887 current = repo['.']
2887 current = repo['.']
2888 for pos, ctx in enumerate(repo.set("%ld", revs)):
2888 for pos, ctx in enumerate(repo.set("%ld", revs)):
2889
2889
2890 ui.status(_('grafting revision %s\n') % ctx.rev())
2890 ui.status(_('grafting revision %s\n') % ctx.rev())
2891 if opts.get('dry_run'):
2891 if opts.get('dry_run'):
2892 continue
2892 continue
2893
2893
2894 source = ctx.extra().get('source')
2894 source = ctx.extra().get('source')
2895 if not source:
2895 if not source:
2896 source = ctx.hex()
2896 source = ctx.hex()
2897 extra = {'source': source}
2897 extra = {'source': source}
2898 user = ctx.user()
2898 user = ctx.user()
2899 if opts.get('user'):
2899 if opts.get('user'):
2900 user = opts['user']
2900 user = opts['user']
2901 date = ctx.date()
2901 date = ctx.date()
2902 if opts.get('date'):
2902 if opts.get('date'):
2903 date = opts['date']
2903 date = opts['date']
2904 message = ctx.description()
2904 message = ctx.description()
2905 if opts.get('log'):
2905 if opts.get('log'):
2906 message += '\n(grafted from %s)' % ctx.hex()
2906 message += '\n(grafted from %s)' % ctx.hex()
2907
2907
2908 # we don't merge the first commit when continuing
2908 # we don't merge the first commit when continuing
2909 if not cont:
2909 if not cont:
2910 # perform the graft merge with p1(rev) as 'ancestor'
2910 # perform the graft merge with p1(rev) as 'ancestor'
2911 try:
2911 try:
2912 # ui.forcemerge is an internal variable, do not document
2912 # ui.forcemerge is an internal variable, do not document
2913 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2913 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2914 stats = mergemod.update(repo, ctx.node(), True, True, False,
2914 stats = mergemod.update(repo, ctx.node(), True, True, False,
2915 ctx.p1().node())
2915 ctx.p1().node())
2916 finally:
2916 finally:
2917 repo.ui.setconfig('ui', 'forcemerge', '')
2917 repo.ui.setconfig('ui', 'forcemerge', '')
2918 # report any conflicts
2918 # report any conflicts
2919 if stats and stats[3] > 0:
2919 if stats and stats[3] > 0:
2920 # write out state for --continue
2920 # write out state for --continue
2921 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2921 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2922 repo.opener.write('graftstate', ''.join(nodelines))
2922 repo.opener.write('graftstate', ''.join(nodelines))
2923 raise util.Abort(
2923 raise util.Abort(
2924 _("unresolved conflicts, can't continue"),
2924 _("unresolved conflicts, can't continue"),
2925 hint=_('use hg resolve and hg graft --continue'))
2925 hint=_('use hg resolve and hg graft --continue'))
2926 else:
2926 else:
2927 cont = False
2927 cont = False
2928
2928
2929 # drop the second merge parent
2929 # drop the second merge parent
2930 repo.setparents(current.node(), nullid)
2930 repo.setparents(current.node(), nullid)
2931 repo.dirstate.write()
2931 repo.dirstate.write()
2932 # fix up dirstate for copies and renames
2932 # fix up dirstate for copies and renames
2933 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
2933 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
2934
2934
2935 # commit
2935 # commit
2936 node = repo.commit(text=message, user=user,
2936 node = repo.commit(text=message, user=user,
2937 date=date, extra=extra, editor=editor)
2937 date=date, extra=extra, editor=editor)
2938 if node is None:
2938 if node is None:
2939 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
2939 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
2940 else:
2940 else:
2941 current = repo[node]
2941 current = repo[node]
2942 finally:
2942 finally:
2943 wlock.release()
2943 wlock.release()
2944
2944
2945 # remove state when we complete successfully
2945 # remove state when we complete successfully
2946 if not opts.get('dry_run') and os.path.exists(repo.join('graftstate')):
2946 if not opts.get('dry_run') and os.path.exists(repo.join('graftstate')):
2947 util.unlinkpath(repo.join('graftstate'))
2947 util.unlinkpath(repo.join('graftstate'))
2948
2948
2949 return 0
2949 return 0
2950
2950
2951 @command('grep',
2951 @command('grep',
2952 [('0', 'print0', None, _('end fields with NUL')),
2952 [('0', 'print0', None, _('end fields with NUL')),
2953 ('', 'all', None, _('print all revisions that match')),
2953 ('', 'all', None, _('print all revisions that match')),
2954 ('a', 'text', None, _('treat all files as text')),
2954 ('a', 'text', None, _('treat all files as text')),
2955 ('f', 'follow', None,
2955 ('f', 'follow', None,
2956 _('follow changeset history,'
2956 _('follow changeset history,'
2957 ' or file history across copies and renames')),
2957 ' or file history across copies and renames')),
2958 ('i', 'ignore-case', None, _('ignore case when matching')),
2958 ('i', 'ignore-case', None, _('ignore case when matching')),
2959 ('l', 'files-with-matches', None,
2959 ('l', 'files-with-matches', None,
2960 _('print only filenames and revisions that match')),
2960 _('print only filenames and revisions that match')),
2961 ('n', 'line-number', None, _('print matching line numbers')),
2961 ('n', 'line-number', None, _('print matching line numbers')),
2962 ('r', 'rev', [],
2962 ('r', 'rev', [],
2963 _('only search files changed within revision range'), _('REV')),
2963 _('only search files changed within revision range'), _('REV')),
2964 ('u', 'user', None, _('list the author (long with -v)')),
2964 ('u', 'user', None, _('list the author (long with -v)')),
2965 ('d', 'date', None, _('list the date (short with -q)')),
2965 ('d', 'date', None, _('list the date (short with -q)')),
2966 ] + walkopts,
2966 ] + walkopts,
2967 _('[OPTION]... PATTERN [FILE]...'))
2967 _('[OPTION]... PATTERN [FILE]...'))
2968 def grep(ui, repo, pattern, *pats, **opts):
2968 def grep(ui, repo, pattern, *pats, **opts):
2969 """search for a pattern in specified files and revisions
2969 """search for a pattern in specified files and revisions
2970
2970
2971 Search revisions of files for a regular expression.
2971 Search revisions of files for a regular expression.
2972
2972
2973 This command behaves differently than Unix grep. It only accepts
2973 This command behaves differently than Unix grep. It only accepts
2974 Python/Perl regexps. It searches repository history, not the
2974 Python/Perl regexps. It searches repository history, not the
2975 working directory. It always prints the revision number in which a
2975 working directory. It always prints the revision number in which a
2976 match appears.
2976 match appears.
2977
2977
2978 By default, grep only prints output for the first revision of a
2978 By default, grep only prints output for the first revision of a
2979 file in which it finds a match. To get it to print every revision
2979 file in which it finds a match. To get it to print every revision
2980 that contains a change in match status ("-" for a match that
2980 that contains a change in match status ("-" for a match that
2981 becomes a non-match, or "+" for a non-match that becomes a match),
2981 becomes a non-match, or "+" for a non-match that becomes a match),
2982 use the --all flag.
2982 use the --all flag.
2983
2983
2984 Returns 0 if a match is found, 1 otherwise.
2984 Returns 0 if a match is found, 1 otherwise.
2985 """
2985 """
2986 reflags = re.M
2986 reflags = re.M
2987 if opts.get('ignore_case'):
2987 if opts.get('ignore_case'):
2988 reflags |= re.I
2988 reflags |= re.I
2989 try:
2989 try:
2990 regexp = re.compile(pattern, reflags)
2990 regexp = re.compile(pattern, reflags)
2991 except re.error, inst:
2991 except re.error, inst:
2992 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2992 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2993 return 1
2993 return 1
2994 sep, eol = ':', '\n'
2994 sep, eol = ':', '\n'
2995 if opts.get('print0'):
2995 if opts.get('print0'):
2996 sep = eol = '\0'
2996 sep = eol = '\0'
2997
2997
2998 getfile = util.lrucachefunc(repo.file)
2998 getfile = util.lrucachefunc(repo.file)
2999
2999
3000 def matchlines(body):
3000 def matchlines(body):
3001 begin = 0
3001 begin = 0
3002 linenum = 0
3002 linenum = 0
3003 while begin < len(body):
3003 while begin < len(body):
3004 match = regexp.search(body, begin)
3004 match = regexp.search(body, begin)
3005 if not match:
3005 if not match:
3006 break
3006 break
3007 mstart, mend = match.span()
3007 mstart, mend = match.span()
3008 linenum += body.count('\n', begin, mstart) + 1
3008 linenum += body.count('\n', begin, mstart) + 1
3009 lstart = body.rfind('\n', begin, mstart) + 1 or begin
3009 lstart = body.rfind('\n', begin, mstart) + 1 or begin
3010 begin = body.find('\n', mend) + 1 or len(body) + 1
3010 begin = body.find('\n', mend) + 1 or len(body) + 1
3011 lend = begin - 1
3011 lend = begin - 1
3012 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3012 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3013
3013
3014 class linestate(object):
3014 class linestate(object):
3015 def __init__(self, line, linenum, colstart, colend):
3015 def __init__(self, line, linenum, colstart, colend):
3016 self.line = line
3016 self.line = line
3017 self.linenum = linenum
3017 self.linenum = linenum
3018 self.colstart = colstart
3018 self.colstart = colstart
3019 self.colend = colend
3019 self.colend = colend
3020
3020
3021 def __hash__(self):
3021 def __hash__(self):
3022 return hash((self.linenum, self.line))
3022 return hash((self.linenum, self.line))
3023
3023
3024 def __eq__(self, other):
3024 def __eq__(self, other):
3025 return self.line == other.line
3025 return self.line == other.line
3026
3026
3027 matches = {}
3027 matches = {}
3028 copies = {}
3028 copies = {}
3029 def grepbody(fn, rev, body):
3029 def grepbody(fn, rev, body):
3030 matches[rev].setdefault(fn, [])
3030 matches[rev].setdefault(fn, [])
3031 m = matches[rev][fn]
3031 m = matches[rev][fn]
3032 for lnum, cstart, cend, line in matchlines(body):
3032 for lnum, cstart, cend, line in matchlines(body):
3033 s = linestate(line, lnum, cstart, cend)
3033 s = linestate(line, lnum, cstart, cend)
3034 m.append(s)
3034 m.append(s)
3035
3035
3036 def difflinestates(a, b):
3036 def difflinestates(a, b):
3037 sm = difflib.SequenceMatcher(None, a, b)
3037 sm = difflib.SequenceMatcher(None, a, b)
3038 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3038 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3039 if tag == 'insert':
3039 if tag == 'insert':
3040 for i in xrange(blo, bhi):
3040 for i in xrange(blo, bhi):
3041 yield ('+', b[i])
3041 yield ('+', b[i])
3042 elif tag == 'delete':
3042 elif tag == 'delete':
3043 for i in xrange(alo, ahi):
3043 for i in xrange(alo, ahi):
3044 yield ('-', a[i])
3044 yield ('-', a[i])
3045 elif tag == 'replace':
3045 elif tag == 'replace':
3046 for i in xrange(alo, ahi):
3046 for i in xrange(alo, ahi):
3047 yield ('-', a[i])
3047 yield ('-', a[i])
3048 for i in xrange(blo, bhi):
3048 for i in xrange(blo, bhi):
3049 yield ('+', b[i])
3049 yield ('+', b[i])
3050
3050
3051 def display(fn, ctx, pstates, states):
3051 def display(fn, ctx, pstates, states):
3052 rev = ctx.rev()
3052 rev = ctx.rev()
3053 datefunc = ui.quiet and util.shortdate or util.datestr
3053 datefunc = ui.quiet and util.shortdate or util.datestr
3054 found = False
3054 found = False
3055 filerevmatches = {}
3055 filerevmatches = {}
3056 def binary():
3056 def binary():
3057 flog = getfile(fn)
3057 flog = getfile(fn)
3058 return util.binary(flog.read(ctx.filenode(fn)))
3058 return util.binary(flog.read(ctx.filenode(fn)))
3059
3059
3060 if opts.get('all'):
3060 if opts.get('all'):
3061 iter = difflinestates(pstates, states)
3061 iter = difflinestates(pstates, states)
3062 else:
3062 else:
3063 iter = [('', l) for l in states]
3063 iter = [('', l) for l in states]
3064 for change, l in iter:
3064 for change, l in iter:
3065 cols = [(fn, 'grep.filename'), (str(rev), 'grep.rev')]
3065 cols = [(fn, 'grep.filename'), (str(rev), 'grep.rev')]
3066 before, match, after = None, None, None
3066 before, match, after = None, None, None
3067
3067
3068 if opts.get('line_number'):
3068 if opts.get('line_number'):
3069 cols.append((str(l.linenum), 'grep.linenumber'))
3069 cols.append((str(l.linenum), 'grep.linenumber'))
3070 if opts.get('all'):
3070 if opts.get('all'):
3071 cols.append((change, 'grep.change'))
3071 cols.append((change, 'grep.change'))
3072 if opts.get('user'):
3072 if opts.get('user'):
3073 cols.append((ui.shortuser(ctx.user()), 'grep.user'))
3073 cols.append((ui.shortuser(ctx.user()), 'grep.user'))
3074 if opts.get('date'):
3074 if opts.get('date'):
3075 cols.append((datefunc(ctx.date()), 'grep.date'))
3075 cols.append((datefunc(ctx.date()), 'grep.date'))
3076 if opts.get('files_with_matches'):
3076 if opts.get('files_with_matches'):
3077 c = (fn, rev)
3077 c = (fn, rev)
3078 if c in filerevmatches:
3078 if c in filerevmatches:
3079 continue
3079 continue
3080 filerevmatches[c] = 1
3080 filerevmatches[c] = 1
3081 else:
3081 else:
3082 before = l.line[:l.colstart]
3082 before = l.line[:l.colstart]
3083 match = l.line[l.colstart:l.colend]
3083 match = l.line[l.colstart:l.colend]
3084 after = l.line[l.colend:]
3084 after = l.line[l.colend:]
3085 for col, label in cols[:-1]:
3085 for col, label in cols[:-1]:
3086 ui.write(col, label=label)
3086 ui.write(col, label=label)
3087 ui.write(sep, label='grep.sep')
3087 ui.write(sep, label='grep.sep')
3088 ui.write(cols[-1][0], label=cols[-1][1])
3088 ui.write(cols[-1][0], label=cols[-1][1])
3089 if before is not None:
3089 if before is not None:
3090 ui.write(sep, label='grep.sep')
3090 ui.write(sep, label='grep.sep')
3091 if not opts.get('text') and binary():
3091 if not opts.get('text') and binary():
3092 ui.write(" Binary file matches")
3092 ui.write(" Binary file matches")
3093 else:
3093 else:
3094 ui.write(before)
3094 ui.write(before)
3095 ui.write(match, label='grep.match')
3095 ui.write(match, label='grep.match')
3096 ui.write(after)
3096 ui.write(after)
3097 ui.write(eol)
3097 ui.write(eol)
3098 found = True
3098 found = True
3099 return found
3099 return found
3100
3100
3101 skip = {}
3101 skip = {}
3102 revfiles = {}
3102 revfiles = {}
3103 matchfn = scmutil.match(repo[None], pats, opts)
3103 matchfn = scmutil.match(repo[None], pats, opts)
3104 found = False
3104 found = False
3105 follow = opts.get('follow')
3105 follow = opts.get('follow')
3106
3106
3107 def prep(ctx, fns):
3107 def prep(ctx, fns):
3108 rev = ctx.rev()
3108 rev = ctx.rev()
3109 pctx = ctx.p1()
3109 pctx = ctx.p1()
3110 parent = pctx.rev()
3110 parent = pctx.rev()
3111 matches.setdefault(rev, {})
3111 matches.setdefault(rev, {})
3112 matches.setdefault(parent, {})
3112 matches.setdefault(parent, {})
3113 files = revfiles.setdefault(rev, [])
3113 files = revfiles.setdefault(rev, [])
3114 for fn in fns:
3114 for fn in fns:
3115 flog = getfile(fn)
3115 flog = getfile(fn)
3116 try:
3116 try:
3117 fnode = ctx.filenode(fn)
3117 fnode = ctx.filenode(fn)
3118 except error.LookupError:
3118 except error.LookupError:
3119 continue
3119 continue
3120
3120
3121 copied = flog.renamed(fnode)
3121 copied = flog.renamed(fnode)
3122 copy = follow and copied and copied[0]
3122 copy = follow and copied and copied[0]
3123 if copy:
3123 if copy:
3124 copies.setdefault(rev, {})[fn] = copy
3124 copies.setdefault(rev, {})[fn] = copy
3125 if fn in skip:
3125 if fn in skip:
3126 if copy:
3126 if copy:
3127 skip[copy] = True
3127 skip[copy] = True
3128 continue
3128 continue
3129 files.append(fn)
3129 files.append(fn)
3130
3130
3131 if fn not in matches[rev]:
3131 if fn not in matches[rev]:
3132 grepbody(fn, rev, flog.read(fnode))
3132 grepbody(fn, rev, flog.read(fnode))
3133
3133
3134 pfn = copy or fn
3134 pfn = copy or fn
3135 if pfn not in matches[parent]:
3135 if pfn not in matches[parent]:
3136 try:
3136 try:
3137 fnode = pctx.filenode(pfn)
3137 fnode = pctx.filenode(pfn)
3138 grepbody(pfn, parent, flog.read(fnode))
3138 grepbody(pfn, parent, flog.read(fnode))
3139 except error.LookupError:
3139 except error.LookupError:
3140 pass
3140 pass
3141
3141
3142 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3142 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3143 rev = ctx.rev()
3143 rev = ctx.rev()
3144 parent = ctx.p1().rev()
3144 parent = ctx.p1().rev()
3145 for fn in sorted(revfiles.get(rev, [])):
3145 for fn in sorted(revfiles.get(rev, [])):
3146 states = matches[rev][fn]
3146 states = matches[rev][fn]
3147 copy = copies.get(rev, {}).get(fn)
3147 copy = copies.get(rev, {}).get(fn)
3148 if fn in skip:
3148 if fn in skip:
3149 if copy:
3149 if copy:
3150 skip[copy] = True
3150 skip[copy] = True
3151 continue
3151 continue
3152 pstates = matches.get(parent, {}).get(copy or fn, [])
3152 pstates = matches.get(parent, {}).get(copy or fn, [])
3153 if pstates or states:
3153 if pstates or states:
3154 r = display(fn, ctx, pstates, states)
3154 r = display(fn, ctx, pstates, states)
3155 found = found or r
3155 found = found or r
3156 if r and not opts.get('all'):
3156 if r and not opts.get('all'):
3157 skip[fn] = True
3157 skip[fn] = True
3158 if copy:
3158 if copy:
3159 skip[copy] = True
3159 skip[copy] = True
3160 del matches[rev]
3160 del matches[rev]
3161 del revfiles[rev]
3161 del revfiles[rev]
3162
3162
3163 return not found
3163 return not found
3164
3164
3165 @command('heads',
3165 @command('heads',
3166 [('r', 'rev', '',
3166 [('r', 'rev', '',
3167 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3167 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3168 ('t', 'topo', False, _('show topological heads only')),
3168 ('t', 'topo', False, _('show topological heads only')),
3169 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3169 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3170 ('c', 'closed', False, _('show normal and closed branch heads')),
3170 ('c', 'closed', False, _('show normal and closed branch heads')),
3171 ] + templateopts,
3171 ] + templateopts,
3172 _('[-ct] [-r STARTREV] [REV]...'))
3172 _('[-ct] [-r STARTREV] [REV]...'))
3173 def heads(ui, repo, *branchrevs, **opts):
3173 def heads(ui, repo, *branchrevs, **opts):
3174 """show current repository heads or show branch heads
3174 """show current repository heads or show branch heads
3175
3175
3176 With no arguments, show all repository branch heads.
3176 With no arguments, show all repository branch heads.
3177
3177
3178 Repository "heads" are changesets with no child changesets. They are
3178 Repository "heads" are changesets with no child changesets. They are
3179 where development generally takes place and are the usual targets
3179 where development generally takes place and are the usual targets
3180 for update and merge operations. Branch heads are changesets that have
3180 for update and merge operations. Branch heads are changesets that have
3181 no child changeset on the same branch.
3181 no child changeset on the same branch.
3182
3182
3183 If one or more REVs are given, only branch heads on the branches
3183 If one or more REVs are given, only branch heads on the branches
3184 associated with the specified changesets are shown. This means
3184 associated with the specified changesets are shown. This means
3185 that you can use :hg:`heads foo` to see the heads on a branch
3185 that you can use :hg:`heads foo` to see the heads on a branch
3186 named ``foo``.
3186 named ``foo``.
3187
3187
3188 If -c/--closed is specified, also show branch heads marked closed
3188 If -c/--closed is specified, also show branch heads marked closed
3189 (see :hg:`commit --close-branch`).
3189 (see :hg:`commit --close-branch`).
3190
3190
3191 If STARTREV is specified, only those heads that are descendants of
3191 If STARTREV is specified, only those heads that are descendants of
3192 STARTREV will be displayed.
3192 STARTREV will be displayed.
3193
3193
3194 If -t/--topo is specified, named branch mechanics will be ignored and only
3194 If -t/--topo is specified, named branch mechanics will be ignored and only
3195 changesets without children will be shown.
3195 changesets without children will be shown.
3196
3196
3197 Returns 0 if matching heads are found, 1 if not.
3197 Returns 0 if matching heads are found, 1 if not.
3198 """
3198 """
3199
3199
3200 start = None
3200 start = None
3201 if 'rev' in opts:
3201 if 'rev' in opts:
3202 start = scmutil.revsingle(repo, opts['rev'], None).node()
3202 start = scmutil.revsingle(repo, opts['rev'], None).node()
3203
3203
3204 if opts.get('topo'):
3204 if opts.get('topo'):
3205 heads = [repo[h] for h in repo.heads(start)]
3205 heads = [repo[h] for h in repo.heads(start)]
3206 else:
3206 else:
3207 heads = []
3207 heads = []
3208 for branch in repo.branchmap():
3208 for branch in repo.branchmap():
3209 heads += repo.branchheads(branch, start, opts.get('closed'))
3209 heads += repo.branchheads(branch, start, opts.get('closed'))
3210 heads = [repo[h] for h in heads]
3210 heads = [repo[h] for h in heads]
3211
3211
3212 if branchrevs:
3212 if branchrevs:
3213 branches = set(repo[br].branch() for br in branchrevs)
3213 branches = set(repo[br].branch() for br in branchrevs)
3214 heads = [h for h in heads if h.branch() in branches]
3214 heads = [h for h in heads if h.branch() in branches]
3215
3215
3216 if opts.get('active') and branchrevs:
3216 if opts.get('active') and branchrevs:
3217 dagheads = repo.heads(start)
3217 dagheads = repo.heads(start)
3218 heads = [h for h in heads if h.node() in dagheads]
3218 heads = [h for h in heads if h.node() in dagheads]
3219
3219
3220 if branchrevs:
3220 if branchrevs:
3221 haveheads = set(h.branch() for h in heads)
3221 haveheads = set(h.branch() for h in heads)
3222 if branches - haveheads:
3222 if branches - haveheads:
3223 headless = ', '.join(b for b in branches - haveheads)
3223 headless = ', '.join(b for b in branches - haveheads)
3224 msg = _('no open branch heads found on branches %s')
3224 msg = _('no open branch heads found on branches %s')
3225 if opts.get('rev'):
3225 if opts.get('rev'):
3226 msg += _(' (started at %s)') % opts['rev']
3226 msg += _(' (started at %s)') % opts['rev']
3227 ui.warn((msg + '\n') % headless)
3227 ui.warn((msg + '\n') % headless)
3228
3228
3229 if not heads:
3229 if not heads:
3230 return 1
3230 return 1
3231
3231
3232 heads = sorted(heads, key=lambda x: -x.rev())
3232 heads = sorted(heads, key=lambda x: -x.rev())
3233 displayer = cmdutil.show_changeset(ui, repo, opts)
3233 displayer = cmdutil.show_changeset(ui, repo, opts)
3234 for ctx in heads:
3234 for ctx in heads:
3235 displayer.show(ctx)
3235 displayer.show(ctx)
3236 displayer.close()
3236 displayer.close()
3237
3237
3238 @command('help',
3238 @command('help',
3239 [('e', 'extension', None, _('show only help for extensions')),
3239 [('e', 'extension', None, _('show only help for extensions')),
3240 ('c', 'command', None, _('show only help for commands')),
3240 ('c', 'command', None, _('show only help for commands')),
3241 ('k', 'keyword', '', _('show topics matching keyword')),
3241 ('k', 'keyword', '', _('show topics matching keyword')),
3242 ],
3242 ],
3243 _('[-ec] [TOPIC]'))
3243 _('[-ec] [TOPIC]'))
3244 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
3244 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
3245 """show help for a given topic or a help overview
3245 """show help for a given topic or a help overview
3246
3246
3247 With no arguments, print a list of commands with short help messages.
3247 With no arguments, print a list of commands with short help messages.
3248
3248
3249 Given a topic, extension, or command name, print help for that
3249 Given a topic, extension, or command name, print help for that
3250 topic.
3250 topic.
3251
3251
3252 Returns 0 if successful.
3252 Returns 0 if successful.
3253 """
3253 """
3254
3254
3255 textwidth = min(ui.termwidth(), 80) - 2
3255 textwidth = min(ui.termwidth(), 80) - 2
3256
3256
3257 def helpcmd(name):
3257 def helpcmd(name):
3258 try:
3258 try:
3259 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3259 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3260 except error.AmbiguousCommand, inst:
3260 except error.AmbiguousCommand, inst:
3261 # py3k fix: except vars can't be used outside the scope of the
3261 # py3k fix: except vars can't be used outside the scope of the
3262 # except block, nor can be used inside a lambda. python issue4617
3262 # except block, nor can be used inside a lambda. python issue4617
3263 prefix = inst.args[0]
3263 prefix = inst.args[0]
3264 select = lambda c: c.lstrip('^').startswith(prefix)
3264 select = lambda c: c.lstrip('^').startswith(prefix)
3265 rst = helplist(select)
3265 rst = helplist(select)
3266 return rst
3266 return rst
3267
3267
3268 rst = []
3268 rst = []
3269
3269
3270 # check if it's an invalid alias and display its error if it is
3270 # check if it's an invalid alias and display its error if it is
3271 if getattr(entry[0], 'badalias', False):
3271 if getattr(entry[0], 'badalias', False):
3272 if not unknowncmd:
3272 if not unknowncmd:
3273 ui.pushbuffer()
3273 ui.pushbuffer()
3274 entry[0](ui)
3274 entry[0](ui)
3275 rst.append(ui.popbuffer())
3275 rst.append(ui.popbuffer())
3276 return rst
3276 return rst
3277
3277
3278 # synopsis
3278 # synopsis
3279 if len(entry) > 2:
3279 if len(entry) > 2:
3280 if entry[2].startswith('hg'):
3280 if entry[2].startswith('hg'):
3281 rst.append("%s\n" % entry[2])
3281 rst.append("%s\n" % entry[2])
3282 else:
3282 else:
3283 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
3283 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
3284 else:
3284 else:
3285 rst.append('hg %s\n' % aliases[0])
3285 rst.append('hg %s\n' % aliases[0])
3286 # aliases
3286 # aliases
3287 if full and not ui.quiet and len(aliases) > 1:
3287 if full and not ui.quiet and len(aliases) > 1:
3288 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
3288 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
3289 rst.append('\n')
3289 rst.append('\n')
3290
3290
3291 # description
3291 # description
3292 doc = gettext(entry[0].__doc__)
3292 doc = gettext(entry[0].__doc__)
3293 if not doc:
3293 if not doc:
3294 doc = _("(no help text available)")
3294 doc = _("(no help text available)")
3295 if util.safehasattr(entry[0], 'definition'): # aliased command
3295 if util.safehasattr(entry[0], 'definition'): # aliased command
3296 if entry[0].definition.startswith('!'): # shell alias
3296 if entry[0].definition.startswith('!'): # shell alias
3297 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3297 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3298 else:
3298 else:
3299 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3299 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3300 doc = doc.splitlines(True)
3300 doc = doc.splitlines(True)
3301 if ui.quiet or not full:
3301 if ui.quiet or not full:
3302 rst.append(doc[0])
3302 rst.append(doc[0])
3303 else:
3303 else:
3304 rst.extend(doc)
3304 rst.extend(doc)
3305 rst.append('\n')
3305 rst.append('\n')
3306
3306
3307 # check if this command shadows a non-trivial (multi-line)
3307 # check if this command shadows a non-trivial (multi-line)
3308 # extension help text
3308 # extension help text
3309 try:
3309 try:
3310 mod = extensions.find(name)
3310 mod = extensions.find(name)
3311 doc = gettext(mod.__doc__) or ''
3311 doc = gettext(mod.__doc__) or ''
3312 if '\n' in doc.strip():
3312 if '\n' in doc.strip():
3313 msg = _('use "hg help -e %s" to show help for '
3313 msg = _('use "hg help -e %s" to show help for '
3314 'the %s extension') % (name, name)
3314 'the %s extension') % (name, name)
3315 rst.append('\n%s\n' % msg)
3315 rst.append('\n%s\n' % msg)
3316 except KeyError:
3316 except KeyError:
3317 pass
3317 pass
3318
3318
3319 # options
3319 # options
3320 if not ui.quiet and entry[1]:
3320 if not ui.quiet and entry[1]:
3321 rst.append('\n%s\n\n' % _("options:"))
3321 rst.append('\n%s\n\n' % _("options:"))
3322 rst.append(help.optrst(entry[1], ui.verbose))
3322 rst.append(help.optrst(entry[1], ui.verbose))
3323
3323
3324 if ui.verbose:
3324 if ui.verbose:
3325 rst.append('\n%s\n\n' % _("global options:"))
3325 rst.append('\n%s\n\n' % _("global options:"))
3326 rst.append(help.optrst(globalopts, ui.verbose))
3326 rst.append(help.optrst(globalopts, ui.verbose))
3327
3327
3328 if not ui.verbose:
3328 if not ui.verbose:
3329 if not full:
3329 if not full:
3330 rst.append(_('\nuse "hg help %s" to show the full help text\n')
3330 rst.append(_('\nuse "hg help %s" to show the full help text\n')
3331 % name)
3331 % name)
3332 elif not ui.quiet:
3332 elif not ui.quiet:
3333 omitted = _('use "hg -v help %s" to show more complete'
3333 omitted = _('use "hg -v help %s" to show more complete'
3334 ' help and the global options') % name
3334 ' help and the global options') % name
3335 notomitted = _('use "hg -v help %s" to show'
3335 notomitted = _('use "hg -v help %s" to show'
3336 ' the global options') % name
3336 ' the global options') % name
3337 help.indicateomitted(rst, omitted, notomitted)
3337 help.indicateomitted(rst, omitted, notomitted)
3338
3338
3339 return rst
3339 return rst
3340
3340
3341
3341
3342 def helplist(select=None):
3342 def helplist(select=None):
3343 # list of commands
3343 # list of commands
3344 if name == "shortlist":
3344 if name == "shortlist":
3345 header = _('basic commands:\n\n')
3345 header = _('basic commands:\n\n')
3346 else:
3346 else:
3347 header = _('list of commands:\n\n')
3347 header = _('list of commands:\n\n')
3348
3348
3349 h = {}
3349 h = {}
3350 cmds = {}
3350 cmds = {}
3351 for c, e in table.iteritems():
3351 for c, e in table.iteritems():
3352 f = c.split("|", 1)[0]
3352 f = c.split("|", 1)[0]
3353 if select and not select(f):
3353 if select and not select(f):
3354 continue
3354 continue
3355 if (not select and name != 'shortlist' and
3355 if (not select and name != 'shortlist' and
3356 e[0].__module__ != __name__):
3356 e[0].__module__ != __name__):
3357 continue
3357 continue
3358 if name == "shortlist" and not f.startswith("^"):
3358 if name == "shortlist" and not f.startswith("^"):
3359 continue
3359 continue
3360 f = f.lstrip("^")
3360 f = f.lstrip("^")
3361 if not ui.debugflag and f.startswith("debug"):
3361 if not ui.debugflag and f.startswith("debug"):
3362 continue
3362 continue
3363 doc = e[0].__doc__
3363 doc = e[0].__doc__
3364 if doc and 'DEPRECATED' in doc and not ui.verbose:
3364 if doc and 'DEPRECATED' in doc and not ui.verbose:
3365 continue
3365 continue
3366 doc = gettext(doc)
3366 doc = gettext(doc)
3367 if not doc:
3367 if not doc:
3368 doc = _("(no help text available)")
3368 doc = _("(no help text available)")
3369 h[f] = doc.splitlines()[0].rstrip()
3369 h[f] = doc.splitlines()[0].rstrip()
3370 cmds[f] = c.lstrip("^")
3370 cmds[f] = c.lstrip("^")
3371
3371
3372 rst = []
3372 rst = []
3373 if not h:
3373 if not h:
3374 if not ui.quiet:
3374 if not ui.quiet:
3375 rst.append(_('no commands defined\n'))
3375 rst.append(_('no commands defined\n'))
3376 return rst
3376 return rst
3377
3377
3378 if not ui.quiet:
3378 if not ui.quiet:
3379 rst.append(header)
3379 rst.append(header)
3380 fns = sorted(h)
3380 fns = sorted(h)
3381 for f in fns:
3381 for f in fns:
3382 if ui.verbose:
3382 if ui.verbose:
3383 commands = cmds[f].replace("|",", ")
3383 commands = cmds[f].replace("|",", ")
3384 rst.append(" :%s: %s\n" % (commands, h[f]))
3384 rst.append(" :%s: %s\n" % (commands, h[f]))
3385 else:
3385 else:
3386 rst.append(' :%s: %s\n' % (f, h[f]))
3386 rst.append(' :%s: %s\n' % (f, h[f]))
3387
3387
3388 if not name:
3388 if not name:
3389 exts = help.listexts(_('enabled extensions:'), extensions.enabled())
3389 exts = help.listexts(_('enabled extensions:'), extensions.enabled())
3390 if exts:
3390 if exts:
3391 rst.append('\n')
3391 rst.append('\n')
3392 rst.extend(exts)
3392 rst.extend(exts)
3393
3393
3394 rst.append(_("\nadditional help topics:\n\n"))
3394 rst.append(_("\nadditional help topics:\n\n"))
3395 topics = []
3395 topics = []
3396 for names, header, doc in help.helptable:
3396 for names, header, doc in help.helptable:
3397 topics.append((names[0], header))
3397 topics.append((names[0], header))
3398 for t, desc in topics:
3398 for t, desc in topics:
3399 rst.append(" :%s: %s\n" % (t, desc))
3399 rst.append(" :%s: %s\n" % (t, desc))
3400
3400
3401 optlist = []
3401 optlist = []
3402 if not ui.quiet:
3402 if not ui.quiet:
3403 if ui.verbose:
3403 if ui.verbose:
3404 optlist.append((_("global options:"), globalopts))
3404 optlist.append((_("global options:"), globalopts))
3405 if name == 'shortlist':
3405 if name == 'shortlist':
3406 optlist.append((_('use "hg help" for the full list '
3406 optlist.append((_('use "hg help" for the full list '
3407 'of commands'), ()))
3407 'of commands'), ()))
3408 else:
3408 else:
3409 if name == 'shortlist':
3409 if name == 'shortlist':
3410 msg = _('use "hg help" for the full list of commands '
3410 msg = _('use "hg help" for the full list of commands '
3411 'or "hg -v" for details')
3411 'or "hg -v" for details')
3412 elif name and not full:
3412 elif name and not full:
3413 msg = _('use "hg help %s" to show the full help '
3413 msg = _('use "hg help %s" to show the full help '
3414 'text') % name
3414 'text') % name
3415 else:
3415 else:
3416 msg = _('use "hg -v help%s" to show builtin aliases and '
3416 msg = _('use "hg -v help%s" to show builtin aliases and '
3417 'global options') % (name and " " + name or "")
3417 'global options') % (name and " " + name or "")
3418 optlist.append((msg, ()))
3418 optlist.append((msg, ()))
3419
3419
3420 if optlist:
3420 if optlist:
3421 for title, options in optlist:
3421 for title, options in optlist:
3422 rst.append('\n%s\n' % title)
3422 rst.append('\n%s\n' % title)
3423 if options:
3423 if options:
3424 rst.append('\n%s\n' % help.optrst(options, ui.verbose))
3424 rst.append('\n%s\n' % help.optrst(options, ui.verbose))
3425 return rst
3425 return rst
3426
3426
3427 def helptopic(name):
3427 def helptopic(name):
3428 for names, header, doc in help.helptable:
3428 for names, header, doc in help.helptable:
3429 if name in names:
3429 if name in names:
3430 break
3430 break
3431 else:
3431 else:
3432 raise error.UnknownCommand(name)
3432 raise error.UnknownCommand(name)
3433
3433
3434 rst = ["%s\n\n" % header]
3434 rst = ["%s\n\n" % header]
3435 # description
3435 # description
3436 if not doc:
3436 if not doc:
3437 rst.append(" %s\n" % _("(no help text available)"))
3437 rst.append(" %s\n" % _("(no help text available)"))
3438 if util.safehasattr(doc, '__call__'):
3438 if util.safehasattr(doc, '__call__'):
3439 rst += [" %s\n" % l for l in doc().splitlines()]
3439 rst += [" %s\n" % l for l in doc().splitlines()]
3440
3440
3441 if not ui.verbose:
3441 if not ui.verbose:
3442 omitted = (_('use "hg help -v %s" to show more complete help') %
3442 omitted = (_('use "hg help -v %s" to show more complete help') %
3443 name)
3443 name)
3444 help.indicateomitted(rst, omitted)
3444 help.indicateomitted(rst, omitted)
3445
3445
3446 try:
3446 try:
3447 cmdutil.findcmd(name, table)
3447 cmdutil.findcmd(name, table)
3448 rst.append(_('\nuse "hg help -c %s" to see help for '
3448 rst.append(_('\nuse "hg help -c %s" to see help for '
3449 'the %s command\n') % (name, name))
3449 'the %s command\n') % (name, name))
3450 except error.UnknownCommand:
3450 except error.UnknownCommand:
3451 pass
3451 pass
3452 return rst
3452 return rst
3453
3453
3454 def helpext(name):
3454 def helpext(name):
3455 try:
3455 try:
3456 mod = extensions.find(name)
3456 mod = extensions.find(name)
3457 doc = gettext(mod.__doc__) or _('no help text available')
3457 doc = gettext(mod.__doc__) or _('no help text available')
3458 except KeyError:
3458 except KeyError:
3459 mod = None
3459 mod = None
3460 doc = extensions.disabledext(name)
3460 doc = extensions.disabledext(name)
3461 if not doc:
3461 if not doc:
3462 raise error.UnknownCommand(name)
3462 raise error.UnknownCommand(name)
3463
3463
3464 if '\n' not in doc:
3464 if '\n' not in doc:
3465 head, tail = doc, ""
3465 head, tail = doc, ""
3466 else:
3466 else:
3467 head, tail = doc.split('\n', 1)
3467 head, tail = doc.split('\n', 1)
3468 rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
3468 rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
3469 if tail:
3469 if tail:
3470 rst.extend(tail.splitlines(True))
3470 rst.extend(tail.splitlines(True))
3471 rst.append('\n')
3471 rst.append('\n')
3472
3472
3473 if not ui.verbose:
3473 if not ui.verbose:
3474 omitted = (_('use "hg help -v %s" to show more complete help') %
3474 omitted = (_('use "hg help -v %s" to show more complete help') %
3475 name)
3475 name)
3476 help.indicateomitted(rst, omitted)
3476 help.indicateomitted(rst, omitted)
3477
3477
3478 if mod:
3478 if mod:
3479 try:
3479 try:
3480 ct = mod.cmdtable
3480 ct = mod.cmdtable
3481 except AttributeError:
3481 except AttributeError:
3482 ct = {}
3482 ct = {}
3483 modcmds = set([c.split('|', 1)[0] for c in ct])
3483 modcmds = set([c.split('|', 1)[0] for c in ct])
3484 rst.extend(helplist(modcmds.__contains__))
3484 rst.extend(helplist(modcmds.__contains__))
3485 else:
3485 else:
3486 rst.append(_('use "hg help extensions" for information on enabling '
3486 rst.append(_('use "hg help extensions" for information on enabling '
3487 'extensions\n'))
3487 'extensions\n'))
3488 return rst
3488 return rst
3489
3489
3490 def helpextcmd(name):
3490 def helpextcmd(name):
3491 cmd, ext, mod = extensions.disabledcmd(ui, name,
3491 cmd, ext, mod = extensions.disabledcmd(ui, name,
3492 ui.configbool('ui', 'strict'))
3492 ui.configbool('ui', 'strict'))
3493 doc = gettext(mod.__doc__).splitlines()[0]
3493 doc = gettext(mod.__doc__).splitlines()[0]
3494
3494
3495 rst = help.listexts(_("'%s' is provided by the following "
3495 rst = help.listexts(_("'%s' is provided by the following "
3496 "extension:") % cmd, {ext: doc}, indent=4)
3496 "extension:") % cmd, {ext: doc}, indent=4)
3497 rst.append('\n')
3497 rst.append('\n')
3498 rst.append(_('use "hg help extensions" for information on enabling '
3498 rst.append(_('use "hg help extensions" for information on enabling '
3499 'extensions\n'))
3499 'extensions\n'))
3500 return rst
3500 return rst
3501
3501
3502
3502
3503 rst = []
3503 rst = []
3504 kw = opts.get('keyword')
3504 kw = opts.get('keyword')
3505 if kw:
3505 if kw:
3506 matches = help.topicmatch(kw)
3506 matches = help.topicmatch(kw)
3507 for t, title in (('topics', _('Topics')),
3507 for t, title in (('topics', _('Topics')),
3508 ('commands', _('Commands')),
3508 ('commands', _('Commands')),
3509 ('extensions', _('Extensions')),
3509 ('extensions', _('Extensions')),
3510 ('extensioncommands', _('Extension Commands'))):
3510 ('extensioncommands', _('Extension Commands'))):
3511 if matches[t]:
3511 if matches[t]:
3512 rst.append('%s:\n\n' % title)
3512 rst.append('%s:\n\n' % title)
3513 rst.extend(minirst.maketable(sorted(matches[t]), 1))
3513 rst.extend(minirst.maketable(sorted(matches[t]), 1))
3514 rst.append('\n')
3514 rst.append('\n')
3515 elif name and name != 'shortlist':
3515 elif name and name != 'shortlist':
3516 i = None
3516 i = None
3517 if unknowncmd:
3517 if unknowncmd:
3518 queries = (helpextcmd,)
3518 queries = (helpextcmd,)
3519 elif opts.get('extension'):
3519 elif opts.get('extension'):
3520 queries = (helpext,)
3520 queries = (helpext,)
3521 elif opts.get('command'):
3521 elif opts.get('command'):
3522 queries = (helpcmd,)
3522 queries = (helpcmd,)
3523 else:
3523 else:
3524 queries = (helptopic, helpcmd, helpext, helpextcmd)
3524 queries = (helptopic, helpcmd, helpext, helpextcmd)
3525 for f in queries:
3525 for f in queries:
3526 try:
3526 try:
3527 rst = f(name)
3527 rst = f(name)
3528 i = None
3528 i = None
3529 break
3529 break
3530 except error.UnknownCommand, inst:
3530 except error.UnknownCommand, inst:
3531 i = inst
3531 i = inst
3532 if i:
3532 if i:
3533 raise i
3533 raise i
3534 else:
3534 else:
3535 # program name
3535 # program name
3536 if not ui.quiet:
3536 if not ui.quiet:
3537 rst = [_("Mercurial Distributed SCM\n"), '\n']
3537 rst = [_("Mercurial Distributed SCM\n"), '\n']
3538 rst.extend(helplist())
3538 rst.extend(helplist())
3539
3539
3540 keep = ui.verbose and ['verbose'] or []
3540 keep = ui.verbose and ['verbose'] or []
3541 text = ''.join(rst)
3541 text = ''.join(rst)
3542 formatted, pruned = minirst.format(text, textwidth, keep=keep)
3542 formatted, pruned = minirst.format(text, textwidth, keep=keep)
3543 if 'verbose' in pruned:
3543 if 'verbose' in pruned:
3544 keep.append('omitted')
3544 keep.append('omitted')
3545 else:
3545 else:
3546 keep.append('notomitted')
3546 keep.append('notomitted')
3547 formatted, pruned = minirst.format(text, textwidth, keep=keep)
3547 formatted, pruned = minirst.format(text, textwidth, keep=keep)
3548 ui.write(formatted)
3548 ui.write(formatted)
3549
3549
3550
3550
3551 @command('identify|id',
3551 @command('identify|id',
3552 [('r', 'rev', '',
3552 [('r', 'rev', '',
3553 _('identify the specified revision'), _('REV')),
3553 _('identify the specified revision'), _('REV')),
3554 ('n', 'num', None, _('show local revision number')),
3554 ('n', 'num', None, _('show local revision number')),
3555 ('i', 'id', None, _('show global revision id')),
3555 ('i', 'id', None, _('show global revision id')),
3556 ('b', 'branch', None, _('show branch')),
3556 ('b', 'branch', None, _('show branch')),
3557 ('t', 'tags', None, _('show tags')),
3557 ('t', 'tags', None, _('show tags')),
3558 ('B', 'bookmarks', None, _('show bookmarks')),
3558 ('B', 'bookmarks', None, _('show bookmarks')),
3559 ] + remoteopts,
3559 ] + remoteopts,
3560 _('[-nibtB] [-r REV] [SOURCE]'))
3560 _('[-nibtB] [-r REV] [SOURCE]'))
3561 def identify(ui, repo, source=None, rev=None,
3561 def identify(ui, repo, source=None, rev=None,
3562 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3562 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3563 """identify the working copy or specified revision
3563 """identify the working copy or specified revision
3564
3564
3565 Print a summary identifying the repository state at REV using one or
3565 Print a summary identifying the repository state at REV using one or
3566 two parent hash identifiers, followed by a "+" if the working
3566 two parent hash identifiers, followed by a "+" if the working
3567 directory has uncommitted changes, the branch name (if not default),
3567 directory has uncommitted changes, the branch name (if not default),
3568 a list of tags, and a list of bookmarks.
3568 a list of tags, and a list of bookmarks.
3569
3569
3570 When REV is not given, print a summary of the current state of the
3570 When REV is not given, print a summary of the current state of the
3571 repository.
3571 repository.
3572
3572
3573 Specifying a path to a repository root or Mercurial bundle will
3573 Specifying a path to a repository root or Mercurial bundle will
3574 cause lookup to operate on that repository/bundle.
3574 cause lookup to operate on that repository/bundle.
3575
3575
3576 .. container:: verbose
3576 .. container:: verbose
3577
3577
3578 Examples:
3578 Examples:
3579
3579
3580 - generate a build identifier for the working directory::
3580 - generate a build identifier for the working directory::
3581
3581
3582 hg id --id > build-id.dat
3582 hg id --id > build-id.dat
3583
3583
3584 - find the revision corresponding to a tag::
3584 - find the revision corresponding to a tag::
3585
3585
3586 hg id -n -r 1.3
3586 hg id -n -r 1.3
3587
3587
3588 - check the most recent revision of a remote repository::
3588 - check the most recent revision of a remote repository::
3589
3589
3590 hg id -r tip http://selenic.com/hg/
3590 hg id -r tip http://selenic.com/hg/
3591
3591
3592 Returns 0 if successful.
3592 Returns 0 if successful.
3593 """
3593 """
3594
3594
3595 if not repo and not source:
3595 if not repo and not source:
3596 raise util.Abort(_("there is no Mercurial repository here "
3596 raise util.Abort(_("there is no Mercurial repository here "
3597 "(.hg not found)"))
3597 "(.hg not found)"))
3598
3598
3599 hexfunc = ui.debugflag and hex or short
3599 hexfunc = ui.debugflag and hex or short
3600 default = not (num or id or branch or tags or bookmarks)
3600 default = not (num or id or branch or tags or bookmarks)
3601 output = []
3601 output = []
3602 revs = []
3602 revs = []
3603
3603
3604 if source:
3604 if source:
3605 source, branches = hg.parseurl(ui.expandpath(source))
3605 source, branches = hg.parseurl(ui.expandpath(source))
3606 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3606 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3607 repo = peer.local()
3607 repo = peer.local()
3608 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3608 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3609
3609
3610 if not repo:
3610 if not repo:
3611 if num or branch or tags:
3611 if num or branch or tags:
3612 raise util.Abort(
3612 raise util.Abort(
3613 _("can't query remote revision number, branch, or tags"))
3613 _("can't query remote revision number, branch, or tags"))
3614 if not rev and revs:
3614 if not rev and revs:
3615 rev = revs[0]
3615 rev = revs[0]
3616 if not rev:
3616 if not rev:
3617 rev = "tip"
3617 rev = "tip"
3618
3618
3619 remoterev = peer.lookup(rev)
3619 remoterev = peer.lookup(rev)
3620 if default or id:
3620 if default or id:
3621 output = [hexfunc(remoterev)]
3621 output = [hexfunc(remoterev)]
3622
3622
3623 def getbms():
3623 def getbms():
3624 bms = []
3624 bms = []
3625
3625
3626 if 'bookmarks' in peer.listkeys('namespaces'):
3626 if 'bookmarks' in peer.listkeys('namespaces'):
3627 hexremoterev = hex(remoterev)
3627 hexremoterev = hex(remoterev)
3628 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3628 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3629 if bmr == hexremoterev]
3629 if bmr == hexremoterev]
3630
3630
3631 return bms
3631 return bms
3632
3632
3633 if bookmarks:
3633 if bookmarks:
3634 output.extend(getbms())
3634 output.extend(getbms())
3635 elif default and not ui.quiet:
3635 elif default and not ui.quiet:
3636 # multiple bookmarks for a single parent separated by '/'
3636 # multiple bookmarks for a single parent separated by '/'
3637 bm = '/'.join(getbms())
3637 bm = '/'.join(getbms())
3638 if bm:
3638 if bm:
3639 output.append(bm)
3639 output.append(bm)
3640 else:
3640 else:
3641 if not rev:
3641 if not rev:
3642 ctx = repo[None]
3642 ctx = repo[None]
3643 parents = ctx.parents()
3643 parents = ctx.parents()
3644 changed = ""
3644 changed = ""
3645 if default or id or num:
3645 if default or id or num:
3646 if (util.any(repo.status())
3646 if (util.any(repo.status())
3647 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3647 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3648 changed = '+'
3648 changed = '+'
3649 if default or id:
3649 if default or id:
3650 output = ["%s%s" %
3650 output = ["%s%s" %
3651 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3651 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3652 if num:
3652 if num:
3653 output.append("%s%s" %
3653 output.append("%s%s" %
3654 ('+'.join([str(p.rev()) for p in parents]), changed))
3654 ('+'.join([str(p.rev()) for p in parents]), changed))
3655 else:
3655 else:
3656 ctx = scmutil.revsingle(repo, rev)
3656 ctx = scmutil.revsingle(repo, rev)
3657 if default or id:
3657 if default or id:
3658 output = [hexfunc(ctx.node())]
3658 output = [hexfunc(ctx.node())]
3659 if num:
3659 if num:
3660 output.append(str(ctx.rev()))
3660 output.append(str(ctx.rev()))
3661
3661
3662 if default and not ui.quiet:
3662 if default and not ui.quiet:
3663 b = ctx.branch()
3663 b = ctx.branch()
3664 if b != 'default':
3664 if b != 'default':
3665 output.append("(%s)" % b)
3665 output.append("(%s)" % b)
3666
3666
3667 # multiple tags for a single parent separated by '/'
3667 # multiple tags for a single parent separated by '/'
3668 t = '/'.join(ctx.tags())
3668 t = '/'.join(ctx.tags())
3669 if t:
3669 if t:
3670 output.append(t)
3670 output.append(t)
3671
3671
3672 # multiple bookmarks for a single parent separated by '/'
3672 # multiple bookmarks for a single parent separated by '/'
3673 bm = '/'.join(ctx.bookmarks())
3673 bm = '/'.join(ctx.bookmarks())
3674 if bm:
3674 if bm:
3675 output.append(bm)
3675 output.append(bm)
3676 else:
3676 else:
3677 if branch:
3677 if branch:
3678 output.append(ctx.branch())
3678 output.append(ctx.branch())
3679
3679
3680 if tags:
3680 if tags:
3681 output.extend(ctx.tags())
3681 output.extend(ctx.tags())
3682
3682
3683 if bookmarks:
3683 if bookmarks:
3684 output.extend(ctx.bookmarks())
3684 output.extend(ctx.bookmarks())
3685
3685
3686 ui.write("%s\n" % ' '.join(output))
3686 ui.write("%s\n" % ' '.join(output))
3687
3687
3688 @command('import|patch',
3688 @command('import|patch',
3689 [('p', 'strip', 1,
3689 [('p', 'strip', 1,
3690 _('directory strip option for patch. This has the same '
3690 _('directory strip option for patch. This has the same '
3691 'meaning as the corresponding patch option'), _('NUM')),
3691 'meaning as the corresponding patch option'), _('NUM')),
3692 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3692 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3693 ('e', 'edit', False, _('invoke editor on commit messages')),
3693 ('e', 'edit', False, _('invoke editor on commit messages')),
3694 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3694 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3695 ('', 'no-commit', None,
3695 ('', 'no-commit', None,
3696 _("don't commit, just update the working directory")),
3696 _("don't commit, just update the working directory")),
3697 ('', 'bypass', None,
3697 ('', 'bypass', None,
3698 _("apply patch without touching the working directory")),
3698 _("apply patch without touching the working directory")),
3699 ('', 'exact', None,
3699 ('', 'exact', None,
3700 _('apply patch to the nodes from which it was generated')),
3700 _('apply patch to the nodes from which it was generated')),
3701 ('', 'import-branch', None,
3701 ('', 'import-branch', None,
3702 _('use any branch information in patch (implied by --exact)'))] +
3702 _('use any branch information in patch (implied by --exact)'))] +
3703 commitopts + commitopts2 + similarityopts,
3703 commitopts + commitopts2 + similarityopts,
3704 _('[OPTION]... PATCH...'))
3704 _('[OPTION]... PATCH...'))
3705 def import_(ui, repo, patch1=None, *patches, **opts):
3705 def import_(ui, repo, patch1=None, *patches, **opts):
3706 """import an ordered set of patches
3706 """import an ordered set of patches
3707
3707
3708 Import a list of patches and commit them individually (unless
3708 Import a list of patches and commit them individually (unless
3709 --no-commit is specified).
3709 --no-commit is specified).
3710
3710
3711 If there are outstanding changes in the working directory, import
3711 If there are outstanding changes in the working directory, import
3712 will abort unless given the -f/--force flag.
3712 will abort unless given the -f/--force flag.
3713
3713
3714 You can import a patch straight from a mail message. Even patches
3714 You can import a patch straight from a mail message. Even patches
3715 as attachments work (to use the body part, it must have type
3715 as attachments work (to use the body part, it must have type
3716 text/plain or text/x-patch). From and Subject headers of email
3716 text/plain or text/x-patch). From and Subject headers of email
3717 message are used as default committer and commit message. All
3717 message are used as default committer and commit message. All
3718 text/plain body parts before first diff are added to commit
3718 text/plain body parts before first diff are added to commit
3719 message.
3719 message.
3720
3720
3721 If the imported patch was generated by :hg:`export`, user and
3721 If the imported patch was generated by :hg:`export`, user and
3722 description from patch override values from message headers and
3722 description from patch override values from message headers and
3723 body. Values given on command line with -m/--message and -u/--user
3723 body. Values given on command line with -m/--message and -u/--user
3724 override these.
3724 override these.
3725
3725
3726 If --exact is specified, import will set the working directory to
3726 If --exact is specified, import will set the working directory to
3727 the parent of each patch before applying it, and will abort if the
3727 the parent of each patch before applying it, and will abort if the
3728 resulting changeset has a different ID than the one recorded in
3728 resulting changeset has a different ID than the one recorded in
3729 the patch. This may happen due to character set problems or other
3729 the patch. This may happen due to character set problems or other
3730 deficiencies in the text patch format.
3730 deficiencies in the text patch format.
3731
3731
3732 Use --bypass to apply and commit patches directly to the
3732 Use --bypass to apply and commit patches directly to the
3733 repository, not touching the working directory. Without --exact,
3733 repository, not touching the working directory. Without --exact,
3734 patches will be applied on top of the working directory parent
3734 patches will be applied on top of the working directory parent
3735 revision.
3735 revision.
3736
3736
3737 With -s/--similarity, hg will attempt to discover renames and
3737 With -s/--similarity, hg will attempt to discover renames and
3738 copies in the patch in the same way as :hg:`addremove`.
3738 copies in the patch in the same way as :hg:`addremove`.
3739
3739
3740 To read a patch from standard input, use "-" as the patch name. If
3740 To read a patch from standard input, use "-" as the patch name. If
3741 a URL is specified, the patch will be downloaded from it.
3741 a URL is specified, the patch will be downloaded from it.
3742 See :hg:`help dates` for a list of formats valid for -d/--date.
3742 See :hg:`help dates` for a list of formats valid for -d/--date.
3743
3743
3744 .. container:: verbose
3744 .. container:: verbose
3745
3745
3746 Examples:
3746 Examples:
3747
3747
3748 - import a traditional patch from a website and detect renames::
3748 - import a traditional patch from a website and detect renames::
3749
3749
3750 hg import -s 80 http://example.com/bugfix.patch
3750 hg import -s 80 http://example.com/bugfix.patch
3751
3751
3752 - import a changeset from an hgweb server::
3752 - import a changeset from an hgweb server::
3753
3753
3754 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3754 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3755
3755
3756 - import all the patches in an Unix-style mbox::
3756 - import all the patches in an Unix-style mbox::
3757
3757
3758 hg import incoming-patches.mbox
3758 hg import incoming-patches.mbox
3759
3759
3760 - attempt to exactly restore an exported changeset (not always
3760 - attempt to exactly restore an exported changeset (not always
3761 possible)::
3761 possible)::
3762
3762
3763 hg import --exact proposed-fix.patch
3763 hg import --exact proposed-fix.patch
3764
3764
3765 Returns 0 on success.
3765 Returns 0 on success.
3766 """
3766 """
3767
3767
3768 if not patch1:
3768 if not patch1:
3769 raise util.Abort(_('need at least one patch to import'))
3769 raise util.Abort(_('need at least one patch to import'))
3770
3770
3771 patches = (patch1,) + patches
3771 patches = (patch1,) + patches
3772
3772
3773 date = opts.get('date')
3773 date = opts.get('date')
3774 if date:
3774 if date:
3775 opts['date'] = util.parsedate(date)
3775 opts['date'] = util.parsedate(date)
3776
3776
3777 editor = cmdutil.commiteditor
3777 editor = cmdutil.commiteditor
3778 if opts.get('edit'):
3778 if opts.get('edit'):
3779 editor = cmdutil.commitforceeditor
3779 editor = cmdutil.commitforceeditor
3780
3780
3781 update = not opts.get('bypass')
3781 update = not opts.get('bypass')
3782 if not update and opts.get('no_commit'):
3782 if not update and opts.get('no_commit'):
3783 raise util.Abort(_('cannot use --no-commit with --bypass'))
3783 raise util.Abort(_('cannot use --no-commit with --bypass'))
3784 try:
3784 try:
3785 sim = float(opts.get('similarity') or 0)
3785 sim = float(opts.get('similarity') or 0)
3786 except ValueError:
3786 except ValueError:
3787 raise util.Abort(_('similarity must be a number'))
3787 raise util.Abort(_('similarity must be a number'))
3788 if sim < 0 or sim > 100:
3788 if sim < 0 or sim > 100:
3789 raise util.Abort(_('similarity must be between 0 and 100'))
3789 raise util.Abort(_('similarity must be between 0 and 100'))
3790 if sim and not update:
3790 if sim and not update:
3791 raise util.Abort(_('cannot use --similarity with --bypass'))
3791 raise util.Abort(_('cannot use --similarity with --bypass'))
3792
3792
3793 if (opts.get('exact') or not opts.get('force')) and update:
3793 if (opts.get('exact') or not opts.get('force')) and update:
3794 cmdutil.bailifchanged(repo)
3794 cmdutil.bailifchanged(repo)
3795
3795
3796 base = opts["base"]
3796 base = opts["base"]
3797 strip = opts["strip"]
3797 strip = opts["strip"]
3798 wlock = lock = tr = None
3798 wlock = lock = tr = None
3799 msgs = []
3799 msgs = []
3800
3800
3801 def checkexact(repo, n, nodeid):
3801 def checkexact(repo, n, nodeid):
3802 if opts.get('exact') and hex(n) != nodeid:
3802 if opts.get('exact') and hex(n) != nodeid:
3803 repo.rollback()
3803 repo.rollback()
3804 raise util.Abort(_('patch is damaged or loses information'))
3804 raise util.Abort(_('patch is damaged or loses information'))
3805
3805
3806 def tryone(ui, hunk, parents):
3806 def tryone(ui, hunk, parents):
3807 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3807 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3808 patch.extract(ui, hunk)
3808 patch.extract(ui, hunk)
3809
3809
3810 if not tmpname:
3810 if not tmpname:
3811 return (None, None)
3811 return (None, None)
3812 msg = _('applied to working directory')
3812 msg = _('applied to working directory')
3813
3813
3814 try:
3814 try:
3815 cmdline_message = cmdutil.logmessage(ui, opts)
3815 cmdline_message = cmdutil.logmessage(ui, opts)
3816 if cmdline_message:
3816 if cmdline_message:
3817 # pickup the cmdline msg
3817 # pickup the cmdline msg
3818 message = cmdline_message
3818 message = cmdline_message
3819 elif message:
3819 elif message:
3820 # pickup the patch msg
3820 # pickup the patch msg
3821 message = message.strip()
3821 message = message.strip()
3822 else:
3822 else:
3823 # launch the editor
3823 # launch the editor
3824 message = None
3824 message = None
3825 ui.debug('message:\n%s\n' % message)
3825 ui.debug('message:\n%s\n' % message)
3826
3826
3827 if len(parents) == 1:
3827 if len(parents) == 1:
3828 parents.append(repo[nullid])
3828 parents.append(repo[nullid])
3829 if opts.get('exact'):
3829 if opts.get('exact'):
3830 if not nodeid or not p1:
3830 if not nodeid or not p1:
3831 raise util.Abort(_('not a Mercurial patch'))
3831 raise util.Abort(_('not a Mercurial patch'))
3832 p1 = repo[p1]
3832 p1 = repo[p1]
3833 p2 = repo[p2 or nullid]
3833 p2 = repo[p2 or nullid]
3834 elif p2:
3834 elif p2:
3835 try:
3835 try:
3836 p1 = repo[p1]
3836 p1 = repo[p1]
3837 p2 = repo[p2]
3837 p2 = repo[p2]
3838 # Without any options, consider p2 only if the
3838 # Without any options, consider p2 only if the
3839 # patch is being applied on top of the recorded
3839 # patch is being applied on top of the recorded
3840 # first parent.
3840 # first parent.
3841 if p1 != parents[0]:
3841 if p1 != parents[0]:
3842 p1 = parents[0]
3842 p1 = parents[0]
3843 p2 = repo[nullid]
3843 p2 = repo[nullid]
3844 except error.RepoError:
3844 except error.RepoError:
3845 p1, p2 = parents
3845 p1, p2 = parents
3846 else:
3846 else:
3847 p1, p2 = parents
3847 p1, p2 = parents
3848
3848
3849 n = None
3849 n = None
3850 if update:
3850 if update:
3851 if p1 != parents[0]:
3851 if p1 != parents[0]:
3852 hg.clean(repo, p1.node())
3852 hg.clean(repo, p1.node())
3853 if p2 != parents[1]:
3853 if p2 != parents[1]:
3854 repo.setparents(p1.node(), p2.node())
3854 repo.setparents(p1.node(), p2.node())
3855
3855
3856 if opts.get('exact') or opts.get('import_branch'):
3856 if opts.get('exact') or opts.get('import_branch'):
3857 repo.dirstate.setbranch(branch or 'default')
3857 repo.dirstate.setbranch(branch or 'default')
3858
3858
3859 files = set()
3859 files = set()
3860 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3860 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3861 eolmode=None, similarity=sim / 100.0)
3861 eolmode=None, similarity=sim / 100.0)
3862 files = list(files)
3862 files = list(files)
3863 if opts.get('no_commit'):
3863 if opts.get('no_commit'):
3864 if message:
3864 if message:
3865 msgs.append(message)
3865 msgs.append(message)
3866 else:
3866 else:
3867 if opts.get('exact') or p2:
3867 if opts.get('exact') or p2:
3868 # If you got here, you either use --force and know what
3868 # If you got here, you either use --force and know what
3869 # you are doing or used --exact or a merge patch while
3869 # you are doing or used --exact or a merge patch while
3870 # being updated to its first parent.
3870 # being updated to its first parent.
3871 m = None
3871 m = None
3872 else:
3872 else:
3873 m = scmutil.matchfiles(repo, files or [])
3873 m = scmutil.matchfiles(repo, files or [])
3874 n = repo.commit(message, opts.get('user') or user,
3874 n = repo.commit(message, opts.get('user') or user,
3875 opts.get('date') or date, match=m,
3875 opts.get('date') or date, match=m,
3876 editor=editor)
3876 editor=editor)
3877 checkexact(repo, n, nodeid)
3877 checkexact(repo, n, nodeid)
3878 else:
3878 else:
3879 if opts.get('exact') or opts.get('import_branch'):
3879 if opts.get('exact') or opts.get('import_branch'):
3880 branch = branch or 'default'
3880 branch = branch or 'default'
3881 else:
3881 else:
3882 branch = p1.branch()
3882 branch = p1.branch()
3883 store = patch.filestore()
3883 store = patch.filestore()
3884 try:
3884 try:
3885 files = set()
3885 files = set()
3886 try:
3886 try:
3887 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3887 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3888 files, eolmode=None)
3888 files, eolmode=None)
3889 except patch.PatchError, e:
3889 except patch.PatchError, e:
3890 raise util.Abort(str(e))
3890 raise util.Abort(str(e))
3891 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3891 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3892 message,
3892 message,
3893 opts.get('user') or user,
3893 opts.get('user') or user,
3894 opts.get('date') or date,
3894 opts.get('date') or date,
3895 branch, files, store,
3895 branch, files, store,
3896 editor=cmdutil.commiteditor)
3896 editor=cmdutil.commiteditor)
3897 repo.savecommitmessage(memctx.description())
3897 repo.savecommitmessage(memctx.description())
3898 n = memctx.commit()
3898 n = memctx.commit()
3899 checkexact(repo, n, nodeid)
3899 checkexact(repo, n, nodeid)
3900 finally:
3900 finally:
3901 store.close()
3901 store.close()
3902 if n:
3902 if n:
3903 # i18n: refers to a short changeset id
3903 # i18n: refers to a short changeset id
3904 msg = _('created %s') % short(n)
3904 msg = _('created %s') % short(n)
3905 return (msg, n)
3905 return (msg, n)
3906 finally:
3906 finally:
3907 os.unlink(tmpname)
3907 os.unlink(tmpname)
3908
3908
3909 try:
3909 try:
3910 try:
3910 try:
3911 wlock = repo.wlock()
3911 wlock = repo.wlock()
3912 if not opts.get('no_commit'):
3912 if not opts.get('no_commit'):
3913 lock = repo.lock()
3913 lock = repo.lock()
3914 tr = repo.transaction('import')
3914 tr = repo.transaction('import')
3915 parents = repo.parents()
3915 parents = repo.parents()
3916 for patchurl in patches:
3916 for patchurl in patches:
3917 if patchurl == '-':
3917 if patchurl == '-':
3918 ui.status(_('applying patch from stdin\n'))
3918 ui.status(_('applying patch from stdin\n'))
3919 patchfile = ui.fin
3919 patchfile = ui.fin
3920 patchurl = 'stdin' # for error message
3920 patchurl = 'stdin' # for error message
3921 else:
3921 else:
3922 patchurl = os.path.join(base, patchurl)
3922 patchurl = os.path.join(base, patchurl)
3923 ui.status(_('applying %s\n') % patchurl)
3923 ui.status(_('applying %s\n') % patchurl)
3924 patchfile = hg.openpath(ui, patchurl)
3924 patchfile = hg.openpath(ui, patchurl)
3925
3925
3926 haspatch = False
3926 haspatch = False
3927 for hunk in patch.split(patchfile):
3927 for hunk in patch.split(patchfile):
3928 (msg, node) = tryone(ui, hunk, parents)
3928 (msg, node) = tryone(ui, hunk, parents)
3929 if msg:
3929 if msg:
3930 haspatch = True
3930 haspatch = True
3931 ui.note(msg + '\n')
3931 ui.note(msg + '\n')
3932 if update or opts.get('exact'):
3932 if update or opts.get('exact'):
3933 parents = repo.parents()
3933 parents = repo.parents()
3934 else:
3934 else:
3935 parents = [repo[node]]
3935 parents = [repo[node]]
3936
3936
3937 if not haspatch:
3937 if not haspatch:
3938 raise util.Abort(_('%s: no diffs found') % patchurl)
3938 raise util.Abort(_('%s: no diffs found') % patchurl)
3939
3939
3940 if tr:
3940 if tr:
3941 tr.close()
3941 tr.close()
3942 if msgs:
3942 if msgs:
3943 repo.savecommitmessage('\n* * *\n'.join(msgs))
3943 repo.savecommitmessage('\n* * *\n'.join(msgs))
3944 except: # re-raises
3944 except: # re-raises
3945 # wlock.release() indirectly calls dirstate.write(): since
3945 # wlock.release() indirectly calls dirstate.write(): since
3946 # we're crashing, we do not want to change the working dir
3946 # we're crashing, we do not want to change the working dir
3947 # parent after all, so make sure it writes nothing
3947 # parent after all, so make sure it writes nothing
3948 repo.dirstate.invalidate()
3948 repo.dirstate.invalidate()
3949 raise
3949 raise
3950 finally:
3950 finally:
3951 if tr:
3951 if tr:
3952 tr.release()
3952 tr.release()
3953 release(lock, wlock)
3953 release(lock, wlock)
3954
3954
3955 @command('incoming|in',
3955 @command('incoming|in',
3956 [('f', 'force', None,
3956 [('f', 'force', None,
3957 _('run even if remote repository is unrelated')),
3957 _('run even if remote repository is unrelated')),
3958 ('n', 'newest-first', None, _('show newest record first')),
3958 ('n', 'newest-first', None, _('show newest record first')),
3959 ('', 'bundle', '',
3959 ('', 'bundle', '',
3960 _('file to store the bundles into'), _('FILE')),
3960 _('file to store the bundles into'), _('FILE')),
3961 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3961 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3962 ('B', 'bookmarks', False, _("compare bookmarks")),
3962 ('B', 'bookmarks', False, _("compare bookmarks")),
3963 ('b', 'branch', [],
3963 ('b', 'branch', [],
3964 _('a specific branch you would like to pull'), _('BRANCH')),
3964 _('a specific branch you would like to pull'), _('BRANCH')),
3965 ] + logopts + remoteopts + subrepoopts,
3965 ] + logopts + remoteopts + subrepoopts,
3966 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3966 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3967 def incoming(ui, repo, source="default", **opts):
3967 def incoming(ui, repo, source="default", **opts):
3968 """show new changesets found in source
3968 """show new changesets found in source
3969
3969
3970 Show new changesets found in the specified path/URL or the default
3970 Show new changesets found in the specified path/URL or the default
3971 pull location. These are the changesets that would have been pulled
3971 pull location. These are the changesets that would have been pulled
3972 if a pull at the time you issued this command.
3972 if a pull at the time you issued this command.
3973
3973
3974 For remote repository, using --bundle avoids downloading the
3974 For remote repository, using --bundle avoids downloading the
3975 changesets twice if the incoming is followed by a pull.
3975 changesets twice if the incoming is followed by a pull.
3976
3976
3977 See pull for valid source format details.
3977 See pull for valid source format details.
3978
3978
3979 Returns 0 if there are incoming changes, 1 otherwise.
3979 Returns 0 if there are incoming changes, 1 otherwise.
3980 """
3980 """
3981 if opts.get('graph'):
3981 if opts.get('graph'):
3982 cmdutil.checkunsupportedgraphflags([], opts)
3982 cmdutil.checkunsupportedgraphflags([], opts)
3983 def display(other, chlist, displayer):
3983 def display(other, chlist, displayer):
3984 revdag = cmdutil.graphrevs(other, chlist, opts)
3984 revdag = cmdutil.graphrevs(other, chlist, opts)
3985 showparents = [ctx.node() for ctx in repo[None].parents()]
3985 showparents = [ctx.node() for ctx in repo[None].parents()]
3986 cmdutil.displaygraph(ui, revdag, displayer, showparents,
3986 cmdutil.displaygraph(ui, revdag, displayer, showparents,
3987 graphmod.asciiedges)
3987 graphmod.asciiedges)
3988
3988
3989 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3989 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3990 return 0
3990 return 0
3991
3991
3992 if opts.get('bundle') and opts.get('subrepos'):
3992 if opts.get('bundle') and opts.get('subrepos'):
3993 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3993 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3994
3994
3995 if opts.get('bookmarks'):
3995 if opts.get('bookmarks'):
3996 source, branches = hg.parseurl(ui.expandpath(source),
3996 source, branches = hg.parseurl(ui.expandpath(source),
3997 opts.get('branch'))
3997 opts.get('branch'))
3998 other = hg.peer(repo, opts, source)
3998 other = hg.peer(repo, opts, source)
3999 if 'bookmarks' not in other.listkeys('namespaces'):
3999 if 'bookmarks' not in other.listkeys('namespaces'):
4000 ui.warn(_("remote doesn't support bookmarks\n"))
4000 ui.warn(_("remote doesn't support bookmarks\n"))
4001 return 0
4001 return 0
4002 ui.status(_('comparing with %s\n') % util.hidepassword(source))
4002 ui.status(_('comparing with %s\n') % util.hidepassword(source))
4003 return bookmarks.diff(ui, repo, other)
4003 return bookmarks.diff(ui, repo, other)
4004
4004
4005 repo._subtoppath = ui.expandpath(source)
4005 repo._subtoppath = ui.expandpath(source)
4006 try:
4006 try:
4007 return hg.incoming(ui, repo, source, opts)
4007 return hg.incoming(ui, repo, source, opts)
4008 finally:
4008 finally:
4009 del repo._subtoppath
4009 del repo._subtoppath
4010
4010
4011
4011
4012 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
4012 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
4013 def init(ui, dest=".", **opts):
4013 def init(ui, dest=".", **opts):
4014 """create a new repository in the given directory
4014 """create a new repository in the given directory
4015
4015
4016 Initialize a new repository in the given directory. If the given
4016 Initialize a new repository in the given directory. If the given
4017 directory does not exist, it will be created.
4017 directory does not exist, it will be created.
4018
4018
4019 If no directory is given, the current directory is used.
4019 If no directory is given, the current directory is used.
4020
4020
4021 It is possible to specify an ``ssh://`` URL as the destination.
4021 It is possible to specify an ``ssh://`` URL as the destination.
4022 See :hg:`help urls` for more information.
4022 See :hg:`help urls` for more information.
4023
4023
4024 Returns 0 on success.
4024 Returns 0 on success.
4025 """
4025 """
4026 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4026 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4027
4027
4028 @command('locate',
4028 @command('locate',
4029 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
4029 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
4030 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4030 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4031 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
4031 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
4032 ] + walkopts,
4032 ] + walkopts,
4033 _('[OPTION]... [PATTERN]...'))
4033 _('[OPTION]... [PATTERN]...'))
4034 def locate(ui, repo, *pats, **opts):
4034 def locate(ui, repo, *pats, **opts):
4035 """locate files matching specific patterns
4035 """locate files matching specific patterns
4036
4036
4037 Print files under Mercurial control in the working directory whose
4037 Print files under Mercurial control in the working directory whose
4038 names match the given patterns.
4038 names match the given patterns.
4039
4039
4040 By default, this command searches all directories in the working
4040 By default, this command searches all directories in the working
4041 directory. To search just the current directory and its
4041 directory. To search just the current directory and its
4042 subdirectories, use "--include .".
4042 subdirectories, use "--include .".
4043
4043
4044 If no patterns are given to match, this command prints the names
4044 If no patterns are given to match, this command prints the names
4045 of all files under Mercurial control in the working directory.
4045 of all files under Mercurial control in the working directory.
4046
4046
4047 If you want to feed the output of this command into the "xargs"
4047 If you want to feed the output of this command into the "xargs"
4048 command, use the -0 option to both this command and "xargs". This
4048 command, use the -0 option to both this command and "xargs". This
4049 will avoid the problem of "xargs" treating single filenames that
4049 will avoid the problem of "xargs" treating single filenames that
4050 contain whitespace as multiple filenames.
4050 contain whitespace as multiple filenames.
4051
4051
4052 Returns 0 if a match is found, 1 otherwise.
4052 Returns 0 if a match is found, 1 otherwise.
4053 """
4053 """
4054 end = opts.get('print0') and '\0' or '\n'
4054 end = opts.get('print0') and '\0' or '\n'
4055 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
4055 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
4056
4056
4057 ret = 1
4057 ret = 1
4058 m = scmutil.match(repo[rev], pats, opts, default='relglob')
4058 m = scmutil.match(repo[rev], pats, opts, default='relglob')
4059 m.bad = lambda x, y: False
4059 m.bad = lambda x, y: False
4060 for abs in repo[rev].walk(m):
4060 for abs in repo[rev].walk(m):
4061 if not rev and abs not in repo.dirstate:
4061 if not rev and abs not in repo.dirstate:
4062 continue
4062 continue
4063 if opts.get('fullpath'):
4063 if opts.get('fullpath'):
4064 ui.write(repo.wjoin(abs), end)
4064 ui.write(repo.wjoin(abs), end)
4065 else:
4065 else:
4066 ui.write(((pats and m.rel(abs)) or abs), end)
4066 ui.write(((pats and m.rel(abs)) or abs), end)
4067 ret = 0
4067 ret = 0
4068
4068
4069 return ret
4069 return ret
4070
4070
4071 @command('^log|history',
4071 @command('^log|history',
4072 [('f', 'follow', None,
4072 [('f', 'follow', None,
4073 _('follow changeset history, or file history across copies and renames')),
4073 _('follow changeset history, or file history across copies and renames')),
4074 ('', 'follow-first', None,
4074 ('', 'follow-first', None,
4075 _('only follow the first parent of merge changesets (DEPRECATED)')),
4075 _('only follow the first parent of merge changesets (DEPRECATED)')),
4076 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
4076 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
4077 ('C', 'copies', None, _('show copied files')),
4077 ('C', 'copies', None, _('show copied files')),
4078 ('k', 'keyword', [],
4078 ('k', 'keyword', [],
4079 _('do case-insensitive search for a given text'), _('TEXT')),
4079 _('do case-insensitive search for a given text'), _('TEXT')),
4080 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
4080 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
4081 ('', 'removed', None, _('include revisions where files were removed')),
4081 ('', 'removed', None, _('include revisions where files were removed')),
4082 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
4082 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
4083 ('u', 'user', [], _('revisions committed by user'), _('USER')),
4083 ('u', 'user', [], _('revisions committed by user'), _('USER')),
4084 ('', 'only-branch', [],
4084 ('', 'only-branch', [],
4085 _('show only changesets within the given named branch (DEPRECATED)'),
4085 _('show only changesets within the given named branch (DEPRECATED)'),
4086 _('BRANCH')),
4086 _('BRANCH')),
4087 ('b', 'branch', [],
4087 ('b', 'branch', [],
4088 _('show changesets within the given named branch'), _('BRANCH')),
4088 _('show changesets within the given named branch'), _('BRANCH')),
4089 ('P', 'prune', [],
4089 ('P', 'prune', [],
4090 _('do not display revision or any of its ancestors'), _('REV')),
4090 _('do not display revision or any of its ancestors'), _('REV')),
4091 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
4091 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
4092 ] + logopts + walkopts,
4092 ] + logopts + walkopts,
4093 _('[OPTION]... [FILE]'))
4093 _('[OPTION]... [FILE]'))
4094 def log(ui, repo, *pats, **opts):
4094 def log(ui, repo, *pats, **opts):
4095 """show revision history of entire repository or files
4095 """show revision history of entire repository or files
4096
4096
4097 Print the revision history of the specified files or the entire
4097 Print the revision history of the specified files or the entire
4098 project.
4098 project.
4099
4099
4100 If no revision range is specified, the default is ``tip:0`` unless
4100 If no revision range is specified, the default is ``tip:0`` unless
4101 --follow is set, in which case the working directory parent is
4101 --follow is set, in which case the working directory parent is
4102 used as the starting revision.
4102 used as the starting revision.
4103
4103
4104 File history is shown without following rename or copy history of
4104 File history is shown without following rename or copy history of
4105 files. Use -f/--follow with a filename to follow history across
4105 files. Use -f/--follow with a filename to follow history across
4106 renames and copies. --follow without a filename will only show
4106 renames and copies. --follow without a filename will only show
4107 ancestors or descendants of the starting revision.
4107 ancestors or descendants of the starting revision.
4108
4108
4109 By default this command prints revision number and changeset id,
4109 By default this command prints revision number and changeset id,
4110 tags, non-trivial parents, user, date and time, and a summary for
4110 tags, non-trivial parents, user, date and time, and a summary for
4111 each commit. When the -v/--verbose switch is used, the list of
4111 each commit. When the -v/--verbose switch is used, the list of
4112 changed files and full commit message are shown.
4112 changed files and full commit message are shown.
4113
4113
4114 .. note::
4114 .. note::
4115 log -p/--patch may generate unexpected diff output for merge
4115 log -p/--patch may generate unexpected diff output for merge
4116 changesets, as it will only compare the merge changeset against
4116 changesets, as it will only compare the merge changeset against
4117 its first parent. Also, only files different from BOTH parents
4117 its first parent. Also, only files different from BOTH parents
4118 will appear in files:.
4118 will appear in files:.
4119
4119
4120 .. note::
4120 .. note::
4121 for performance reasons, log FILE may omit duplicate changes
4121 for performance reasons, log FILE may omit duplicate changes
4122 made on branches and will not show deletions. To see all
4122 made on branches and will not show deletions. To see all
4123 changes including duplicates and deletions, use the --removed
4123 changes including duplicates and deletions, use the --removed
4124 switch.
4124 switch.
4125
4125
4126 .. container:: verbose
4126 .. container:: verbose
4127
4127
4128 Some examples:
4128 Some examples:
4129
4129
4130 - changesets with full descriptions and file lists::
4130 - changesets with full descriptions and file lists::
4131
4131
4132 hg log -v
4132 hg log -v
4133
4133
4134 - changesets ancestral to the working directory::
4134 - changesets ancestral to the working directory::
4135
4135
4136 hg log -f
4136 hg log -f
4137
4137
4138 - last 10 commits on the current branch::
4138 - last 10 commits on the current branch::
4139
4139
4140 hg log -l 10 -b .
4140 hg log -l 10 -b .
4141
4141
4142 - changesets showing all modifications of a file, including removals::
4142 - changesets showing all modifications of a file, including removals::
4143
4143
4144 hg log --removed file.c
4144 hg log --removed file.c
4145
4145
4146 - all changesets that touch a directory, with diffs, excluding merges::
4146 - all changesets that touch a directory, with diffs, excluding merges::
4147
4147
4148 hg log -Mp lib/
4148 hg log -Mp lib/
4149
4149
4150 - all revision numbers that match a keyword::
4150 - all revision numbers that match a keyword::
4151
4151
4152 hg log -k bug --template "{rev}\\n"
4152 hg log -k bug --template "{rev}\\n"
4153
4153
4154 - check if a given changeset is included is a tagged release::
4154 - check if a given changeset is included is a tagged release::
4155
4155
4156 hg log -r "a21ccf and ancestor(1.9)"
4156 hg log -r "a21ccf and ancestor(1.9)"
4157
4157
4158 - find all changesets by some user in a date range::
4158 - find all changesets by some user in a date range::
4159
4159
4160 hg log -k alice -d "may 2008 to jul 2008"
4160 hg log -k alice -d "may 2008 to jul 2008"
4161
4161
4162 - summary of all changesets after the last tag::
4162 - summary of all changesets after the last tag::
4163
4163
4164 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4164 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4165
4165
4166 See :hg:`help dates` for a list of formats valid for -d/--date.
4166 See :hg:`help dates` for a list of formats valid for -d/--date.
4167
4167
4168 See :hg:`help revisions` and :hg:`help revsets` for more about
4168 See :hg:`help revisions` and :hg:`help revsets` for more about
4169 specifying revisions.
4169 specifying revisions.
4170
4170
4171 See :hg:`help templates` for more about pre-packaged styles and
4171 See :hg:`help templates` for more about pre-packaged styles and
4172 specifying custom templates.
4172 specifying custom templates.
4173
4173
4174 Returns 0 on success.
4174 Returns 0 on success.
4175 """
4175 """
4176 if opts.get('graph'):
4176 if opts.get('graph'):
4177 return cmdutil.graphlog(ui, repo, *pats, **opts)
4177 return cmdutil.graphlog(ui, repo, *pats, **opts)
4178
4178
4179 matchfn = scmutil.match(repo[None], pats, opts)
4179 matchfn = scmutil.match(repo[None], pats, opts)
4180 limit = cmdutil.loglimit(opts)
4180 limit = cmdutil.loglimit(opts)
4181 count = 0
4181 count = 0
4182
4182
4183 getrenamed, endrev = None, None
4183 getrenamed, endrev = None, None
4184 if opts.get('copies'):
4184 if opts.get('copies'):
4185 if opts.get('rev'):
4185 if opts.get('rev'):
4186 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
4186 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
4187 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4187 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4188
4188
4189 df = False
4189 df = False
4190 if opts.get("date"):
4190 if opts.get("date"):
4191 df = util.matchdate(opts["date"])
4191 df = util.matchdate(opts["date"])
4192
4192
4193 branches = opts.get('branch', []) + opts.get('only_branch', [])
4193 branches = opts.get('branch', []) + opts.get('only_branch', [])
4194 opts['branch'] = [repo.lookupbranch(b) for b in branches]
4194 opts['branch'] = [repo.lookupbranch(b) for b in branches]
4195
4195
4196 displayer = cmdutil.show_changeset(ui, repo, opts, True)
4196 displayer = cmdutil.show_changeset(ui, repo, opts, True)
4197 def prep(ctx, fns):
4197 def prep(ctx, fns):
4198 rev = ctx.rev()
4198 rev = ctx.rev()
4199 parents = [p for p in repo.changelog.parentrevs(rev)
4199 parents = [p for p in repo.changelog.parentrevs(rev)
4200 if p != nullrev]
4200 if p != nullrev]
4201 if opts.get('no_merges') and len(parents) == 2:
4201 if opts.get('no_merges') and len(parents) == 2:
4202 return
4202 return
4203 if opts.get('only_merges') and len(parents) != 2:
4203 if opts.get('only_merges') and len(parents) != 2:
4204 return
4204 return
4205 if opts.get('branch') and ctx.branch() not in opts['branch']:
4205 if opts.get('branch') and ctx.branch() not in opts['branch']:
4206 return
4206 return
4207 if not opts.get('hidden') and ctx.hidden():
4207 if not opts.get('hidden') and ctx.hidden():
4208 return
4208 return
4209 if df and not df(ctx.date()[0]):
4209 if df and not df(ctx.date()[0]):
4210 return
4210 return
4211
4211
4212 lower = encoding.lower
4212 lower = encoding.lower
4213 if opts.get('user'):
4213 if opts.get('user'):
4214 luser = lower(ctx.user())
4214 luser = lower(ctx.user())
4215 for k in [lower(x) for x in opts['user']]:
4215 for k in [lower(x) for x in opts['user']]:
4216 if (k in luser):
4216 if (k in luser):
4217 break
4217 break
4218 else:
4218 else:
4219 return
4219 return
4220 if opts.get('keyword'):
4220 if opts.get('keyword'):
4221 luser = lower(ctx.user())
4221 luser = lower(ctx.user())
4222 ldesc = lower(ctx.description())
4222 ldesc = lower(ctx.description())
4223 lfiles = lower(" ".join(ctx.files()))
4223 lfiles = lower(" ".join(ctx.files()))
4224 for k in [lower(x) for x in opts['keyword']]:
4224 for k in [lower(x) for x in opts['keyword']]:
4225 if (k in luser or k in ldesc or k in lfiles):
4225 if (k in luser or k in ldesc or k in lfiles):
4226 break
4226 break
4227 else:
4227 else:
4228 return
4228 return
4229
4229
4230 copies = None
4230 copies = None
4231 if getrenamed is not None and rev:
4231 if getrenamed is not None and rev:
4232 copies = []
4232 copies = []
4233 for fn in ctx.files():
4233 for fn in ctx.files():
4234 rename = getrenamed(fn, rev)
4234 rename = getrenamed(fn, rev)
4235 if rename:
4235 if rename:
4236 copies.append((fn, rename[0]))
4236 copies.append((fn, rename[0]))
4237
4237
4238 revmatchfn = None
4238 revmatchfn = None
4239 if opts.get('patch') or opts.get('stat'):
4239 if opts.get('patch') or opts.get('stat'):
4240 if opts.get('follow') or opts.get('follow_first'):
4240 if opts.get('follow') or opts.get('follow_first'):
4241 # note: this might be wrong when following through merges
4241 # note: this might be wrong when following through merges
4242 revmatchfn = scmutil.match(repo[None], fns, default='path')
4242 revmatchfn = scmutil.match(repo[None], fns, default='path')
4243 else:
4243 else:
4244 revmatchfn = matchfn
4244 revmatchfn = matchfn
4245
4245
4246 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4246 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4247
4247
4248 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4248 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4249 if count == limit:
4249 if count == limit:
4250 break
4250 break
4251 if displayer.flush(ctx.rev()):
4251 if displayer.flush(ctx.rev()):
4252 count += 1
4252 count += 1
4253 displayer.close()
4253 displayer.close()
4254
4254
4255 @command('manifest',
4255 @command('manifest',
4256 [('r', 'rev', '', _('revision to display'), _('REV')),
4256 [('r', 'rev', '', _('revision to display'), _('REV')),
4257 ('', 'all', False, _("list files from all revisions"))],
4257 ('', 'all', False, _("list files from all revisions"))],
4258 _('[-r REV]'))
4258 _('[-r REV]'))
4259 def manifest(ui, repo, node=None, rev=None, **opts):
4259 def manifest(ui, repo, node=None, rev=None, **opts):
4260 """output the current or given revision of the project manifest
4260 """output the current or given revision of the project manifest
4261
4261
4262 Print a list of version controlled files for the given revision.
4262 Print a list of version controlled files for the given revision.
4263 If no revision is given, the first parent of the working directory
4263 If no revision is given, the first parent of the working directory
4264 is used, or the null revision if no revision is checked out.
4264 is used, or the null revision if no revision is checked out.
4265
4265
4266 With -v, print file permissions, symlink and executable bits.
4266 With -v, print file permissions, symlink and executable bits.
4267 With --debug, print file revision hashes.
4267 With --debug, print file revision hashes.
4268
4268
4269 If option --all is specified, the list of all files from all revisions
4269 If option --all is specified, the list of all files from all revisions
4270 is printed. This includes deleted and renamed files.
4270 is printed. This includes deleted and renamed files.
4271
4271
4272 Returns 0 on success.
4272 Returns 0 on success.
4273 """
4273 """
4274
4274
4275 fm = ui.formatter('manifest', opts)
4275 fm = ui.formatter('manifest', opts)
4276
4276
4277 if opts.get('all'):
4277 if opts.get('all'):
4278 if rev or node:
4278 if rev or node:
4279 raise util.Abort(_("can't specify a revision with --all"))
4279 raise util.Abort(_("can't specify a revision with --all"))
4280
4280
4281 res = []
4281 res = []
4282 prefix = "data/"
4282 prefix = "data/"
4283 suffix = ".i"
4283 suffix = ".i"
4284 plen = len(prefix)
4284 plen = len(prefix)
4285 slen = len(suffix)
4285 slen = len(suffix)
4286 lock = repo.lock()
4286 lock = repo.lock()
4287 try:
4287 try:
4288 for fn, b, size in repo.store.datafiles():
4288 for fn, b, size in repo.store.datafiles():
4289 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4289 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4290 res.append(fn[plen:-slen])
4290 res.append(fn[plen:-slen])
4291 finally:
4291 finally:
4292 lock.release()
4292 lock.release()
4293 for f in res:
4293 for f in res:
4294 fm.startitem()
4294 fm.startitem()
4295 fm.write("path", '%s\n', f)
4295 fm.write("path", '%s\n', f)
4296 fm.end()
4296 fm.end()
4297 return
4297 return
4298
4298
4299 if rev and node:
4299 if rev and node:
4300 raise util.Abort(_("please specify just one revision"))
4300 raise util.Abort(_("please specify just one revision"))
4301
4301
4302 if not node:
4302 if not node:
4303 node = rev
4303 node = rev
4304
4304
4305 char = {'l': '@', 'x': '*', '': ''}
4305 char = {'l': '@', 'x': '*', '': ''}
4306 mode = {'l': '644', 'x': '755', '': '644'}
4306 mode = {'l': '644', 'x': '755', '': '644'}
4307 ctx = scmutil.revsingle(repo, node)
4307 ctx = scmutil.revsingle(repo, node)
4308 mf = ctx.manifest()
4308 mf = ctx.manifest()
4309 for f in ctx:
4309 for f in ctx:
4310 fm.startitem()
4310 fm.startitem()
4311 fl = ctx[f].flags()
4311 fl = ctx[f].flags()
4312 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
4312 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
4313 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
4313 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
4314 fm.write('path', '%s\n', f)
4314 fm.write('path', '%s\n', f)
4315 fm.end()
4315 fm.end()
4316
4316
4317 @command('^merge',
4317 @command('^merge',
4318 [('f', 'force', None, _('force a merge with outstanding changes')),
4318 [('f', 'force', None, _('force a merge with outstanding changes')),
4319 ('r', 'rev', '', _('revision to merge'), _('REV')),
4319 ('r', 'rev', '', _('revision to merge'), _('REV')),
4320 ('P', 'preview', None,
4320 ('P', 'preview', None,
4321 _('review revisions to merge (no merge is performed)'))
4321 _('review revisions to merge (no merge is performed)'))
4322 ] + mergetoolopts,
4322 ] + mergetoolopts,
4323 _('[-P] [-f] [[-r] REV]'))
4323 _('[-P] [-f] [[-r] REV]'))
4324 def merge(ui, repo, node=None, **opts):
4324 def merge(ui, repo, node=None, **opts):
4325 """merge working directory with another revision
4325 """merge working directory with another revision
4326
4326
4327 The current working directory is updated with all changes made in
4327 The current working directory is updated with all changes made in
4328 the requested revision since the last common predecessor revision.
4328 the requested revision since the last common predecessor revision.
4329
4329
4330 Files that changed between either parent are marked as changed for
4330 Files that changed between either parent are marked as changed for
4331 the next commit and a commit must be performed before any further
4331 the next commit and a commit must be performed before any further
4332 updates to the repository are allowed. The next commit will have
4332 updates to the repository are allowed. The next commit will have
4333 two parents.
4333 two parents.
4334
4334
4335 ``--tool`` can be used to specify the merge tool used for file
4335 ``--tool`` can be used to specify the merge tool used for file
4336 merges. It overrides the HGMERGE environment variable and your
4336 merges. It overrides the HGMERGE environment variable and your
4337 configuration files. See :hg:`help merge-tools` for options.
4337 configuration files. See :hg:`help merge-tools` for options.
4338
4338
4339 If no revision is specified, the working directory's parent is a
4339 If no revision is specified, the working directory's parent is a
4340 head revision, and the current branch contains exactly one other
4340 head revision, and the current branch contains exactly one other
4341 head, the other head is merged with by default. Otherwise, an
4341 head, the other head is merged with by default. Otherwise, an
4342 explicit revision with which to merge with must be provided.
4342 explicit revision with which to merge with must be provided.
4343
4343
4344 :hg:`resolve` must be used to resolve unresolved files.
4344 :hg:`resolve` must be used to resolve unresolved files.
4345
4345
4346 To undo an uncommitted merge, use :hg:`update --clean .` which
4346 To undo an uncommitted merge, use :hg:`update --clean .` which
4347 will check out a clean copy of the original merge parent, losing
4347 will check out a clean copy of the original merge parent, losing
4348 all changes.
4348 all changes.
4349
4349
4350 Returns 0 on success, 1 if there are unresolved files.
4350 Returns 0 on success, 1 if there are unresolved files.
4351 """
4351 """
4352
4352
4353 if opts.get('rev') and node:
4353 if opts.get('rev') and node:
4354 raise util.Abort(_("please specify just one revision"))
4354 raise util.Abort(_("please specify just one revision"))
4355 if not node:
4355 if not node:
4356 node = opts.get('rev')
4356 node = opts.get('rev')
4357
4357
4358 if node:
4358 if node:
4359 node = scmutil.revsingle(repo, node).node()
4359 node = scmutil.revsingle(repo, node).node()
4360
4360
4361 if not node and repo._bookmarkcurrent:
4361 if not node and repo._bookmarkcurrent:
4362 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4362 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4363 curhead = repo[repo._bookmarkcurrent].node()
4363 curhead = repo[repo._bookmarkcurrent].node()
4364 if len(bmheads) == 2:
4364 if len(bmheads) == 2:
4365 if curhead == bmheads[0]:
4365 if curhead == bmheads[0]:
4366 node = bmheads[1]
4366 node = bmheads[1]
4367 else:
4367 else:
4368 node = bmheads[0]
4368 node = bmheads[0]
4369 elif len(bmheads) > 2:
4369 elif len(bmheads) > 2:
4370 raise util.Abort(_("multiple matching bookmarks to merge - "
4370 raise util.Abort(_("multiple matching bookmarks to merge - "
4371 "please merge with an explicit rev or bookmark"),
4371 "please merge with an explicit rev or bookmark"),
4372 hint=_("run 'hg heads' to see all heads"))
4372 hint=_("run 'hg heads' to see all heads"))
4373 elif len(bmheads) <= 1:
4373 elif len(bmheads) <= 1:
4374 raise util.Abort(_("no matching bookmark to merge - "
4374 raise util.Abort(_("no matching bookmark to merge - "
4375 "please merge with an explicit rev or bookmark"),
4375 "please merge with an explicit rev or bookmark"),
4376 hint=_("run 'hg heads' to see all heads"))
4376 hint=_("run 'hg heads' to see all heads"))
4377
4377
4378 if not node and not repo._bookmarkcurrent:
4378 if not node and not repo._bookmarkcurrent:
4379 branch = repo[None].branch()
4379 branch = repo[None].branch()
4380 bheads = repo.branchheads(branch)
4380 bheads = repo.branchheads(branch)
4381 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4381 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4382
4382
4383 if len(nbhs) > 2:
4383 if len(nbhs) > 2:
4384 raise util.Abort(_("branch '%s' has %d heads - "
4384 raise util.Abort(_("branch '%s' has %d heads - "
4385 "please merge with an explicit rev")
4385 "please merge with an explicit rev")
4386 % (branch, len(bheads)),
4386 % (branch, len(bheads)),
4387 hint=_("run 'hg heads .' to see heads"))
4387 hint=_("run 'hg heads .' to see heads"))
4388
4388
4389 parent = repo.dirstate.p1()
4389 parent = repo.dirstate.p1()
4390 if len(nbhs) <= 1:
4390 if len(nbhs) <= 1:
4391 if len(bheads) > 1:
4391 if len(bheads) > 1:
4392 raise util.Abort(_("heads are bookmarked - "
4392 raise util.Abort(_("heads are bookmarked - "
4393 "please merge with an explicit rev"),
4393 "please merge with an explicit rev"),
4394 hint=_("run 'hg heads' to see all heads"))
4394 hint=_("run 'hg heads' to see all heads"))
4395 if len(repo.heads()) > 1:
4395 if len(repo.heads()) > 1:
4396 raise util.Abort(_("branch '%s' has one head - "
4396 raise util.Abort(_("branch '%s' has one head - "
4397 "please merge with an explicit rev")
4397 "please merge with an explicit rev")
4398 % branch,
4398 % branch,
4399 hint=_("run 'hg heads' to see all heads"))
4399 hint=_("run 'hg heads' to see all heads"))
4400 msg, hint = _('nothing to merge'), None
4400 msg, hint = _('nothing to merge'), None
4401 if parent != repo.lookup(branch):
4401 if parent != repo.lookup(branch):
4402 hint = _("use 'hg update' instead")
4402 hint = _("use 'hg update' instead")
4403 raise util.Abort(msg, hint=hint)
4403 raise util.Abort(msg, hint=hint)
4404
4404
4405 if parent not in bheads:
4405 if parent not in bheads:
4406 raise util.Abort(_('working directory not at a head revision'),
4406 raise util.Abort(_('working directory not at a head revision'),
4407 hint=_("use 'hg update' or merge with an "
4407 hint=_("use 'hg update' or merge with an "
4408 "explicit revision"))
4408 "explicit revision"))
4409 if parent == nbhs[0]:
4409 if parent == nbhs[0]:
4410 node = nbhs[-1]
4410 node = nbhs[-1]
4411 else:
4411 else:
4412 node = nbhs[0]
4412 node = nbhs[0]
4413
4413
4414 if opts.get('preview'):
4414 if opts.get('preview'):
4415 # find nodes that are ancestors of p2 but not of p1
4415 # find nodes that are ancestors of p2 but not of p1
4416 p1 = repo.lookup('.')
4416 p1 = repo.lookup('.')
4417 p2 = repo.lookup(node)
4417 p2 = repo.lookup(node)
4418 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4418 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4419
4419
4420 displayer = cmdutil.show_changeset(ui, repo, opts)
4420 displayer = cmdutil.show_changeset(ui, repo, opts)
4421 for node in nodes:
4421 for node in nodes:
4422 displayer.show(repo[node])
4422 displayer.show(repo[node])
4423 displayer.close()
4423 displayer.close()
4424 return 0
4424 return 0
4425
4425
4426 try:
4426 try:
4427 # ui.forcemerge is an internal variable, do not document
4427 # ui.forcemerge is an internal variable, do not document
4428 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4428 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4429 return hg.merge(repo, node, force=opts.get('force'))
4429 return hg.merge(repo, node, force=opts.get('force'))
4430 finally:
4430 finally:
4431 ui.setconfig('ui', 'forcemerge', '')
4431 ui.setconfig('ui', 'forcemerge', '')
4432
4432
4433 @command('outgoing|out',
4433 @command('outgoing|out',
4434 [('f', 'force', None, _('run even when the destination is unrelated')),
4434 [('f', 'force', None, _('run even when the destination is unrelated')),
4435 ('r', 'rev', [],
4435 ('r', 'rev', [],
4436 _('a changeset intended to be included in the destination'), _('REV')),
4436 _('a changeset intended to be included in the destination'), _('REV')),
4437 ('n', 'newest-first', None, _('show newest record first')),
4437 ('n', 'newest-first', None, _('show newest record first')),
4438 ('B', 'bookmarks', False, _('compare bookmarks')),
4438 ('B', 'bookmarks', False, _('compare bookmarks')),
4439 ('b', 'branch', [], _('a specific branch you would like to push'),
4439 ('b', 'branch', [], _('a specific branch you would like to push'),
4440 _('BRANCH')),
4440 _('BRANCH')),
4441 ] + logopts + remoteopts + subrepoopts,
4441 ] + logopts + remoteopts + subrepoopts,
4442 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4442 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4443 def outgoing(ui, repo, dest=None, **opts):
4443 def outgoing(ui, repo, dest=None, **opts):
4444 """show changesets not found in the destination
4444 """show changesets not found in the destination
4445
4445
4446 Show changesets not found in the specified destination repository
4446 Show changesets not found in the specified destination repository
4447 or the default push location. These are the changesets that would
4447 or the default push location. These are the changesets that would
4448 be pushed if a push was requested.
4448 be pushed if a push was requested.
4449
4449
4450 See pull for details of valid destination formats.
4450 See pull for details of valid destination formats.
4451
4451
4452 Returns 0 if there are outgoing changes, 1 otherwise.
4452 Returns 0 if there are outgoing changes, 1 otherwise.
4453 """
4453 """
4454 if opts.get('graph'):
4454 if opts.get('graph'):
4455 cmdutil.checkunsupportedgraphflags([], opts)
4455 cmdutil.checkunsupportedgraphflags([], opts)
4456 o = hg._outgoing(ui, repo, dest, opts)
4456 o = hg._outgoing(ui, repo, dest, opts)
4457 if o is None:
4457 if o is None:
4458 return
4458 return
4459
4459
4460 revdag = cmdutil.graphrevs(repo, o, opts)
4460 revdag = cmdutil.graphrevs(repo, o, opts)
4461 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4461 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4462 showparents = [ctx.node() for ctx in repo[None].parents()]
4462 showparents = [ctx.node() for ctx in repo[None].parents()]
4463 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4463 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4464 graphmod.asciiedges)
4464 graphmod.asciiedges)
4465 return 0
4465 return 0
4466
4466
4467 if opts.get('bookmarks'):
4467 if opts.get('bookmarks'):
4468 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4468 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4469 dest, branches = hg.parseurl(dest, opts.get('branch'))
4469 dest, branches = hg.parseurl(dest, opts.get('branch'))
4470 other = hg.peer(repo, opts, dest)
4470 other = hg.peer(repo, opts, dest)
4471 if 'bookmarks' not in other.listkeys('namespaces'):
4471 if 'bookmarks' not in other.listkeys('namespaces'):
4472 ui.warn(_("remote doesn't support bookmarks\n"))
4472 ui.warn(_("remote doesn't support bookmarks\n"))
4473 return 0
4473 return 0
4474 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4474 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4475 return bookmarks.diff(ui, other, repo)
4475 return bookmarks.diff(ui, other, repo)
4476
4476
4477 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4477 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4478 try:
4478 try:
4479 return hg.outgoing(ui, repo, dest, opts)
4479 return hg.outgoing(ui, repo, dest, opts)
4480 finally:
4480 finally:
4481 del repo._subtoppath
4481 del repo._subtoppath
4482
4482
4483 @command('parents',
4483 @command('parents',
4484 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4484 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4485 ] + templateopts,
4485 ] + templateopts,
4486 _('[-r REV] [FILE]'))
4486 _('[-r REV] [FILE]'))
4487 def parents(ui, repo, file_=None, **opts):
4487 def parents(ui, repo, file_=None, **opts):
4488 """show the parents of the working directory or revision
4488 """show the parents of the working directory or revision
4489
4489
4490 Print the working directory's parent revisions. If a revision is
4490 Print the working directory's parent revisions. If a revision is
4491 given via -r/--rev, the parent of that revision will be printed.
4491 given via -r/--rev, the parent of that revision will be printed.
4492 If a file argument is given, the revision in which the file was
4492 If a file argument is given, the revision in which the file was
4493 last changed (before the working directory revision or the
4493 last changed (before the working directory revision or the
4494 argument to --rev if given) is printed.
4494 argument to --rev if given) is printed.
4495
4495
4496 Returns 0 on success.
4496 Returns 0 on success.
4497 """
4497 """
4498
4498
4499 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4499 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4500
4500
4501 if file_:
4501 if file_:
4502 m = scmutil.match(ctx, (file_,), opts)
4502 m = scmutil.match(ctx, (file_,), opts)
4503 if m.anypats() or len(m.files()) != 1:
4503 if m.anypats() or len(m.files()) != 1:
4504 raise util.Abort(_('can only specify an explicit filename'))
4504 raise util.Abort(_('can only specify an explicit filename'))
4505 file_ = m.files()[0]
4505 file_ = m.files()[0]
4506 filenodes = []
4506 filenodes = []
4507 for cp in ctx.parents():
4507 for cp in ctx.parents():
4508 if not cp:
4508 if not cp:
4509 continue
4509 continue
4510 try:
4510 try:
4511 filenodes.append(cp.filenode(file_))
4511 filenodes.append(cp.filenode(file_))
4512 except error.LookupError:
4512 except error.LookupError:
4513 pass
4513 pass
4514 if not filenodes:
4514 if not filenodes:
4515 raise util.Abort(_("'%s' not found in manifest!") % file_)
4515 raise util.Abort(_("'%s' not found in manifest!") % file_)
4516 fl = repo.file(file_)
4516 fl = repo.file(file_)
4517 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4517 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4518 else:
4518 else:
4519 p = [cp.node() for cp in ctx.parents()]
4519 p = [cp.node() for cp in ctx.parents()]
4520
4520
4521 displayer = cmdutil.show_changeset(ui, repo, opts)
4521 displayer = cmdutil.show_changeset(ui, repo, opts)
4522 for n in p:
4522 for n in p:
4523 if n != nullid:
4523 if n != nullid:
4524 displayer.show(repo[n])
4524 displayer.show(repo[n])
4525 displayer.close()
4525 displayer.close()
4526
4526
4527 @command('paths', [], _('[NAME]'))
4527 @command('paths', [], _('[NAME]'))
4528 def paths(ui, repo, search=None):
4528 def paths(ui, repo, search=None):
4529 """show aliases for remote repositories
4529 """show aliases for remote repositories
4530
4530
4531 Show definition of symbolic path name NAME. If no name is given,
4531 Show definition of symbolic path name NAME. If no name is given,
4532 show definition of all available names.
4532 show definition of all available names.
4533
4533
4534 Option -q/--quiet suppresses all output when searching for NAME
4534 Option -q/--quiet suppresses all output when searching for NAME
4535 and shows only the path names when listing all definitions.
4535 and shows only the path names when listing all definitions.
4536
4536
4537 Path names are defined in the [paths] section of your
4537 Path names are defined in the [paths] section of your
4538 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4538 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4539 repository, ``.hg/hgrc`` is used, too.
4539 repository, ``.hg/hgrc`` is used, too.
4540
4540
4541 The path names ``default`` and ``default-push`` have a special
4541 The path names ``default`` and ``default-push`` have a special
4542 meaning. When performing a push or pull operation, they are used
4542 meaning. When performing a push or pull operation, they are used
4543 as fallbacks if no location is specified on the command-line.
4543 as fallbacks if no location is specified on the command-line.
4544 When ``default-push`` is set, it will be used for push and
4544 When ``default-push`` is set, it will be used for push and
4545 ``default`` will be used for pull; otherwise ``default`` is used
4545 ``default`` will be used for pull; otherwise ``default`` is used
4546 as the fallback for both. When cloning a repository, the clone
4546 as the fallback for both. When cloning a repository, the clone
4547 source is written as ``default`` in ``.hg/hgrc``. Note that
4547 source is written as ``default`` in ``.hg/hgrc``. Note that
4548 ``default`` and ``default-push`` apply to all inbound (e.g.
4548 ``default`` and ``default-push`` apply to all inbound (e.g.
4549 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4549 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4550 :hg:`bundle`) operations.
4550 :hg:`bundle`) operations.
4551
4551
4552 See :hg:`help urls` for more information.
4552 See :hg:`help urls` for more information.
4553
4553
4554 Returns 0 on success.
4554 Returns 0 on success.
4555 """
4555 """
4556 if search:
4556 if search:
4557 for name, path in ui.configitems("paths"):
4557 for name, path in ui.configitems("paths"):
4558 if name == search:
4558 if name == search:
4559 ui.status("%s\n" % util.hidepassword(path))
4559 ui.status("%s\n" % util.hidepassword(path))
4560 return
4560 return
4561 if not ui.quiet:
4561 if not ui.quiet:
4562 ui.warn(_("not found!\n"))
4562 ui.warn(_("not found!\n"))
4563 return 1
4563 return 1
4564 else:
4564 else:
4565 for name, path in ui.configitems("paths"):
4565 for name, path in ui.configitems("paths"):
4566 if ui.quiet:
4566 if ui.quiet:
4567 ui.write("%s\n" % name)
4567 ui.write("%s\n" % name)
4568 else:
4568 else:
4569 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4569 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4570
4570
4571 @command('phase',
4571 @command('phase',
4572 [('p', 'public', False, _('set changeset phase to public')),
4572 [('p', 'public', False, _('set changeset phase to public')),
4573 ('d', 'draft', False, _('set changeset phase to draft')),
4573 ('d', 'draft', False, _('set changeset phase to draft')),
4574 ('s', 'secret', False, _('set changeset phase to secret')),
4574 ('s', 'secret', False, _('set changeset phase to secret')),
4575 ('f', 'force', False, _('allow to move boundary backward')),
4575 ('f', 'force', False, _('allow to move boundary backward')),
4576 ('r', 'rev', [], _('target revision'), _('REV')),
4576 ('r', 'rev', [], _('target revision'), _('REV')),
4577 ],
4577 ],
4578 _('[-p|-d|-s] [-f] [-r] REV...'))
4578 _('[-p|-d|-s] [-f] [-r] REV...'))
4579 def phase(ui, repo, *revs, **opts):
4579 def phase(ui, repo, *revs, **opts):
4580 """set or show the current phase name
4580 """set or show the current phase name
4581
4581
4582 With no argument, show the phase name of specified revisions.
4582 With no argument, show the phase name of specified revisions.
4583
4583
4584 With one of -p/--public, -d/--draft or -s/--secret, change the
4584 With one of -p/--public, -d/--draft or -s/--secret, change the
4585 phase value of the specified revisions.
4585 phase value of the specified revisions.
4586
4586
4587 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4587 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4588 lower phase to an higher phase. Phases are ordered as follows::
4588 lower phase to an higher phase. Phases are ordered as follows::
4589
4589
4590 public < draft < secret
4590 public < draft < secret
4591
4591
4592 Return 0 on success, 1 if no phases were changed or some could not
4592 Return 0 on success, 1 if no phases were changed or some could not
4593 be changed.
4593 be changed.
4594 """
4594 """
4595 # search for a unique phase argument
4595 # search for a unique phase argument
4596 targetphase = None
4596 targetphase = None
4597 for idx, name in enumerate(phases.phasenames):
4597 for idx, name in enumerate(phases.phasenames):
4598 if opts[name]:
4598 if opts[name]:
4599 if targetphase is not None:
4599 if targetphase is not None:
4600 raise util.Abort(_('only one phase can be specified'))
4600 raise util.Abort(_('only one phase can be specified'))
4601 targetphase = idx
4601 targetphase = idx
4602
4602
4603 # look for specified revision
4603 # look for specified revision
4604 revs = list(revs)
4604 revs = list(revs)
4605 revs.extend(opts['rev'])
4605 revs.extend(opts['rev'])
4606 if not revs:
4606 if not revs:
4607 raise util.Abort(_('no revisions specified'))
4607 raise util.Abort(_('no revisions specified'))
4608
4608
4609 revs = scmutil.revrange(repo, revs)
4609 revs = scmutil.revrange(repo, revs)
4610
4610
4611 lock = None
4611 lock = None
4612 ret = 0
4612 ret = 0
4613 if targetphase is None:
4613 if targetphase is None:
4614 # display
4614 # display
4615 for r in revs:
4615 for r in revs:
4616 ctx = repo[r]
4616 ctx = repo[r]
4617 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4617 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4618 else:
4618 else:
4619 lock = repo.lock()
4619 lock = repo.lock()
4620 try:
4620 try:
4621 # set phase
4621 # set phase
4622 if not revs:
4622 if not revs:
4623 raise util.Abort(_('empty revision set'))
4623 raise util.Abort(_('empty revision set'))
4624 nodes = [repo[r].node() for r in revs]
4624 nodes = [repo[r].node() for r in revs]
4625 olddata = repo._phasecache.getphaserevs(repo)[:]
4625 olddata = repo._phasecache.getphaserevs(repo)[:]
4626 phases.advanceboundary(repo, targetphase, nodes)
4626 phases.advanceboundary(repo, targetphase, nodes)
4627 if opts['force']:
4627 if opts['force']:
4628 phases.retractboundary(repo, targetphase, nodes)
4628 phases.retractboundary(repo, targetphase, nodes)
4629 finally:
4629 finally:
4630 lock.release()
4630 lock.release()
4631 newdata = repo._phasecache.getphaserevs(repo)
4631 newdata = repo._phasecache.getphaserevs(repo)
4632 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4632 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4633 rejected = [n for n in nodes
4633 rejected = [n for n in nodes
4634 if newdata[repo[n].rev()] < targetphase]
4634 if newdata[repo[n].rev()] < targetphase]
4635 if rejected:
4635 if rejected:
4636 ui.warn(_('cannot move %i changesets to a more permissive '
4636 ui.warn(_('cannot move %i changesets to a more permissive '
4637 'phase, use --force\n') % len(rejected))
4637 'phase, use --force\n') % len(rejected))
4638 ret = 1
4638 ret = 1
4639 if changes:
4639 if changes:
4640 msg = _('phase changed for %i changesets\n') % changes
4640 msg = _('phase changed for %i changesets\n') % changes
4641 if ret:
4641 if ret:
4642 ui.status(msg)
4642 ui.status(msg)
4643 else:
4643 else:
4644 ui.note(msg)
4644 ui.note(msg)
4645 else:
4645 else:
4646 ui.warn(_('no phases changed\n'))
4646 ui.warn(_('no phases changed\n'))
4647 ret = 1
4647 ret = 1
4648 return ret
4648 return ret
4649
4649
4650 def postincoming(ui, repo, modheads, optupdate, checkout):
4650 def postincoming(ui, repo, modheads, optupdate, checkout):
4651 if modheads == 0:
4651 if modheads == 0:
4652 return
4652 return
4653 if optupdate:
4653 if optupdate:
4654 movemarkfrom = repo['.'].node()
4654 movemarkfrom = repo['.'].node()
4655 try:
4655 try:
4656 ret = hg.update(repo, checkout)
4656 ret = hg.update(repo, checkout)
4657 except util.Abort, inst:
4657 except util.Abort, inst:
4658 ui.warn(_("not updating: %s\n") % str(inst))
4658 ui.warn(_("not updating: %s\n") % str(inst))
4659 return 0
4659 return 0
4660 if not ret and not checkout:
4660 if not ret and not checkout:
4661 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4661 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4662 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4662 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4663 return ret
4663 return ret
4664 if modheads > 1:
4664 if modheads > 1:
4665 currentbranchheads = len(repo.branchheads())
4665 currentbranchheads = len(repo.branchheads())
4666 if currentbranchheads == modheads:
4666 if currentbranchheads == modheads:
4667 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4667 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4668 elif currentbranchheads > 1:
4668 elif currentbranchheads > 1:
4669 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4669 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4670 "merge)\n"))
4670 "merge)\n"))
4671 else:
4671 else:
4672 ui.status(_("(run 'hg heads' to see heads)\n"))
4672 ui.status(_("(run 'hg heads' to see heads)\n"))
4673 else:
4673 else:
4674 ui.status(_("(run 'hg update' to get a working copy)\n"))
4674 ui.status(_("(run 'hg update' to get a working copy)\n"))
4675
4675
4676 @command('^pull',
4676 @command('^pull',
4677 [('u', 'update', None,
4677 [('u', 'update', None,
4678 _('update to new branch head if changesets were pulled')),
4678 _('update to new branch head if changesets were pulled')),
4679 ('f', 'force', None, _('run even when remote repository is unrelated')),
4679 ('f', 'force', None, _('run even when remote repository is unrelated')),
4680 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4680 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4681 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4681 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4682 ('b', 'branch', [], _('a specific branch you would like to pull'),
4682 ('b', 'branch', [], _('a specific branch you would like to pull'),
4683 _('BRANCH')),
4683 _('BRANCH')),
4684 ] + remoteopts,
4684 ] + remoteopts,
4685 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4685 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4686 def pull(ui, repo, source="default", **opts):
4686 def pull(ui, repo, source="default", **opts):
4687 """pull changes from the specified source
4687 """pull changes from the specified source
4688
4688
4689 Pull changes from a remote repository to a local one.
4689 Pull changes from a remote repository to a local one.
4690
4690
4691 This finds all changes from the repository at the specified path
4691 This finds all changes from the repository at the specified path
4692 or URL and adds them to a local repository (the current one unless
4692 or URL and adds them to a local repository (the current one unless
4693 -R is specified). By default, this does not update the copy of the
4693 -R is specified). By default, this does not update the copy of the
4694 project in the working directory.
4694 project in the working directory.
4695
4695
4696 Use :hg:`incoming` if you want to see what would have been added
4696 Use :hg:`incoming` if you want to see what would have been added
4697 by a pull at the time you issued this command. If you then decide
4697 by a pull at the time you issued this command. If you then decide
4698 to add those changes to the repository, you should use :hg:`pull
4698 to add those changes to the repository, you should use :hg:`pull
4699 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4699 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4700
4700
4701 If SOURCE is omitted, the 'default' path will be used.
4701 If SOURCE is omitted, the 'default' path will be used.
4702 See :hg:`help urls` for more information.
4702 See :hg:`help urls` for more information.
4703
4703
4704 Returns 0 on success, 1 if an update had unresolved files.
4704 Returns 0 on success, 1 if an update had unresolved files.
4705 """
4705 """
4706 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4706 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4707 other = hg.peer(repo, opts, source)
4707 other = hg.peer(repo, opts, source)
4708 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4708 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4709 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4709 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4710
4710
4711 if opts.get('bookmark'):
4711 if opts.get('bookmark'):
4712 if not revs:
4712 if not revs:
4713 revs = []
4713 revs = []
4714 rb = other.listkeys('bookmarks')
4714 rb = other.listkeys('bookmarks')
4715 for b in opts['bookmark']:
4715 for b in opts['bookmark']:
4716 if b not in rb:
4716 if b not in rb:
4717 raise util.Abort(_('remote bookmark %s not found!') % b)
4717 raise util.Abort(_('remote bookmark %s not found!') % b)
4718 revs.append(rb[b])
4718 revs.append(rb[b])
4719
4719
4720 if revs:
4720 if revs:
4721 try:
4721 try:
4722 revs = [other.lookup(rev) for rev in revs]
4722 revs = [other.lookup(rev) for rev in revs]
4723 except error.CapabilityError:
4723 except error.CapabilityError:
4724 err = _("other repository doesn't support revision lookup, "
4724 err = _("other repository doesn't support revision lookup, "
4725 "so a rev cannot be specified.")
4725 "so a rev cannot be specified.")
4726 raise util.Abort(err)
4726 raise util.Abort(err)
4727
4727
4728 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4728 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4729 bookmarks.updatefromremote(ui, repo, other, source)
4729 bookmarks.updatefromremote(ui, repo, other, source)
4730 if checkout:
4730 if checkout:
4731 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4731 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4732 repo._subtoppath = source
4732 repo._subtoppath = source
4733 try:
4733 try:
4734 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4734 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4735
4735
4736 finally:
4736 finally:
4737 del repo._subtoppath
4737 del repo._subtoppath
4738
4738
4739 # update specified bookmarks
4739 # update specified bookmarks
4740 if opts.get('bookmark'):
4740 if opts.get('bookmark'):
4741 marks = repo._bookmarks
4741 marks = repo._bookmarks
4742 for b in opts['bookmark']:
4742 for b in opts['bookmark']:
4743 # explicit pull overrides local bookmark if any
4743 # explicit pull overrides local bookmark if any
4744 ui.status(_("importing bookmark %s\n") % b)
4744 ui.status(_("importing bookmark %s\n") % b)
4745 marks[b] = repo[rb[b]].node()
4745 marks[b] = repo[rb[b]].node()
4746 marks.write()
4746 marks.write()
4747
4747
4748 return ret
4748 return ret
4749
4749
4750 @command('^push',
4750 @command('^push',
4751 [('f', 'force', None, _('force push')),
4751 [('f', 'force', None, _('force push')),
4752 ('r', 'rev', [],
4752 ('r', 'rev', [],
4753 _('a changeset intended to be included in the destination'),
4753 _('a changeset intended to be included in the destination'),
4754 _('REV')),
4754 _('REV')),
4755 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4755 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4756 ('b', 'branch', [],
4756 ('b', 'branch', [],
4757 _('a specific branch you would like to push'), _('BRANCH')),
4757 _('a specific branch you would like to push'), _('BRANCH')),
4758 ('', 'new-branch', False, _('allow pushing a new branch')),
4758 ('', 'new-branch', False, _('allow pushing a new branch')),
4759 ] + remoteopts,
4759 ] + remoteopts,
4760 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4760 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4761 def push(ui, repo, dest=None, **opts):
4761 def push(ui, repo, dest=None, **opts):
4762 """push changes to the specified destination
4762 """push changes to the specified destination
4763
4763
4764 Push changesets from the local repository to the specified
4764 Push changesets from the local repository to the specified
4765 destination.
4765 destination.
4766
4766
4767 This operation is symmetrical to pull: it is identical to a pull
4767 This operation is symmetrical to pull: it is identical to a pull
4768 in the destination repository from the current one.
4768 in the destination repository from the current one.
4769
4769
4770 By default, push will not allow creation of new heads at the
4770 By default, push will not allow creation of new heads at the
4771 destination, since multiple heads would make it unclear which head
4771 destination, since multiple heads would make it unclear which head
4772 to use. In this situation, it is recommended to pull and merge
4772 to use. In this situation, it is recommended to pull and merge
4773 before pushing.
4773 before pushing.
4774
4774
4775 Use --new-branch if you want to allow push to create a new named
4775 Use --new-branch if you want to allow push to create a new named
4776 branch that is not present at the destination. This allows you to
4776 branch that is not present at the destination. This allows you to
4777 only create a new branch without forcing other changes.
4777 only create a new branch without forcing other changes.
4778
4778
4779 Use -f/--force to override the default behavior and push all
4779 Use -f/--force to override the default behavior and push all
4780 changesets on all branches.
4780 changesets on all branches.
4781
4781
4782 If -r/--rev is used, the specified revision and all its ancestors
4782 If -r/--rev is used, the specified revision and all its ancestors
4783 will be pushed to the remote repository.
4783 will be pushed to the remote repository.
4784
4784
4785 If -B/--bookmark is used, the specified bookmarked revision, its
4785 If -B/--bookmark is used, the specified bookmarked revision, its
4786 ancestors, and the bookmark will be pushed to the remote
4786 ancestors, and the bookmark will be pushed to the remote
4787 repository.
4787 repository.
4788
4788
4789 Please see :hg:`help urls` for important details about ``ssh://``
4789 Please see :hg:`help urls` for important details about ``ssh://``
4790 URLs. If DESTINATION is omitted, a default path will be used.
4790 URLs. If DESTINATION is omitted, a default path will be used.
4791
4791
4792 Returns 0 if push was successful, 1 if nothing to push.
4792 Returns 0 if push was successful, 1 if nothing to push.
4793 """
4793 """
4794
4794
4795 if opts.get('bookmark'):
4795 if opts.get('bookmark'):
4796 for b in opts['bookmark']:
4796 for b in opts['bookmark']:
4797 # translate -B options to -r so changesets get pushed
4797 # translate -B options to -r so changesets get pushed
4798 if b in repo._bookmarks:
4798 if b in repo._bookmarks:
4799 opts.setdefault('rev', []).append(b)
4799 opts.setdefault('rev', []).append(b)
4800 else:
4800 else:
4801 # if we try to push a deleted bookmark, translate it to null
4801 # if we try to push a deleted bookmark, translate it to null
4802 # this lets simultaneous -r, -b options continue working
4802 # this lets simultaneous -r, -b options continue working
4803 opts.setdefault('rev', []).append("null")
4803 opts.setdefault('rev', []).append("null")
4804
4804
4805 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4805 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4806 dest, branches = hg.parseurl(dest, opts.get('branch'))
4806 dest, branches = hg.parseurl(dest, opts.get('branch'))
4807 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4807 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4808 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4808 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4809 other = hg.peer(repo, opts, dest)
4809 other = hg.peer(repo, opts, dest)
4810 if revs:
4810 if revs:
4811 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4811 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4812
4812
4813 repo._subtoppath = dest
4813 repo._subtoppath = dest
4814 try:
4814 try:
4815 # push subrepos depth-first for coherent ordering
4815 # push subrepos depth-first for coherent ordering
4816 c = repo['']
4816 c = repo['']
4817 subs = c.substate # only repos that are committed
4817 subs = c.substate # only repos that are committed
4818 for s in sorted(subs):
4818 for s in sorted(subs):
4819 if c.sub(s).push(opts) == 0:
4819 if c.sub(s).push(opts) == 0:
4820 return False
4820 return False
4821 finally:
4821 finally:
4822 del repo._subtoppath
4822 del repo._subtoppath
4823 result = repo.push(other, opts.get('force'), revs=revs,
4823 result = repo.push(other, opts.get('force'), revs=revs,
4824 newbranch=opts.get('new_branch'))
4824 newbranch=opts.get('new_branch'))
4825
4825
4826 result = not result
4826 result = not result
4827
4827
4828 if opts.get('bookmark'):
4828 if opts.get('bookmark'):
4829 rb = other.listkeys('bookmarks')
4829 rb = other.listkeys('bookmarks')
4830 for b in opts['bookmark']:
4830 for b in opts['bookmark']:
4831 # explicit push overrides remote bookmark if any
4831 # explicit push overrides remote bookmark if any
4832 if b in repo._bookmarks:
4832 if b in repo._bookmarks:
4833 ui.status(_("exporting bookmark %s\n") % b)
4833 ui.status(_("exporting bookmark %s\n") % b)
4834 new = repo[b].hex()
4834 new = repo[b].hex()
4835 elif b in rb:
4835 elif b in rb:
4836 ui.status(_("deleting remote bookmark %s\n") % b)
4836 ui.status(_("deleting remote bookmark %s\n") % b)
4837 new = '' # delete
4837 new = '' # delete
4838 else:
4838 else:
4839 ui.warn(_('bookmark %s does not exist on the local '
4839 ui.warn(_('bookmark %s does not exist on the local '
4840 'or remote repository!\n') % b)
4840 'or remote repository!\n') % b)
4841 return 2
4841 return 2
4842 old = rb.get(b, '')
4842 old = rb.get(b, '')
4843 r = other.pushkey('bookmarks', b, old, new)
4843 r = other.pushkey('bookmarks', b, old, new)
4844 if not r:
4844 if not r:
4845 ui.warn(_('updating bookmark %s failed!\n') % b)
4845 ui.warn(_('updating bookmark %s failed!\n') % b)
4846 if not result:
4846 if not result:
4847 result = 2
4847 result = 2
4848
4848
4849 return result
4849 return result
4850
4850
4851 @command('recover', [])
4851 @command('recover', [])
4852 def recover(ui, repo):
4852 def recover(ui, repo):
4853 """roll back an interrupted transaction
4853 """roll back an interrupted transaction
4854
4854
4855 Recover from an interrupted commit or pull.
4855 Recover from an interrupted commit or pull.
4856
4856
4857 This command tries to fix the repository status after an
4857 This command tries to fix the repository status after an
4858 interrupted operation. It should only be necessary when Mercurial
4858 interrupted operation. It should only be necessary when Mercurial
4859 suggests it.
4859 suggests it.
4860
4860
4861 Returns 0 if successful, 1 if nothing to recover or verify fails.
4861 Returns 0 if successful, 1 if nothing to recover or verify fails.
4862 """
4862 """
4863 if repo.recover():
4863 if repo.recover():
4864 return hg.verify(repo)
4864 return hg.verify(repo)
4865 return 1
4865 return 1
4866
4866
4867 @command('^remove|rm',
4867 @command('^remove|rm',
4868 [('A', 'after', None, _('record delete for missing files')),
4868 [('A', 'after', None, _('record delete for missing files')),
4869 ('f', 'force', None,
4869 ('f', 'force', None,
4870 _('remove (and delete) file even if added or modified')),
4870 _('remove (and delete) file even if added or modified')),
4871 ] + walkopts,
4871 ] + walkopts,
4872 _('[OPTION]... FILE...'))
4872 _('[OPTION]... FILE...'))
4873 def remove(ui, repo, *pats, **opts):
4873 def remove(ui, repo, *pats, **opts):
4874 """remove the specified files on the next commit
4874 """remove the specified files on the next commit
4875
4875
4876 Schedule the indicated files for removal from the current branch.
4876 Schedule the indicated files for removal from the current branch.
4877
4877
4878 This command schedules the files to be removed at the next commit.
4878 This command schedules the files to be removed at the next commit.
4879 To undo a remove before that, see :hg:`revert`. To undo added
4879 To undo a remove before that, see :hg:`revert`. To undo added
4880 files, see :hg:`forget`.
4880 files, see :hg:`forget`.
4881
4881
4882 .. container:: verbose
4882 .. container:: verbose
4883
4883
4884 -A/--after can be used to remove only files that have already
4884 -A/--after can be used to remove only files that have already
4885 been deleted, -f/--force can be used to force deletion, and -Af
4885 been deleted, -f/--force can be used to force deletion, and -Af
4886 can be used to remove files from the next revision without
4886 can be used to remove files from the next revision without
4887 deleting them from the working directory.
4887 deleting them from the working directory.
4888
4888
4889 The following table details the behavior of remove for different
4889 The following table details the behavior of remove for different
4890 file states (columns) and option combinations (rows). The file
4890 file states (columns) and option combinations (rows). The file
4891 states are Added [A], Clean [C], Modified [M] and Missing [!]
4891 states are Added [A], Clean [C], Modified [M] and Missing [!]
4892 (as reported by :hg:`status`). The actions are Warn, Remove
4892 (as reported by :hg:`status`). The actions are Warn, Remove
4893 (from branch) and Delete (from disk):
4893 (from branch) and Delete (from disk):
4894
4894
4895 ======= == == == ==
4895 ======= == == == ==
4896 A C M !
4896 A C M !
4897 ======= == == == ==
4897 ======= == == == ==
4898 none W RD W R
4898 none W RD W R
4899 -f R RD RD R
4899 -f R RD RD R
4900 -A W W W R
4900 -A W W W R
4901 -Af R R R R
4901 -Af R R R R
4902 ======= == == == ==
4902 ======= == == == ==
4903
4903
4904 Note that remove never deletes files in Added [A] state from the
4904 Note that remove never deletes files in Added [A] state from the
4905 working directory, not even if option --force is specified.
4905 working directory, not even if option --force is specified.
4906
4906
4907 Returns 0 on success, 1 if any warnings encountered.
4907 Returns 0 on success, 1 if any warnings encountered.
4908 """
4908 """
4909
4909
4910 ret = 0
4910 ret = 0
4911 after, force = opts.get('after'), opts.get('force')
4911 after, force = opts.get('after'), opts.get('force')
4912 if not pats and not after:
4912 if not pats and not after:
4913 raise util.Abort(_('no files specified'))
4913 raise util.Abort(_('no files specified'))
4914
4914
4915 m = scmutil.match(repo[None], pats, opts)
4915 m = scmutil.match(repo[None], pats, opts)
4916 s = repo.status(match=m, clean=True)
4916 s = repo.status(match=m, clean=True)
4917 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4917 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4918
4918
4919 # warn about failure to delete explicit files/dirs
4919 # warn about failure to delete explicit files/dirs
4920 wctx = repo[None]
4920 wctx = repo[None]
4921 for f in m.files():
4921 for f in m.files():
4922 if f in repo.dirstate or f in wctx.dirs():
4922 if f in repo.dirstate or f in wctx.dirs():
4923 continue
4923 continue
4924 if os.path.exists(m.rel(f)):
4924 if os.path.exists(m.rel(f)):
4925 if os.path.isdir(m.rel(f)):
4925 if os.path.isdir(m.rel(f)):
4926 ui.warn(_('not removing %s: no tracked files\n') % m.rel(f))
4926 ui.warn(_('not removing %s: no tracked files\n') % m.rel(f))
4927 else:
4927 else:
4928 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4928 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4929 # missing files will generate a warning elsewhere
4929 # missing files will generate a warning elsewhere
4930 ret = 1
4930 ret = 1
4931
4931
4932 if force:
4932 if force:
4933 list = modified + deleted + clean + added
4933 list = modified + deleted + clean + added
4934 elif after:
4934 elif after:
4935 list = deleted
4935 list = deleted
4936 for f in modified + added + clean:
4936 for f in modified + added + clean:
4937 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
4937 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
4938 ret = 1
4938 ret = 1
4939 else:
4939 else:
4940 list = deleted + clean
4940 list = deleted + clean
4941 for f in modified:
4941 for f in modified:
4942 ui.warn(_('not removing %s: file is modified (use -f'
4942 ui.warn(_('not removing %s: file is modified (use -f'
4943 ' to force removal)\n') % m.rel(f))
4943 ' to force removal)\n') % m.rel(f))
4944 ret = 1
4944 ret = 1
4945 for f in added:
4945 for f in added:
4946 ui.warn(_('not removing %s: file has been marked for add'
4946 ui.warn(_('not removing %s: file has been marked for add'
4947 ' (use forget to undo)\n') % m.rel(f))
4947 ' (use forget to undo)\n') % m.rel(f))
4948 ret = 1
4948 ret = 1
4949
4949
4950 for f in sorted(list):
4950 for f in sorted(list):
4951 if ui.verbose or not m.exact(f):
4951 if ui.verbose or not m.exact(f):
4952 ui.status(_('removing %s\n') % m.rel(f))
4952 ui.status(_('removing %s\n') % m.rel(f))
4953
4953
4954 wlock = repo.wlock()
4954 wlock = repo.wlock()
4955 try:
4955 try:
4956 if not after:
4956 if not after:
4957 for f in list:
4957 for f in list:
4958 if f in added:
4958 if f in added:
4959 continue # we never unlink added files on remove
4959 continue # we never unlink added files on remove
4960 try:
4960 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
4961 util.unlinkpath(repo.wjoin(f))
4962 except OSError, inst:
4963 if inst.errno != errno.ENOENT:
4964 raise
4965 repo[None].forget(list)
4961 repo[None].forget(list)
4966 finally:
4962 finally:
4967 wlock.release()
4963 wlock.release()
4968
4964
4969 return ret
4965 return ret
4970
4966
4971 @command('rename|move|mv',
4967 @command('rename|move|mv',
4972 [('A', 'after', None, _('record a rename that has already occurred')),
4968 [('A', 'after', None, _('record a rename that has already occurred')),
4973 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4969 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4974 ] + walkopts + dryrunopts,
4970 ] + walkopts + dryrunopts,
4975 _('[OPTION]... SOURCE... DEST'))
4971 _('[OPTION]... SOURCE... DEST'))
4976 def rename(ui, repo, *pats, **opts):
4972 def rename(ui, repo, *pats, **opts):
4977 """rename files; equivalent of copy + remove
4973 """rename files; equivalent of copy + remove
4978
4974
4979 Mark dest as copies of sources; mark sources for deletion. If dest
4975 Mark dest as copies of sources; mark sources for deletion. If dest
4980 is a directory, copies are put in that directory. If dest is a
4976 is a directory, copies are put in that directory. If dest is a
4981 file, there can only be one source.
4977 file, there can only be one source.
4982
4978
4983 By default, this command copies the contents of files as they
4979 By default, this command copies the contents of files as they
4984 exist in the working directory. If invoked with -A/--after, the
4980 exist in the working directory. If invoked with -A/--after, the
4985 operation is recorded, but no copying is performed.
4981 operation is recorded, but no copying is performed.
4986
4982
4987 This command takes effect at the next commit. To undo a rename
4983 This command takes effect at the next commit. To undo a rename
4988 before that, see :hg:`revert`.
4984 before that, see :hg:`revert`.
4989
4985
4990 Returns 0 on success, 1 if errors are encountered.
4986 Returns 0 on success, 1 if errors are encountered.
4991 """
4987 """
4992 wlock = repo.wlock(False)
4988 wlock = repo.wlock(False)
4993 try:
4989 try:
4994 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4990 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4995 finally:
4991 finally:
4996 wlock.release()
4992 wlock.release()
4997
4993
4998 @command('resolve',
4994 @command('resolve',
4999 [('a', 'all', None, _('select all unresolved files')),
4995 [('a', 'all', None, _('select all unresolved files')),
5000 ('l', 'list', None, _('list state of files needing merge')),
4996 ('l', 'list', None, _('list state of files needing merge')),
5001 ('m', 'mark', None, _('mark files as resolved')),
4997 ('m', 'mark', None, _('mark files as resolved')),
5002 ('u', 'unmark', None, _('mark files as unresolved')),
4998 ('u', 'unmark', None, _('mark files as unresolved')),
5003 ('n', 'no-status', None, _('hide status prefix'))]
4999 ('n', 'no-status', None, _('hide status prefix'))]
5004 + mergetoolopts + walkopts,
5000 + mergetoolopts + walkopts,
5005 _('[OPTION]... [FILE]...'))
5001 _('[OPTION]... [FILE]...'))
5006 def resolve(ui, repo, *pats, **opts):
5002 def resolve(ui, repo, *pats, **opts):
5007 """redo merges or set/view the merge status of files
5003 """redo merges or set/view the merge status of files
5008
5004
5009 Merges with unresolved conflicts are often the result of
5005 Merges with unresolved conflicts are often the result of
5010 non-interactive merging using the ``internal:merge`` configuration
5006 non-interactive merging using the ``internal:merge`` configuration
5011 setting, or a command-line merge tool like ``diff3``. The resolve
5007 setting, or a command-line merge tool like ``diff3``. The resolve
5012 command is used to manage the files involved in a merge, after
5008 command is used to manage the files involved in a merge, after
5013 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5009 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5014 working directory must have two parents). See :hg:`help
5010 working directory must have two parents). See :hg:`help
5015 merge-tools` for information on configuring merge tools.
5011 merge-tools` for information on configuring merge tools.
5016
5012
5017 The resolve command can be used in the following ways:
5013 The resolve command can be used in the following ways:
5018
5014
5019 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
5015 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
5020 files, discarding any previous merge attempts. Re-merging is not
5016 files, discarding any previous merge attempts. Re-merging is not
5021 performed for files already marked as resolved. Use ``--all/-a``
5017 performed for files already marked as resolved. Use ``--all/-a``
5022 to select all unresolved files. ``--tool`` can be used to specify
5018 to select all unresolved files. ``--tool`` can be used to specify
5023 the merge tool used for the given files. It overrides the HGMERGE
5019 the merge tool used for the given files. It overrides the HGMERGE
5024 environment variable and your configuration files. Previous file
5020 environment variable and your configuration files. Previous file
5025 contents are saved with a ``.orig`` suffix.
5021 contents are saved with a ``.orig`` suffix.
5026
5022
5027 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5023 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5028 (e.g. after having manually fixed-up the files). The default is
5024 (e.g. after having manually fixed-up the files). The default is
5029 to mark all unresolved files.
5025 to mark all unresolved files.
5030
5026
5031 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5027 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5032 default is to mark all resolved files.
5028 default is to mark all resolved files.
5033
5029
5034 - :hg:`resolve -l`: list files which had or still have conflicts.
5030 - :hg:`resolve -l`: list files which had or still have conflicts.
5035 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5031 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5036
5032
5037 Note that Mercurial will not let you commit files with unresolved
5033 Note that Mercurial will not let you commit files with unresolved
5038 merge conflicts. You must use :hg:`resolve -m ...` before you can
5034 merge conflicts. You must use :hg:`resolve -m ...` before you can
5039 commit after a conflicting merge.
5035 commit after a conflicting merge.
5040
5036
5041 Returns 0 on success, 1 if any files fail a resolve attempt.
5037 Returns 0 on success, 1 if any files fail a resolve attempt.
5042 """
5038 """
5043
5039
5044 all, mark, unmark, show, nostatus = \
5040 all, mark, unmark, show, nostatus = \
5045 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
5041 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
5046
5042
5047 if (show and (mark or unmark)) or (mark and unmark):
5043 if (show and (mark or unmark)) or (mark and unmark):
5048 raise util.Abort(_("too many options specified"))
5044 raise util.Abort(_("too many options specified"))
5049 if pats and all:
5045 if pats and all:
5050 raise util.Abort(_("can't specify --all and patterns"))
5046 raise util.Abort(_("can't specify --all and patterns"))
5051 if not (all or pats or show or mark or unmark):
5047 if not (all or pats or show or mark or unmark):
5052 raise util.Abort(_('no files or directories specified; '
5048 raise util.Abort(_('no files or directories specified; '
5053 'use --all to remerge all files'))
5049 'use --all to remerge all files'))
5054
5050
5055 ms = mergemod.mergestate(repo)
5051 ms = mergemod.mergestate(repo)
5056 m = scmutil.match(repo[None], pats, opts)
5052 m = scmutil.match(repo[None], pats, opts)
5057 ret = 0
5053 ret = 0
5058
5054
5059 for f in ms:
5055 for f in ms:
5060 if m(f):
5056 if m(f):
5061 if show:
5057 if show:
5062 if nostatus:
5058 if nostatus:
5063 ui.write("%s\n" % f)
5059 ui.write("%s\n" % f)
5064 else:
5060 else:
5065 ui.write("%s %s\n" % (ms[f].upper(), f),
5061 ui.write("%s %s\n" % (ms[f].upper(), f),
5066 label='resolve.' +
5062 label='resolve.' +
5067 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
5063 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
5068 elif mark:
5064 elif mark:
5069 ms.mark(f, "r")
5065 ms.mark(f, "r")
5070 elif unmark:
5066 elif unmark:
5071 ms.mark(f, "u")
5067 ms.mark(f, "u")
5072 else:
5068 else:
5073 wctx = repo[None]
5069 wctx = repo[None]
5074 mctx = wctx.parents()[-1]
5070 mctx = wctx.parents()[-1]
5075
5071
5076 # backup pre-resolve (merge uses .orig for its own purposes)
5072 # backup pre-resolve (merge uses .orig for its own purposes)
5077 a = repo.wjoin(f)
5073 a = repo.wjoin(f)
5078 util.copyfile(a, a + ".resolve")
5074 util.copyfile(a, a + ".resolve")
5079
5075
5080 try:
5076 try:
5081 # resolve file
5077 # resolve file
5082 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
5078 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
5083 if ms.resolve(f, wctx, mctx):
5079 if ms.resolve(f, wctx, mctx):
5084 ret = 1
5080 ret = 1
5085 finally:
5081 finally:
5086 ui.setconfig('ui', 'forcemerge', '')
5082 ui.setconfig('ui', 'forcemerge', '')
5087 ms.commit()
5083 ms.commit()
5088
5084
5089 # replace filemerge's .orig file with our resolve file
5085 # replace filemerge's .orig file with our resolve file
5090 util.rename(a + ".resolve", a + ".orig")
5086 util.rename(a + ".resolve", a + ".orig")
5091
5087
5092 ms.commit()
5088 ms.commit()
5093 return ret
5089 return ret
5094
5090
5095 @command('revert',
5091 @command('revert',
5096 [('a', 'all', None, _('revert all changes when no arguments given')),
5092 [('a', 'all', None, _('revert all changes when no arguments given')),
5097 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5093 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5098 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5094 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5099 ('C', 'no-backup', None, _('do not save backup copies of files')),
5095 ('C', 'no-backup', None, _('do not save backup copies of files')),
5100 ] + walkopts + dryrunopts,
5096 ] + walkopts + dryrunopts,
5101 _('[OPTION]... [-r REV] [NAME]...'))
5097 _('[OPTION]... [-r REV] [NAME]...'))
5102 def revert(ui, repo, *pats, **opts):
5098 def revert(ui, repo, *pats, **opts):
5103 """restore files to their checkout state
5099 """restore files to their checkout state
5104
5100
5105 .. note::
5101 .. note::
5106
5102
5107 To check out earlier revisions, you should use :hg:`update REV`.
5103 To check out earlier revisions, you should use :hg:`update REV`.
5108 To cancel an uncommitted merge (and lose your changes), use
5104 To cancel an uncommitted merge (and lose your changes), use
5109 :hg:`update --clean .`.
5105 :hg:`update --clean .`.
5110
5106
5111 With no revision specified, revert the specified files or directories
5107 With no revision specified, revert the specified files or directories
5112 to the contents they had in the parent of the working directory.
5108 to the contents they had in the parent of the working directory.
5113 This restores the contents of files to an unmodified
5109 This restores the contents of files to an unmodified
5114 state and unschedules adds, removes, copies, and renames. If the
5110 state and unschedules adds, removes, copies, and renames. If the
5115 working directory has two parents, you must explicitly specify a
5111 working directory has two parents, you must explicitly specify a
5116 revision.
5112 revision.
5117
5113
5118 Using the -r/--rev or -d/--date options, revert the given files or
5114 Using the -r/--rev or -d/--date options, revert the given files or
5119 directories to their states as of a specific revision. Because
5115 directories to their states as of a specific revision. Because
5120 revert does not change the working directory parents, this will
5116 revert does not change the working directory parents, this will
5121 cause these files to appear modified. This can be helpful to "back
5117 cause these files to appear modified. This can be helpful to "back
5122 out" some or all of an earlier change. See :hg:`backout` for a
5118 out" some or all of an earlier change. See :hg:`backout` for a
5123 related method.
5119 related method.
5124
5120
5125 Modified files are saved with a .orig suffix before reverting.
5121 Modified files are saved with a .orig suffix before reverting.
5126 To disable these backups, use --no-backup.
5122 To disable these backups, use --no-backup.
5127
5123
5128 See :hg:`help dates` for a list of formats valid for -d/--date.
5124 See :hg:`help dates` for a list of formats valid for -d/--date.
5129
5125
5130 Returns 0 on success.
5126 Returns 0 on success.
5131 """
5127 """
5132
5128
5133 if opts.get("date"):
5129 if opts.get("date"):
5134 if opts.get("rev"):
5130 if opts.get("rev"):
5135 raise util.Abort(_("you can't specify a revision and a date"))
5131 raise util.Abort(_("you can't specify a revision and a date"))
5136 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5132 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5137
5133
5138 parent, p2 = repo.dirstate.parents()
5134 parent, p2 = repo.dirstate.parents()
5139 if not opts.get('rev') and p2 != nullid:
5135 if not opts.get('rev') and p2 != nullid:
5140 # revert after merge is a trap for new users (issue2915)
5136 # revert after merge is a trap for new users (issue2915)
5141 raise util.Abort(_('uncommitted merge with no revision specified'),
5137 raise util.Abort(_('uncommitted merge with no revision specified'),
5142 hint=_('use "hg update" or see "hg help revert"'))
5138 hint=_('use "hg update" or see "hg help revert"'))
5143
5139
5144 ctx = scmutil.revsingle(repo, opts.get('rev'))
5140 ctx = scmutil.revsingle(repo, opts.get('rev'))
5145
5141
5146 if not pats and not opts.get('all'):
5142 if not pats and not opts.get('all'):
5147 msg = _("no files or directories specified")
5143 msg = _("no files or directories specified")
5148 if p2 != nullid:
5144 if p2 != nullid:
5149 hint = _("uncommitted merge, use --all to discard all changes,"
5145 hint = _("uncommitted merge, use --all to discard all changes,"
5150 " or 'hg update -C .' to abort the merge")
5146 " or 'hg update -C .' to abort the merge")
5151 raise util.Abort(msg, hint=hint)
5147 raise util.Abort(msg, hint=hint)
5152 dirty = util.any(repo.status())
5148 dirty = util.any(repo.status())
5153 node = ctx.node()
5149 node = ctx.node()
5154 if node != parent:
5150 if node != parent:
5155 if dirty:
5151 if dirty:
5156 hint = _("uncommitted changes, use --all to discard all"
5152 hint = _("uncommitted changes, use --all to discard all"
5157 " changes, or 'hg update %s' to update") % ctx.rev()
5153 " changes, or 'hg update %s' to update") % ctx.rev()
5158 else:
5154 else:
5159 hint = _("use --all to revert all files,"
5155 hint = _("use --all to revert all files,"
5160 " or 'hg update %s' to update") % ctx.rev()
5156 " or 'hg update %s' to update") % ctx.rev()
5161 elif dirty:
5157 elif dirty:
5162 hint = _("uncommitted changes, use --all to discard all changes")
5158 hint = _("uncommitted changes, use --all to discard all changes")
5163 else:
5159 else:
5164 hint = _("use --all to revert all files")
5160 hint = _("use --all to revert all files")
5165 raise util.Abort(msg, hint=hint)
5161 raise util.Abort(msg, hint=hint)
5166
5162
5167 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5163 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5168
5164
5169 @command('rollback', dryrunopts +
5165 @command('rollback', dryrunopts +
5170 [('f', 'force', False, _('ignore safety measures'))])
5166 [('f', 'force', False, _('ignore safety measures'))])
5171 def rollback(ui, repo, **opts):
5167 def rollback(ui, repo, **opts):
5172 """roll back the last transaction (dangerous)
5168 """roll back the last transaction (dangerous)
5173
5169
5174 This command should be used with care. There is only one level of
5170 This command should be used with care. There is only one level of
5175 rollback, and there is no way to undo a rollback. It will also
5171 rollback, and there is no way to undo a rollback. It will also
5176 restore the dirstate at the time of the last transaction, losing
5172 restore the dirstate at the time of the last transaction, losing
5177 any dirstate changes since that time. This command does not alter
5173 any dirstate changes since that time. This command does not alter
5178 the working directory.
5174 the working directory.
5179
5175
5180 Transactions are used to encapsulate the effects of all commands
5176 Transactions are used to encapsulate the effects of all commands
5181 that create new changesets or propagate existing changesets into a
5177 that create new changesets or propagate existing changesets into a
5182 repository.
5178 repository.
5183
5179
5184 .. container:: verbose
5180 .. container:: verbose
5185
5181
5186 For example, the following commands are transactional, and their
5182 For example, the following commands are transactional, and their
5187 effects can be rolled back:
5183 effects can be rolled back:
5188
5184
5189 - commit
5185 - commit
5190 - import
5186 - import
5191 - pull
5187 - pull
5192 - push (with this repository as the destination)
5188 - push (with this repository as the destination)
5193 - unbundle
5189 - unbundle
5194
5190
5195 To avoid permanent data loss, rollback will refuse to rollback a
5191 To avoid permanent data loss, rollback will refuse to rollback a
5196 commit transaction if it isn't checked out. Use --force to
5192 commit transaction if it isn't checked out. Use --force to
5197 override this protection.
5193 override this protection.
5198
5194
5199 This command is not intended for use on public repositories. Once
5195 This command is not intended for use on public repositories. Once
5200 changes are visible for pull by other users, rolling a transaction
5196 changes are visible for pull by other users, rolling a transaction
5201 back locally is ineffective (someone else may already have pulled
5197 back locally is ineffective (someone else may already have pulled
5202 the changes). Furthermore, a race is possible with readers of the
5198 the changes). Furthermore, a race is possible with readers of the
5203 repository; for example an in-progress pull from the repository
5199 repository; for example an in-progress pull from the repository
5204 may fail if a rollback is performed.
5200 may fail if a rollback is performed.
5205
5201
5206 Returns 0 on success, 1 if no rollback data is available.
5202 Returns 0 on success, 1 if no rollback data is available.
5207 """
5203 """
5208 return repo.rollback(dryrun=opts.get('dry_run'),
5204 return repo.rollback(dryrun=opts.get('dry_run'),
5209 force=opts.get('force'))
5205 force=opts.get('force'))
5210
5206
5211 @command('root', [])
5207 @command('root', [])
5212 def root(ui, repo):
5208 def root(ui, repo):
5213 """print the root (top) of the current working directory
5209 """print the root (top) of the current working directory
5214
5210
5215 Print the root directory of the current repository.
5211 Print the root directory of the current repository.
5216
5212
5217 Returns 0 on success.
5213 Returns 0 on success.
5218 """
5214 """
5219 ui.write(repo.root + "\n")
5215 ui.write(repo.root + "\n")
5220
5216
5221 @command('^serve',
5217 @command('^serve',
5222 [('A', 'accesslog', '', _('name of access log file to write to'),
5218 [('A', 'accesslog', '', _('name of access log file to write to'),
5223 _('FILE')),
5219 _('FILE')),
5224 ('d', 'daemon', None, _('run server in background')),
5220 ('d', 'daemon', None, _('run server in background')),
5225 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
5221 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
5226 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5222 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5227 # use string type, then we can check if something was passed
5223 # use string type, then we can check if something was passed
5228 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5224 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5229 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5225 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5230 _('ADDR')),
5226 _('ADDR')),
5231 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5227 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5232 _('PREFIX')),
5228 _('PREFIX')),
5233 ('n', 'name', '',
5229 ('n', 'name', '',
5234 _('name to show in web pages (default: working directory)'), _('NAME')),
5230 _('name to show in web pages (default: working directory)'), _('NAME')),
5235 ('', 'web-conf', '',
5231 ('', 'web-conf', '',
5236 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5232 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5237 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5233 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5238 _('FILE')),
5234 _('FILE')),
5239 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5235 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5240 ('', 'stdio', None, _('for remote clients')),
5236 ('', 'stdio', None, _('for remote clients')),
5241 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5237 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5242 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5238 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5243 ('', 'style', '', _('template style to use'), _('STYLE')),
5239 ('', 'style', '', _('template style to use'), _('STYLE')),
5244 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5240 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5245 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5241 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5246 _('[OPTION]...'))
5242 _('[OPTION]...'))
5247 def serve(ui, repo, **opts):
5243 def serve(ui, repo, **opts):
5248 """start stand-alone webserver
5244 """start stand-alone webserver
5249
5245
5250 Start a local HTTP repository browser and pull server. You can use
5246 Start a local HTTP repository browser and pull server. You can use
5251 this for ad-hoc sharing and browsing of repositories. It is
5247 this for ad-hoc sharing and browsing of repositories. It is
5252 recommended to use a real web server to serve a repository for
5248 recommended to use a real web server to serve a repository for
5253 longer periods of time.
5249 longer periods of time.
5254
5250
5255 Please note that the server does not implement access control.
5251 Please note that the server does not implement access control.
5256 This means that, by default, anybody can read from the server and
5252 This means that, by default, anybody can read from the server and
5257 nobody can write to it by default. Set the ``web.allow_push``
5253 nobody can write to it by default. Set the ``web.allow_push``
5258 option to ``*`` to allow everybody to push to the server. You
5254 option to ``*`` to allow everybody to push to the server. You
5259 should use a real web server if you need to authenticate users.
5255 should use a real web server if you need to authenticate users.
5260
5256
5261 By default, the server logs accesses to stdout and errors to
5257 By default, the server logs accesses to stdout and errors to
5262 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5258 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5263 files.
5259 files.
5264
5260
5265 To have the server choose a free port number to listen on, specify
5261 To have the server choose a free port number to listen on, specify
5266 a port number of 0; in this case, the server will print the port
5262 a port number of 0; in this case, the server will print the port
5267 number it uses.
5263 number it uses.
5268
5264
5269 Returns 0 on success.
5265 Returns 0 on success.
5270 """
5266 """
5271
5267
5272 if opts["stdio"] and opts["cmdserver"]:
5268 if opts["stdio"] and opts["cmdserver"]:
5273 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5269 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5274
5270
5275 def checkrepo():
5271 def checkrepo():
5276 if repo is None:
5272 if repo is None:
5277 raise error.RepoError(_("there is no Mercurial repository here"
5273 raise error.RepoError(_("there is no Mercurial repository here"
5278 " (.hg not found)"))
5274 " (.hg not found)"))
5279
5275
5280 if opts["stdio"]:
5276 if opts["stdio"]:
5281 checkrepo()
5277 checkrepo()
5282 s = sshserver.sshserver(ui, repo)
5278 s = sshserver.sshserver(ui, repo)
5283 s.serve_forever()
5279 s.serve_forever()
5284
5280
5285 if opts["cmdserver"]:
5281 if opts["cmdserver"]:
5286 checkrepo()
5282 checkrepo()
5287 s = commandserver.server(ui, repo, opts["cmdserver"])
5283 s = commandserver.server(ui, repo, opts["cmdserver"])
5288 return s.serve()
5284 return s.serve()
5289
5285
5290 # this way we can check if something was given in the command-line
5286 # this way we can check if something was given in the command-line
5291 if opts.get('port'):
5287 if opts.get('port'):
5292 opts['port'] = util.getport(opts.get('port'))
5288 opts['port'] = util.getport(opts.get('port'))
5293
5289
5294 baseui = repo and repo.baseui or ui
5290 baseui = repo and repo.baseui or ui
5295 optlist = ("name templates style address port prefix ipv6"
5291 optlist = ("name templates style address port prefix ipv6"
5296 " accesslog errorlog certificate encoding")
5292 " accesslog errorlog certificate encoding")
5297 for o in optlist.split():
5293 for o in optlist.split():
5298 val = opts.get(o, '')
5294 val = opts.get(o, '')
5299 if val in (None, ''): # should check against default options instead
5295 if val in (None, ''): # should check against default options instead
5300 continue
5296 continue
5301 baseui.setconfig("web", o, val)
5297 baseui.setconfig("web", o, val)
5302 if repo and repo.ui != baseui:
5298 if repo and repo.ui != baseui:
5303 repo.ui.setconfig("web", o, val)
5299 repo.ui.setconfig("web", o, val)
5304
5300
5305 o = opts.get('web_conf') or opts.get('webdir_conf')
5301 o = opts.get('web_conf') or opts.get('webdir_conf')
5306 if not o:
5302 if not o:
5307 if not repo:
5303 if not repo:
5308 raise error.RepoError(_("there is no Mercurial repository"
5304 raise error.RepoError(_("there is no Mercurial repository"
5309 " here (.hg not found)"))
5305 " here (.hg not found)"))
5310 o = repo.root
5306 o = repo.root
5311
5307
5312 app = hgweb.hgweb(o, baseui=ui)
5308 app = hgweb.hgweb(o, baseui=ui)
5313
5309
5314 class service(object):
5310 class service(object):
5315 def init(self):
5311 def init(self):
5316 util.setsignalhandler()
5312 util.setsignalhandler()
5317 self.httpd = hgweb.server.create_server(ui, app)
5313 self.httpd = hgweb.server.create_server(ui, app)
5318
5314
5319 if opts['port'] and not ui.verbose:
5315 if opts['port'] and not ui.verbose:
5320 return
5316 return
5321
5317
5322 if self.httpd.prefix:
5318 if self.httpd.prefix:
5323 prefix = self.httpd.prefix.strip('/') + '/'
5319 prefix = self.httpd.prefix.strip('/') + '/'
5324 else:
5320 else:
5325 prefix = ''
5321 prefix = ''
5326
5322
5327 port = ':%d' % self.httpd.port
5323 port = ':%d' % self.httpd.port
5328 if port == ':80':
5324 if port == ':80':
5329 port = ''
5325 port = ''
5330
5326
5331 bindaddr = self.httpd.addr
5327 bindaddr = self.httpd.addr
5332 if bindaddr == '0.0.0.0':
5328 if bindaddr == '0.0.0.0':
5333 bindaddr = '*'
5329 bindaddr = '*'
5334 elif ':' in bindaddr: # IPv6
5330 elif ':' in bindaddr: # IPv6
5335 bindaddr = '[%s]' % bindaddr
5331 bindaddr = '[%s]' % bindaddr
5336
5332
5337 fqaddr = self.httpd.fqaddr
5333 fqaddr = self.httpd.fqaddr
5338 if ':' in fqaddr:
5334 if ':' in fqaddr:
5339 fqaddr = '[%s]' % fqaddr
5335 fqaddr = '[%s]' % fqaddr
5340 if opts['port']:
5336 if opts['port']:
5341 write = ui.status
5337 write = ui.status
5342 else:
5338 else:
5343 write = ui.write
5339 write = ui.write
5344 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5340 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5345 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5341 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5346
5342
5347 def run(self):
5343 def run(self):
5348 self.httpd.serve_forever()
5344 self.httpd.serve_forever()
5349
5345
5350 service = service()
5346 service = service()
5351
5347
5352 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5348 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5353
5349
5354 @command('showconfig|debugconfig',
5350 @command('showconfig|debugconfig',
5355 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5351 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5356 _('[-u] [NAME]...'))
5352 _('[-u] [NAME]...'))
5357 def showconfig(ui, repo, *values, **opts):
5353 def showconfig(ui, repo, *values, **opts):
5358 """show combined config settings from all hgrc files
5354 """show combined config settings from all hgrc files
5359
5355
5360 With no arguments, print names and values of all config items.
5356 With no arguments, print names and values of all config items.
5361
5357
5362 With one argument of the form section.name, print just the value
5358 With one argument of the form section.name, print just the value
5363 of that config item.
5359 of that config item.
5364
5360
5365 With multiple arguments, print names and values of all config
5361 With multiple arguments, print names and values of all config
5366 items with matching section names.
5362 items with matching section names.
5367
5363
5368 With --debug, the source (filename and line number) is printed
5364 With --debug, the source (filename and line number) is printed
5369 for each config item.
5365 for each config item.
5370
5366
5371 Returns 0 on success.
5367 Returns 0 on success.
5372 """
5368 """
5373
5369
5374 for f in scmutil.rcpath():
5370 for f in scmutil.rcpath():
5375 ui.debug('read config from: %s\n' % f)
5371 ui.debug('read config from: %s\n' % f)
5376 untrusted = bool(opts.get('untrusted'))
5372 untrusted = bool(opts.get('untrusted'))
5377 if values:
5373 if values:
5378 sections = [v for v in values if '.' not in v]
5374 sections = [v for v in values if '.' not in v]
5379 items = [v for v in values if '.' in v]
5375 items = [v for v in values if '.' in v]
5380 if len(items) > 1 or items and sections:
5376 if len(items) > 1 or items and sections:
5381 raise util.Abort(_('only one config item permitted'))
5377 raise util.Abort(_('only one config item permitted'))
5382 for section, name, value in ui.walkconfig(untrusted=untrusted):
5378 for section, name, value in ui.walkconfig(untrusted=untrusted):
5383 value = str(value).replace('\n', '\\n')
5379 value = str(value).replace('\n', '\\n')
5384 sectname = section + '.' + name
5380 sectname = section + '.' + name
5385 if values:
5381 if values:
5386 for v in values:
5382 for v in values:
5387 if v == section:
5383 if v == section:
5388 ui.debug('%s: ' %
5384 ui.debug('%s: ' %
5389 ui.configsource(section, name, untrusted))
5385 ui.configsource(section, name, untrusted))
5390 ui.write('%s=%s\n' % (sectname, value))
5386 ui.write('%s=%s\n' % (sectname, value))
5391 elif v == sectname:
5387 elif v == sectname:
5392 ui.debug('%s: ' %
5388 ui.debug('%s: ' %
5393 ui.configsource(section, name, untrusted))
5389 ui.configsource(section, name, untrusted))
5394 ui.write(value, '\n')
5390 ui.write(value, '\n')
5395 else:
5391 else:
5396 ui.debug('%s: ' %
5392 ui.debug('%s: ' %
5397 ui.configsource(section, name, untrusted))
5393 ui.configsource(section, name, untrusted))
5398 ui.write('%s=%s\n' % (sectname, value))
5394 ui.write('%s=%s\n' % (sectname, value))
5399
5395
5400 @command('^status|st',
5396 @command('^status|st',
5401 [('A', 'all', None, _('show status of all files')),
5397 [('A', 'all', None, _('show status of all files')),
5402 ('m', 'modified', None, _('show only modified files')),
5398 ('m', 'modified', None, _('show only modified files')),
5403 ('a', 'added', None, _('show only added files')),
5399 ('a', 'added', None, _('show only added files')),
5404 ('r', 'removed', None, _('show only removed files')),
5400 ('r', 'removed', None, _('show only removed files')),
5405 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5401 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5406 ('c', 'clean', None, _('show only files without changes')),
5402 ('c', 'clean', None, _('show only files without changes')),
5407 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5403 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5408 ('i', 'ignored', None, _('show only ignored files')),
5404 ('i', 'ignored', None, _('show only ignored files')),
5409 ('n', 'no-status', None, _('hide status prefix')),
5405 ('n', 'no-status', None, _('hide status prefix')),
5410 ('C', 'copies', None, _('show source of copied files')),
5406 ('C', 'copies', None, _('show source of copied files')),
5411 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5407 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5412 ('', 'rev', [], _('show difference from revision'), _('REV')),
5408 ('', 'rev', [], _('show difference from revision'), _('REV')),
5413 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5409 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5414 ] + walkopts + subrepoopts,
5410 ] + walkopts + subrepoopts,
5415 _('[OPTION]... [FILE]...'))
5411 _('[OPTION]... [FILE]...'))
5416 def status(ui, repo, *pats, **opts):
5412 def status(ui, repo, *pats, **opts):
5417 """show changed files in the working directory
5413 """show changed files in the working directory
5418
5414
5419 Show status of files in the repository. If names are given, only
5415 Show status of files in the repository. If names are given, only
5420 files that match are shown. Files that are clean or ignored or
5416 files that match are shown. Files that are clean or ignored or
5421 the source of a copy/move operation, are not listed unless
5417 the source of a copy/move operation, are not listed unless
5422 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5418 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5423 Unless options described with "show only ..." are given, the
5419 Unless options described with "show only ..." are given, the
5424 options -mardu are used.
5420 options -mardu are used.
5425
5421
5426 Option -q/--quiet hides untracked (unknown and ignored) files
5422 Option -q/--quiet hides untracked (unknown and ignored) files
5427 unless explicitly requested with -u/--unknown or -i/--ignored.
5423 unless explicitly requested with -u/--unknown or -i/--ignored.
5428
5424
5429 .. note::
5425 .. note::
5430 status may appear to disagree with diff if permissions have
5426 status may appear to disagree with diff if permissions have
5431 changed or a merge has occurred. The standard diff format does
5427 changed or a merge has occurred. The standard diff format does
5432 not report permission changes and diff only reports changes
5428 not report permission changes and diff only reports changes
5433 relative to one merge parent.
5429 relative to one merge parent.
5434
5430
5435 If one revision is given, it is used as the base revision.
5431 If one revision is given, it is used as the base revision.
5436 If two revisions are given, the differences between them are
5432 If two revisions are given, the differences between them are
5437 shown. The --change option can also be used as a shortcut to list
5433 shown. The --change option can also be used as a shortcut to list
5438 the changed files of a revision from its first parent.
5434 the changed files of a revision from its first parent.
5439
5435
5440 The codes used to show the status of files are::
5436 The codes used to show the status of files are::
5441
5437
5442 M = modified
5438 M = modified
5443 A = added
5439 A = added
5444 R = removed
5440 R = removed
5445 C = clean
5441 C = clean
5446 ! = missing (deleted by non-hg command, but still tracked)
5442 ! = missing (deleted by non-hg command, but still tracked)
5447 ? = not tracked
5443 ? = not tracked
5448 I = ignored
5444 I = ignored
5449 = origin of the previous file listed as A (added)
5445 = origin of the previous file listed as A (added)
5450
5446
5451 .. container:: verbose
5447 .. container:: verbose
5452
5448
5453 Examples:
5449 Examples:
5454
5450
5455 - show changes in the working directory relative to a
5451 - show changes in the working directory relative to a
5456 changeset::
5452 changeset::
5457
5453
5458 hg status --rev 9353
5454 hg status --rev 9353
5459
5455
5460 - show all changes including copies in an existing changeset::
5456 - show all changes including copies in an existing changeset::
5461
5457
5462 hg status --copies --change 9353
5458 hg status --copies --change 9353
5463
5459
5464 - get a NUL separated list of added files, suitable for xargs::
5460 - get a NUL separated list of added files, suitable for xargs::
5465
5461
5466 hg status -an0
5462 hg status -an0
5467
5463
5468 Returns 0 on success.
5464 Returns 0 on success.
5469 """
5465 """
5470
5466
5471 revs = opts.get('rev')
5467 revs = opts.get('rev')
5472 change = opts.get('change')
5468 change = opts.get('change')
5473
5469
5474 if revs and change:
5470 if revs and change:
5475 msg = _('cannot specify --rev and --change at the same time')
5471 msg = _('cannot specify --rev and --change at the same time')
5476 raise util.Abort(msg)
5472 raise util.Abort(msg)
5477 elif change:
5473 elif change:
5478 node2 = scmutil.revsingle(repo, change, None).node()
5474 node2 = scmutil.revsingle(repo, change, None).node()
5479 node1 = repo[node2].p1().node()
5475 node1 = repo[node2].p1().node()
5480 else:
5476 else:
5481 node1, node2 = scmutil.revpair(repo, revs)
5477 node1, node2 = scmutil.revpair(repo, revs)
5482
5478
5483 cwd = (pats and repo.getcwd()) or ''
5479 cwd = (pats and repo.getcwd()) or ''
5484 end = opts.get('print0') and '\0' or '\n'
5480 end = opts.get('print0') and '\0' or '\n'
5485 copy = {}
5481 copy = {}
5486 states = 'modified added removed deleted unknown ignored clean'.split()
5482 states = 'modified added removed deleted unknown ignored clean'.split()
5487 show = [k for k in states if opts.get(k)]
5483 show = [k for k in states if opts.get(k)]
5488 if opts.get('all'):
5484 if opts.get('all'):
5489 show += ui.quiet and (states[:4] + ['clean']) or states
5485 show += ui.quiet and (states[:4] + ['clean']) or states
5490 if not show:
5486 if not show:
5491 show = ui.quiet and states[:4] or states[:5]
5487 show = ui.quiet and states[:4] or states[:5]
5492
5488
5493 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5489 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5494 'ignored' in show, 'clean' in show, 'unknown' in show,
5490 'ignored' in show, 'clean' in show, 'unknown' in show,
5495 opts.get('subrepos'))
5491 opts.get('subrepos'))
5496 changestates = zip(states, 'MAR!?IC', stat)
5492 changestates = zip(states, 'MAR!?IC', stat)
5497
5493
5498 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5494 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5499 copy = copies.pathcopies(repo[node1], repo[node2])
5495 copy = copies.pathcopies(repo[node1], repo[node2])
5500
5496
5501 fm = ui.formatter('status', opts)
5497 fm = ui.formatter('status', opts)
5502 fmt = '%s' + end
5498 fmt = '%s' + end
5503 showchar = not opts.get('no_status')
5499 showchar = not opts.get('no_status')
5504
5500
5505 for state, char, files in changestates:
5501 for state, char, files in changestates:
5506 if state in show:
5502 if state in show:
5507 label = 'status.' + state
5503 label = 'status.' + state
5508 for f in files:
5504 for f in files:
5509 fm.startitem()
5505 fm.startitem()
5510 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5506 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5511 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
5507 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
5512 if f in copy:
5508 if f in copy:
5513 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5509 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5514 label='status.copied')
5510 label='status.copied')
5515 fm.end()
5511 fm.end()
5516
5512
5517 @command('^summary|sum',
5513 @command('^summary|sum',
5518 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5514 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5519 def summary(ui, repo, **opts):
5515 def summary(ui, repo, **opts):
5520 """summarize working directory state
5516 """summarize working directory state
5521
5517
5522 This generates a brief summary of the working directory state,
5518 This generates a brief summary of the working directory state,
5523 including parents, branch, commit status, and available updates.
5519 including parents, branch, commit status, and available updates.
5524
5520
5525 With the --remote option, this will check the default paths for
5521 With the --remote option, this will check the default paths for
5526 incoming and outgoing changes. This can be time-consuming.
5522 incoming and outgoing changes. This can be time-consuming.
5527
5523
5528 Returns 0 on success.
5524 Returns 0 on success.
5529 """
5525 """
5530
5526
5531 ctx = repo[None]
5527 ctx = repo[None]
5532 parents = ctx.parents()
5528 parents = ctx.parents()
5533 pnode = parents[0].node()
5529 pnode = parents[0].node()
5534 marks = []
5530 marks = []
5535
5531
5536 for p in parents:
5532 for p in parents:
5537 # label with log.changeset (instead of log.parent) since this
5533 # label with log.changeset (instead of log.parent) since this
5538 # shows a working directory parent *changeset*:
5534 # shows a working directory parent *changeset*:
5539 # i18n: column positioning for "hg summary"
5535 # i18n: column positioning for "hg summary"
5540 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5536 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5541 label='log.changeset changeset.%s' % p.phasestr())
5537 label='log.changeset changeset.%s' % p.phasestr())
5542 ui.write(' '.join(p.tags()), label='log.tag')
5538 ui.write(' '.join(p.tags()), label='log.tag')
5543 if p.bookmarks():
5539 if p.bookmarks():
5544 marks.extend(p.bookmarks())
5540 marks.extend(p.bookmarks())
5545 if p.rev() == -1:
5541 if p.rev() == -1:
5546 if not len(repo):
5542 if not len(repo):
5547 ui.write(_(' (empty repository)'))
5543 ui.write(_(' (empty repository)'))
5548 else:
5544 else:
5549 ui.write(_(' (no revision checked out)'))
5545 ui.write(_(' (no revision checked out)'))
5550 ui.write('\n')
5546 ui.write('\n')
5551 if p.description():
5547 if p.description():
5552 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5548 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5553 label='log.summary')
5549 label='log.summary')
5554
5550
5555 branch = ctx.branch()
5551 branch = ctx.branch()
5556 bheads = repo.branchheads(branch)
5552 bheads = repo.branchheads(branch)
5557 # i18n: column positioning for "hg summary"
5553 # i18n: column positioning for "hg summary"
5558 m = _('branch: %s\n') % branch
5554 m = _('branch: %s\n') % branch
5559 if branch != 'default':
5555 if branch != 'default':
5560 ui.write(m, label='log.branch')
5556 ui.write(m, label='log.branch')
5561 else:
5557 else:
5562 ui.status(m, label='log.branch')
5558 ui.status(m, label='log.branch')
5563
5559
5564 if marks:
5560 if marks:
5565 current = repo._bookmarkcurrent
5561 current = repo._bookmarkcurrent
5566 # i18n: column positioning for "hg summary"
5562 # i18n: column positioning for "hg summary"
5567 ui.write(_('bookmarks:'), label='log.bookmark')
5563 ui.write(_('bookmarks:'), label='log.bookmark')
5568 if current is not None:
5564 if current is not None:
5569 try:
5565 try:
5570 marks.remove(current)
5566 marks.remove(current)
5571 ui.write(' *' + current, label='bookmarks.current')
5567 ui.write(' *' + current, label='bookmarks.current')
5572 except ValueError:
5568 except ValueError:
5573 # current bookmark not in parent ctx marks
5569 # current bookmark not in parent ctx marks
5574 pass
5570 pass
5575 for m in marks:
5571 for m in marks:
5576 ui.write(' ' + m, label='log.bookmark')
5572 ui.write(' ' + m, label='log.bookmark')
5577 ui.write('\n', label='log.bookmark')
5573 ui.write('\n', label='log.bookmark')
5578
5574
5579 st = list(repo.status(unknown=True))[:6]
5575 st = list(repo.status(unknown=True))[:6]
5580
5576
5581 c = repo.dirstate.copies()
5577 c = repo.dirstate.copies()
5582 copied, renamed = [], []
5578 copied, renamed = [], []
5583 for d, s in c.iteritems():
5579 for d, s in c.iteritems():
5584 if s in st[2]:
5580 if s in st[2]:
5585 st[2].remove(s)
5581 st[2].remove(s)
5586 renamed.append(d)
5582 renamed.append(d)
5587 else:
5583 else:
5588 copied.append(d)
5584 copied.append(d)
5589 if d in st[1]:
5585 if d in st[1]:
5590 st[1].remove(d)
5586 st[1].remove(d)
5591 st.insert(3, renamed)
5587 st.insert(3, renamed)
5592 st.insert(4, copied)
5588 st.insert(4, copied)
5593
5589
5594 ms = mergemod.mergestate(repo)
5590 ms = mergemod.mergestate(repo)
5595 st.append([f for f in ms if ms[f] == 'u'])
5591 st.append([f for f in ms if ms[f] == 'u'])
5596
5592
5597 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5593 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5598 st.append(subs)
5594 st.append(subs)
5599
5595
5600 labels = [ui.label(_('%d modified'), 'status.modified'),
5596 labels = [ui.label(_('%d modified'), 'status.modified'),
5601 ui.label(_('%d added'), 'status.added'),
5597 ui.label(_('%d added'), 'status.added'),
5602 ui.label(_('%d removed'), 'status.removed'),
5598 ui.label(_('%d removed'), 'status.removed'),
5603 ui.label(_('%d renamed'), 'status.copied'),
5599 ui.label(_('%d renamed'), 'status.copied'),
5604 ui.label(_('%d copied'), 'status.copied'),
5600 ui.label(_('%d copied'), 'status.copied'),
5605 ui.label(_('%d deleted'), 'status.deleted'),
5601 ui.label(_('%d deleted'), 'status.deleted'),
5606 ui.label(_('%d unknown'), 'status.unknown'),
5602 ui.label(_('%d unknown'), 'status.unknown'),
5607 ui.label(_('%d ignored'), 'status.ignored'),
5603 ui.label(_('%d ignored'), 'status.ignored'),
5608 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5604 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5609 ui.label(_('%d subrepos'), 'status.modified')]
5605 ui.label(_('%d subrepos'), 'status.modified')]
5610 t = []
5606 t = []
5611 for s, l in zip(st, labels):
5607 for s, l in zip(st, labels):
5612 if s:
5608 if s:
5613 t.append(l % len(s))
5609 t.append(l % len(s))
5614
5610
5615 t = ', '.join(t)
5611 t = ', '.join(t)
5616 cleanworkdir = False
5612 cleanworkdir = False
5617
5613
5618 if len(parents) > 1:
5614 if len(parents) > 1:
5619 t += _(' (merge)')
5615 t += _(' (merge)')
5620 elif branch != parents[0].branch():
5616 elif branch != parents[0].branch():
5621 t += _(' (new branch)')
5617 t += _(' (new branch)')
5622 elif (parents[0].closesbranch() and
5618 elif (parents[0].closesbranch() and
5623 pnode in repo.branchheads(branch, closed=True)):
5619 pnode in repo.branchheads(branch, closed=True)):
5624 t += _(' (head closed)')
5620 t += _(' (head closed)')
5625 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5621 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5626 t += _(' (clean)')
5622 t += _(' (clean)')
5627 cleanworkdir = True
5623 cleanworkdir = True
5628 elif pnode not in bheads:
5624 elif pnode not in bheads:
5629 t += _(' (new branch head)')
5625 t += _(' (new branch head)')
5630
5626
5631 if cleanworkdir:
5627 if cleanworkdir:
5632 # i18n: column positioning for "hg summary"
5628 # i18n: column positioning for "hg summary"
5633 ui.status(_('commit: %s\n') % t.strip())
5629 ui.status(_('commit: %s\n') % t.strip())
5634 else:
5630 else:
5635 # i18n: column positioning for "hg summary"
5631 # i18n: column positioning for "hg summary"
5636 ui.write(_('commit: %s\n') % t.strip())
5632 ui.write(_('commit: %s\n') % t.strip())
5637
5633
5638 # all ancestors of branch heads - all ancestors of parent = new csets
5634 # all ancestors of branch heads - all ancestors of parent = new csets
5639 new = [0] * len(repo)
5635 new = [0] * len(repo)
5640 cl = repo.changelog
5636 cl = repo.changelog
5641 for a in [cl.rev(n) for n in bheads]:
5637 for a in [cl.rev(n) for n in bheads]:
5642 new[a] = 1
5638 new[a] = 1
5643 for a in cl.ancestors([cl.rev(n) for n in bheads]):
5639 for a in cl.ancestors([cl.rev(n) for n in bheads]):
5644 new[a] = 1
5640 new[a] = 1
5645 for a in [p.rev() for p in parents]:
5641 for a in [p.rev() for p in parents]:
5646 if a >= 0:
5642 if a >= 0:
5647 new[a] = 0
5643 new[a] = 0
5648 for a in cl.ancestors([p.rev() for p in parents]):
5644 for a in cl.ancestors([p.rev() for p in parents]):
5649 new[a] = 0
5645 new[a] = 0
5650 new = sum(new)
5646 new = sum(new)
5651
5647
5652 if new == 0:
5648 if new == 0:
5653 # i18n: column positioning for "hg summary"
5649 # i18n: column positioning for "hg summary"
5654 ui.status(_('update: (current)\n'))
5650 ui.status(_('update: (current)\n'))
5655 elif pnode not in bheads:
5651 elif pnode not in bheads:
5656 # i18n: column positioning for "hg summary"
5652 # i18n: column positioning for "hg summary"
5657 ui.write(_('update: %d new changesets (update)\n') % new)
5653 ui.write(_('update: %d new changesets (update)\n') % new)
5658 else:
5654 else:
5659 # i18n: column positioning for "hg summary"
5655 # i18n: column positioning for "hg summary"
5660 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5656 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5661 (new, len(bheads)))
5657 (new, len(bheads)))
5662
5658
5663 if opts.get('remote'):
5659 if opts.get('remote'):
5664 t = []
5660 t = []
5665 source, branches = hg.parseurl(ui.expandpath('default'))
5661 source, branches = hg.parseurl(ui.expandpath('default'))
5666 other = hg.peer(repo, {}, source)
5662 other = hg.peer(repo, {}, source)
5667 revs, checkout = hg.addbranchrevs(repo, other, branches,
5663 revs, checkout = hg.addbranchrevs(repo, other, branches,
5668 opts.get('rev'))
5664 opts.get('rev'))
5669 ui.debug('comparing with %s\n' % util.hidepassword(source))
5665 ui.debug('comparing with %s\n' % util.hidepassword(source))
5670 repo.ui.pushbuffer()
5666 repo.ui.pushbuffer()
5671 commoninc = discovery.findcommonincoming(repo, other)
5667 commoninc = discovery.findcommonincoming(repo, other)
5672 _common, incoming, _rheads = commoninc
5668 _common, incoming, _rheads = commoninc
5673 repo.ui.popbuffer()
5669 repo.ui.popbuffer()
5674 if incoming:
5670 if incoming:
5675 t.append(_('1 or more incoming'))
5671 t.append(_('1 or more incoming'))
5676
5672
5677 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5673 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5678 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5674 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5679 if source != dest:
5675 if source != dest:
5680 other = hg.peer(repo, {}, dest)
5676 other = hg.peer(repo, {}, dest)
5681 commoninc = None
5677 commoninc = None
5682 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5678 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5683 repo.ui.pushbuffer()
5679 repo.ui.pushbuffer()
5684 outgoing = discovery.findcommonoutgoing(repo, other,
5680 outgoing = discovery.findcommonoutgoing(repo, other,
5685 commoninc=commoninc)
5681 commoninc=commoninc)
5686 repo.ui.popbuffer()
5682 repo.ui.popbuffer()
5687 o = outgoing.missing
5683 o = outgoing.missing
5688 if o:
5684 if o:
5689 t.append(_('%d outgoing') % len(o))
5685 t.append(_('%d outgoing') % len(o))
5690 if 'bookmarks' in other.listkeys('namespaces'):
5686 if 'bookmarks' in other.listkeys('namespaces'):
5691 lmarks = repo.listkeys('bookmarks')
5687 lmarks = repo.listkeys('bookmarks')
5692 rmarks = other.listkeys('bookmarks')
5688 rmarks = other.listkeys('bookmarks')
5693 diff = set(rmarks) - set(lmarks)
5689 diff = set(rmarks) - set(lmarks)
5694 if len(diff) > 0:
5690 if len(diff) > 0:
5695 t.append(_('%d incoming bookmarks') % len(diff))
5691 t.append(_('%d incoming bookmarks') % len(diff))
5696 diff = set(lmarks) - set(rmarks)
5692 diff = set(lmarks) - set(rmarks)
5697 if len(diff) > 0:
5693 if len(diff) > 0:
5698 t.append(_('%d outgoing bookmarks') % len(diff))
5694 t.append(_('%d outgoing bookmarks') % len(diff))
5699
5695
5700 if t:
5696 if t:
5701 # i18n: column positioning for "hg summary"
5697 # i18n: column positioning for "hg summary"
5702 ui.write(_('remote: %s\n') % (', '.join(t)))
5698 ui.write(_('remote: %s\n') % (', '.join(t)))
5703 else:
5699 else:
5704 # i18n: column positioning for "hg summary"
5700 # i18n: column positioning for "hg summary"
5705 ui.status(_('remote: (synced)\n'))
5701 ui.status(_('remote: (synced)\n'))
5706
5702
5707 @command('tag',
5703 @command('tag',
5708 [('f', 'force', None, _('force tag')),
5704 [('f', 'force', None, _('force tag')),
5709 ('l', 'local', None, _('make the tag local')),
5705 ('l', 'local', None, _('make the tag local')),
5710 ('r', 'rev', '', _('revision to tag'), _('REV')),
5706 ('r', 'rev', '', _('revision to tag'), _('REV')),
5711 ('', 'remove', None, _('remove a tag')),
5707 ('', 'remove', None, _('remove a tag')),
5712 # -l/--local is already there, commitopts cannot be used
5708 # -l/--local is already there, commitopts cannot be used
5713 ('e', 'edit', None, _('edit commit message')),
5709 ('e', 'edit', None, _('edit commit message')),
5714 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5710 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5715 ] + commitopts2,
5711 ] + commitopts2,
5716 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5712 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5717 def tag(ui, repo, name1, *names, **opts):
5713 def tag(ui, repo, name1, *names, **opts):
5718 """add one or more tags for the current or given revision
5714 """add one or more tags for the current or given revision
5719
5715
5720 Name a particular revision using <name>.
5716 Name a particular revision using <name>.
5721
5717
5722 Tags are used to name particular revisions of the repository and are
5718 Tags are used to name particular revisions of the repository and are
5723 very useful to compare different revisions, to go back to significant
5719 very useful to compare different revisions, to go back to significant
5724 earlier versions or to mark branch points as releases, etc. Changing
5720 earlier versions or to mark branch points as releases, etc. Changing
5725 an existing tag is normally disallowed; use -f/--force to override.
5721 an existing tag is normally disallowed; use -f/--force to override.
5726
5722
5727 If no revision is given, the parent of the working directory is
5723 If no revision is given, the parent of the working directory is
5728 used, or tip if no revision is checked out.
5724 used, or tip if no revision is checked out.
5729
5725
5730 To facilitate version control, distribution, and merging of tags,
5726 To facilitate version control, distribution, and merging of tags,
5731 they are stored as a file named ".hgtags" which is managed similarly
5727 they are stored as a file named ".hgtags" which is managed similarly
5732 to other project files and can be hand-edited if necessary. This
5728 to other project files and can be hand-edited if necessary. This
5733 also means that tagging creates a new commit. The file
5729 also means that tagging creates a new commit. The file
5734 ".hg/localtags" is used for local tags (not shared among
5730 ".hg/localtags" is used for local tags (not shared among
5735 repositories).
5731 repositories).
5736
5732
5737 Tag commits are usually made at the head of a branch. If the parent
5733 Tag commits are usually made at the head of a branch. If the parent
5738 of the working directory is not a branch head, :hg:`tag` aborts; use
5734 of the working directory is not a branch head, :hg:`tag` aborts; use
5739 -f/--force to force the tag commit to be based on a non-head
5735 -f/--force to force the tag commit to be based on a non-head
5740 changeset.
5736 changeset.
5741
5737
5742 See :hg:`help dates` for a list of formats valid for -d/--date.
5738 See :hg:`help dates` for a list of formats valid for -d/--date.
5743
5739
5744 Since tag names have priority over branch names during revision
5740 Since tag names have priority over branch names during revision
5745 lookup, using an existing branch name as a tag name is discouraged.
5741 lookup, using an existing branch name as a tag name is discouraged.
5746
5742
5747 Returns 0 on success.
5743 Returns 0 on success.
5748 """
5744 """
5749 wlock = lock = None
5745 wlock = lock = None
5750 try:
5746 try:
5751 wlock = repo.wlock()
5747 wlock = repo.wlock()
5752 lock = repo.lock()
5748 lock = repo.lock()
5753 rev_ = "."
5749 rev_ = "."
5754 names = [t.strip() for t in (name1,) + names]
5750 names = [t.strip() for t in (name1,) + names]
5755 if len(names) != len(set(names)):
5751 if len(names) != len(set(names)):
5756 raise util.Abort(_('tag names must be unique'))
5752 raise util.Abort(_('tag names must be unique'))
5757 for n in names:
5753 for n in names:
5758 scmutil.checknewlabel(repo, n, 'tag')
5754 scmutil.checknewlabel(repo, n, 'tag')
5759 if not n:
5755 if not n:
5760 raise util.Abort(_('tag names cannot consist entirely of '
5756 raise util.Abort(_('tag names cannot consist entirely of '
5761 'whitespace'))
5757 'whitespace'))
5762 if opts.get('rev') and opts.get('remove'):
5758 if opts.get('rev') and opts.get('remove'):
5763 raise util.Abort(_("--rev and --remove are incompatible"))
5759 raise util.Abort(_("--rev and --remove are incompatible"))
5764 if opts.get('rev'):
5760 if opts.get('rev'):
5765 rev_ = opts['rev']
5761 rev_ = opts['rev']
5766 message = opts.get('message')
5762 message = opts.get('message')
5767 if opts.get('remove'):
5763 if opts.get('remove'):
5768 expectedtype = opts.get('local') and 'local' or 'global'
5764 expectedtype = opts.get('local') and 'local' or 'global'
5769 for n in names:
5765 for n in names:
5770 if not repo.tagtype(n):
5766 if not repo.tagtype(n):
5771 raise util.Abort(_("tag '%s' does not exist") % n)
5767 raise util.Abort(_("tag '%s' does not exist") % n)
5772 if repo.tagtype(n) != expectedtype:
5768 if repo.tagtype(n) != expectedtype:
5773 if expectedtype == 'global':
5769 if expectedtype == 'global':
5774 raise util.Abort(_("tag '%s' is not a global tag") % n)
5770 raise util.Abort(_("tag '%s' is not a global tag") % n)
5775 else:
5771 else:
5776 raise util.Abort(_("tag '%s' is not a local tag") % n)
5772 raise util.Abort(_("tag '%s' is not a local tag") % n)
5777 rev_ = nullid
5773 rev_ = nullid
5778 if not message:
5774 if not message:
5779 # we don't translate commit messages
5775 # we don't translate commit messages
5780 message = 'Removed tag %s' % ', '.join(names)
5776 message = 'Removed tag %s' % ', '.join(names)
5781 elif not opts.get('force'):
5777 elif not opts.get('force'):
5782 for n in names:
5778 for n in names:
5783 if n in repo.tags():
5779 if n in repo.tags():
5784 raise util.Abort(_("tag '%s' already exists "
5780 raise util.Abort(_("tag '%s' already exists "
5785 "(use -f to force)") % n)
5781 "(use -f to force)") % n)
5786 if not opts.get('local'):
5782 if not opts.get('local'):
5787 p1, p2 = repo.dirstate.parents()
5783 p1, p2 = repo.dirstate.parents()
5788 if p2 != nullid:
5784 if p2 != nullid:
5789 raise util.Abort(_('uncommitted merge'))
5785 raise util.Abort(_('uncommitted merge'))
5790 bheads = repo.branchheads()
5786 bheads = repo.branchheads()
5791 if not opts.get('force') and bheads and p1 not in bheads:
5787 if not opts.get('force') and bheads and p1 not in bheads:
5792 raise util.Abort(_('not at a branch head (use -f to force)'))
5788 raise util.Abort(_('not at a branch head (use -f to force)'))
5793 r = scmutil.revsingle(repo, rev_).node()
5789 r = scmutil.revsingle(repo, rev_).node()
5794
5790
5795 if not message:
5791 if not message:
5796 # we don't translate commit messages
5792 # we don't translate commit messages
5797 message = ('Added tag %s for changeset %s' %
5793 message = ('Added tag %s for changeset %s' %
5798 (', '.join(names), short(r)))
5794 (', '.join(names), short(r)))
5799
5795
5800 date = opts.get('date')
5796 date = opts.get('date')
5801 if date:
5797 if date:
5802 date = util.parsedate(date)
5798 date = util.parsedate(date)
5803
5799
5804 if opts.get('edit'):
5800 if opts.get('edit'):
5805 message = ui.edit(message, ui.username())
5801 message = ui.edit(message, ui.username())
5806
5802
5807 # don't allow tagging the null rev
5803 # don't allow tagging the null rev
5808 if (not opts.get('remove') and
5804 if (not opts.get('remove') and
5809 scmutil.revsingle(repo, rev_).rev() == nullrev):
5805 scmutil.revsingle(repo, rev_).rev() == nullrev):
5810 raise util.Abort(_("null revision specified"))
5806 raise util.Abort(_("null revision specified"))
5811
5807
5812 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5808 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5813 finally:
5809 finally:
5814 release(lock, wlock)
5810 release(lock, wlock)
5815
5811
5816 @command('tags', [], '')
5812 @command('tags', [], '')
5817 def tags(ui, repo, **opts):
5813 def tags(ui, repo, **opts):
5818 """list repository tags
5814 """list repository tags
5819
5815
5820 This lists both regular and local tags. When the -v/--verbose
5816 This lists both regular and local tags. When the -v/--verbose
5821 switch is used, a third column "local" is printed for local tags.
5817 switch is used, a third column "local" is printed for local tags.
5822
5818
5823 Returns 0 on success.
5819 Returns 0 on success.
5824 """
5820 """
5825
5821
5826 fm = ui.formatter('tags', opts)
5822 fm = ui.formatter('tags', opts)
5827 hexfunc = ui.debugflag and hex or short
5823 hexfunc = ui.debugflag and hex or short
5828 tagtype = ""
5824 tagtype = ""
5829
5825
5830 for t, n in reversed(repo.tagslist()):
5826 for t, n in reversed(repo.tagslist()):
5831 hn = hexfunc(n)
5827 hn = hexfunc(n)
5832 label = 'tags.normal'
5828 label = 'tags.normal'
5833 tagtype = ''
5829 tagtype = ''
5834 if repo.tagtype(t) == 'local':
5830 if repo.tagtype(t) == 'local':
5835 label = 'tags.local'
5831 label = 'tags.local'
5836 tagtype = 'local'
5832 tagtype = 'local'
5837
5833
5838 fm.startitem()
5834 fm.startitem()
5839 fm.write('tag', '%s', t, label=label)
5835 fm.write('tag', '%s', t, label=label)
5840 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5836 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5841 fm.condwrite(not ui.quiet, 'rev id', fmt,
5837 fm.condwrite(not ui.quiet, 'rev id', fmt,
5842 repo.changelog.rev(n), hn, label=label)
5838 repo.changelog.rev(n), hn, label=label)
5843 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5839 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5844 tagtype, label=label)
5840 tagtype, label=label)
5845 fm.plain('\n')
5841 fm.plain('\n')
5846 fm.end()
5842 fm.end()
5847
5843
5848 @command('tip',
5844 @command('tip',
5849 [('p', 'patch', None, _('show patch')),
5845 [('p', 'patch', None, _('show patch')),
5850 ('g', 'git', None, _('use git extended diff format')),
5846 ('g', 'git', None, _('use git extended diff format')),
5851 ] + templateopts,
5847 ] + templateopts,
5852 _('[-p] [-g]'))
5848 _('[-p] [-g]'))
5853 def tip(ui, repo, **opts):
5849 def tip(ui, repo, **opts):
5854 """show the tip revision
5850 """show the tip revision
5855
5851
5856 The tip revision (usually just called the tip) is the changeset
5852 The tip revision (usually just called the tip) is the changeset
5857 most recently added to the repository (and therefore the most
5853 most recently added to the repository (and therefore the most
5858 recently changed head).
5854 recently changed head).
5859
5855
5860 If you have just made a commit, that commit will be the tip. If
5856 If you have just made a commit, that commit will be the tip. If
5861 you have just pulled changes from another repository, the tip of
5857 you have just pulled changes from another repository, the tip of
5862 that repository becomes the current tip. The "tip" tag is special
5858 that repository becomes the current tip. The "tip" tag is special
5863 and cannot be renamed or assigned to a different changeset.
5859 and cannot be renamed or assigned to a different changeset.
5864
5860
5865 Returns 0 on success.
5861 Returns 0 on success.
5866 """
5862 """
5867 displayer = cmdutil.show_changeset(ui, repo, opts)
5863 displayer = cmdutil.show_changeset(ui, repo, opts)
5868 displayer.show(repo[len(repo) - 1])
5864 displayer.show(repo[len(repo) - 1])
5869 displayer.close()
5865 displayer.close()
5870
5866
5871 @command('unbundle',
5867 @command('unbundle',
5872 [('u', 'update', None,
5868 [('u', 'update', None,
5873 _('update to new branch head if changesets were unbundled'))],
5869 _('update to new branch head if changesets were unbundled'))],
5874 _('[-u] FILE...'))
5870 _('[-u] FILE...'))
5875 def unbundle(ui, repo, fname1, *fnames, **opts):
5871 def unbundle(ui, repo, fname1, *fnames, **opts):
5876 """apply one or more changegroup files
5872 """apply one or more changegroup files
5877
5873
5878 Apply one or more compressed changegroup files generated by the
5874 Apply one or more compressed changegroup files generated by the
5879 bundle command.
5875 bundle command.
5880
5876
5881 Returns 0 on success, 1 if an update has unresolved files.
5877 Returns 0 on success, 1 if an update has unresolved files.
5882 """
5878 """
5883 fnames = (fname1,) + fnames
5879 fnames = (fname1,) + fnames
5884
5880
5885 lock = repo.lock()
5881 lock = repo.lock()
5886 wc = repo['.']
5882 wc = repo['.']
5887 try:
5883 try:
5888 for fname in fnames:
5884 for fname in fnames:
5889 f = hg.openpath(ui, fname)
5885 f = hg.openpath(ui, fname)
5890 gen = changegroup.readbundle(f, fname)
5886 gen = changegroup.readbundle(f, fname)
5891 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5887 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5892 finally:
5888 finally:
5893 lock.release()
5889 lock.release()
5894 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5890 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5895 return postincoming(ui, repo, modheads, opts.get('update'), None)
5891 return postincoming(ui, repo, modheads, opts.get('update'), None)
5896
5892
5897 @command('^update|up|checkout|co',
5893 @command('^update|up|checkout|co',
5898 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5894 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5899 ('c', 'check', None,
5895 ('c', 'check', None,
5900 _('update across branches if no uncommitted changes')),
5896 _('update across branches if no uncommitted changes')),
5901 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5897 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5902 ('r', 'rev', '', _('revision'), _('REV'))],
5898 ('r', 'rev', '', _('revision'), _('REV'))],
5903 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5899 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5904 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5900 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5905 """update working directory (or switch revisions)
5901 """update working directory (or switch revisions)
5906
5902
5907 Update the repository's working directory to the specified
5903 Update the repository's working directory to the specified
5908 changeset. If no changeset is specified, update to the tip of the
5904 changeset. If no changeset is specified, update to the tip of the
5909 current named branch and move the current bookmark (see :hg:`help
5905 current named branch and move the current bookmark (see :hg:`help
5910 bookmarks`).
5906 bookmarks`).
5911
5907
5912 Update sets the working directory's parent revision to the specified
5908 Update sets the working directory's parent revision to the specified
5913 changeset (see :hg:`help parents`).
5909 changeset (see :hg:`help parents`).
5914
5910
5915 If the changeset is not a descendant or ancestor of the working
5911 If the changeset is not a descendant or ancestor of the working
5916 directory's parent, the update is aborted. With the -c/--check
5912 directory's parent, the update is aborted. With the -c/--check
5917 option, the working directory is checked for uncommitted changes; if
5913 option, the working directory is checked for uncommitted changes; if
5918 none are found, the working directory is updated to the specified
5914 none are found, the working directory is updated to the specified
5919 changeset.
5915 changeset.
5920
5916
5921 .. container:: verbose
5917 .. container:: verbose
5922
5918
5923 The following rules apply when the working directory contains
5919 The following rules apply when the working directory contains
5924 uncommitted changes:
5920 uncommitted changes:
5925
5921
5926 1. If neither -c/--check nor -C/--clean is specified, and if
5922 1. If neither -c/--check nor -C/--clean is specified, and if
5927 the requested changeset is an ancestor or descendant of
5923 the requested changeset is an ancestor or descendant of
5928 the working directory's parent, the uncommitted changes
5924 the working directory's parent, the uncommitted changes
5929 are merged into the requested changeset and the merged
5925 are merged into the requested changeset and the merged
5930 result is left uncommitted. If the requested changeset is
5926 result is left uncommitted. If the requested changeset is
5931 not an ancestor or descendant (that is, it is on another
5927 not an ancestor or descendant (that is, it is on another
5932 branch), the update is aborted and the uncommitted changes
5928 branch), the update is aborted and the uncommitted changes
5933 are preserved.
5929 are preserved.
5934
5930
5935 2. With the -c/--check option, the update is aborted and the
5931 2. With the -c/--check option, the update is aborted and the
5936 uncommitted changes are preserved.
5932 uncommitted changes are preserved.
5937
5933
5938 3. With the -C/--clean option, uncommitted changes are discarded and
5934 3. With the -C/--clean option, uncommitted changes are discarded and
5939 the working directory is updated to the requested changeset.
5935 the working directory is updated to the requested changeset.
5940
5936
5941 To cancel an uncommitted merge (and lose your changes), use
5937 To cancel an uncommitted merge (and lose your changes), use
5942 :hg:`update --clean .`.
5938 :hg:`update --clean .`.
5943
5939
5944 Use null as the changeset to remove the working directory (like
5940 Use null as the changeset to remove the working directory (like
5945 :hg:`clone -U`).
5941 :hg:`clone -U`).
5946
5942
5947 If you want to revert just one file to an older revision, use
5943 If you want to revert just one file to an older revision, use
5948 :hg:`revert [-r REV] NAME`.
5944 :hg:`revert [-r REV] NAME`.
5949
5945
5950 See :hg:`help dates` for a list of formats valid for -d/--date.
5946 See :hg:`help dates` for a list of formats valid for -d/--date.
5951
5947
5952 Returns 0 on success, 1 if there are unresolved files.
5948 Returns 0 on success, 1 if there are unresolved files.
5953 """
5949 """
5954 if rev and node:
5950 if rev and node:
5955 raise util.Abort(_("please specify just one revision"))
5951 raise util.Abort(_("please specify just one revision"))
5956
5952
5957 if rev is None or rev == '':
5953 if rev is None or rev == '':
5958 rev = node
5954 rev = node
5959
5955
5960 # with no argument, we also move the current bookmark, if any
5956 # with no argument, we also move the current bookmark, if any
5961 movemarkfrom = None
5957 movemarkfrom = None
5962 if rev is None:
5958 if rev is None:
5963 movemarkfrom = repo['.'].node()
5959 movemarkfrom = repo['.'].node()
5964
5960
5965 # if we defined a bookmark, we have to remember the original bookmark name
5961 # if we defined a bookmark, we have to remember the original bookmark name
5966 brev = rev
5962 brev = rev
5967 rev = scmutil.revsingle(repo, rev, rev).rev()
5963 rev = scmutil.revsingle(repo, rev, rev).rev()
5968
5964
5969 if check and clean:
5965 if check and clean:
5970 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5966 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5971
5967
5972 if date:
5968 if date:
5973 if rev is not None:
5969 if rev is not None:
5974 raise util.Abort(_("you can't specify a revision and a date"))
5970 raise util.Abort(_("you can't specify a revision and a date"))
5975 rev = cmdutil.finddate(ui, repo, date)
5971 rev = cmdutil.finddate(ui, repo, date)
5976
5972
5977 if check:
5973 if check:
5978 c = repo[None]
5974 c = repo[None]
5979 if c.dirty(merge=False, branch=False, missing=True):
5975 if c.dirty(merge=False, branch=False, missing=True):
5980 raise util.Abort(_("uncommitted local changes"))
5976 raise util.Abort(_("uncommitted local changes"))
5981 if rev is None:
5977 if rev is None:
5982 rev = repo[repo[None].branch()].rev()
5978 rev = repo[repo[None].branch()].rev()
5983 mergemod._checkunknown(repo, repo[None], repo[rev])
5979 mergemod._checkunknown(repo, repo[None], repo[rev])
5984
5980
5985 if clean:
5981 if clean:
5986 ret = hg.clean(repo, rev)
5982 ret = hg.clean(repo, rev)
5987 else:
5983 else:
5988 ret = hg.update(repo, rev)
5984 ret = hg.update(repo, rev)
5989
5985
5990 if not ret and movemarkfrom:
5986 if not ret and movemarkfrom:
5991 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5987 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5992 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5988 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5993 elif brev in repo._bookmarks:
5989 elif brev in repo._bookmarks:
5994 bookmarks.setcurrent(repo, brev)
5990 bookmarks.setcurrent(repo, brev)
5995 elif brev:
5991 elif brev:
5996 bookmarks.unsetcurrent(repo)
5992 bookmarks.unsetcurrent(repo)
5997
5993
5998 return ret
5994 return ret
5999
5995
6000 @command('verify', [])
5996 @command('verify', [])
6001 def verify(ui, repo):
5997 def verify(ui, repo):
6002 """verify the integrity of the repository
5998 """verify the integrity of the repository
6003
5999
6004 Verify the integrity of the current repository.
6000 Verify the integrity of the current repository.
6005
6001
6006 This will perform an extensive check of the repository's
6002 This will perform an extensive check of the repository's
6007 integrity, validating the hashes and checksums of each entry in
6003 integrity, validating the hashes and checksums of each entry in
6008 the changelog, manifest, and tracked files, as well as the
6004 the changelog, manifest, and tracked files, as well as the
6009 integrity of their crosslinks and indices.
6005 integrity of their crosslinks and indices.
6010
6006
6011 Please see http://mercurial.selenic.com/wiki/RepositoryCorruption
6007 Please see http://mercurial.selenic.com/wiki/RepositoryCorruption
6012 for more information about recovery from corruption of the
6008 for more information about recovery from corruption of the
6013 repository.
6009 repository.
6014
6010
6015 Returns 0 on success, 1 if errors are encountered.
6011 Returns 0 on success, 1 if errors are encountered.
6016 """
6012 """
6017 return hg.verify(repo)
6013 return hg.verify(repo)
6018
6014
6019 @command('version', [])
6015 @command('version', [])
6020 def version_(ui):
6016 def version_(ui):
6021 """output version and copyright information"""
6017 """output version and copyright information"""
6022 ui.write(_("Mercurial Distributed SCM (version %s)\n")
6018 ui.write(_("Mercurial Distributed SCM (version %s)\n")
6023 % util.version())
6019 % util.version())
6024 ui.status(_(
6020 ui.status(_(
6025 "(see http://mercurial.selenic.com for more information)\n"
6021 "(see http://mercurial.selenic.com for more information)\n"
6026 "\nCopyright (C) 2005-2012 Matt Mackall and others\n"
6022 "\nCopyright (C) 2005-2012 Matt Mackall and others\n"
6027 "This is free software; see the source for copying conditions. "
6023 "This is free software; see the source for copying conditions. "
6028 "There is NO\nwarranty; "
6024 "There is NO\nwarranty; "
6029 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6025 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6030 ))
6026 ))
6031
6027
6032 norepo = ("clone init version help debugcommands debugcomplete"
6028 norepo = ("clone init version help debugcommands debugcomplete"
6033 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
6029 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
6034 " debugknown debuggetbundle debugbundle")
6030 " debugknown debuggetbundle debugbundle")
6035 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
6031 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
6036 " debugdata debugindex debugindexdot debugrevlog")
6032 " debugdata debugindex debugindexdot debugrevlog")
6037 inferrepo = ("add addremove annotate cat commit diff grep forget log parents"
6033 inferrepo = ("add addremove annotate cat commit diff grep forget log parents"
6038 " remove resolve status debugwalk")
6034 " remove resolve status debugwalk")
@@ -1,656 +1,655 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 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 nullid, nullrev, hex, bin
8 from node import nullid, nullrev, hex, bin
9 from i18n import _
9 from i18n import _
10 import error, scmutil, util, filemerge, copies, subrepo
10 import error, scmutil, util, filemerge, copies, subrepo
11 import errno, os, shutil
11 import errno, os, shutil
12
12
13 class mergestate(object):
13 class mergestate(object):
14 '''track 3-way merge state of individual files'''
14 '''track 3-way merge state of individual files'''
15 def __init__(self, repo):
15 def __init__(self, repo):
16 self._repo = repo
16 self._repo = repo
17 self._dirty = False
17 self._dirty = False
18 self._read()
18 self._read()
19 def reset(self, node=None):
19 def reset(self, node=None):
20 self._state = {}
20 self._state = {}
21 if node:
21 if node:
22 self._local = node
22 self._local = node
23 shutil.rmtree(self._repo.join("merge"), True)
23 shutil.rmtree(self._repo.join("merge"), True)
24 self._dirty = False
24 self._dirty = False
25 def _read(self):
25 def _read(self):
26 self._state = {}
26 self._state = {}
27 try:
27 try:
28 f = self._repo.opener("merge/state")
28 f = self._repo.opener("merge/state")
29 for i, l in enumerate(f):
29 for i, l in enumerate(f):
30 if i == 0:
30 if i == 0:
31 self._local = bin(l[:-1])
31 self._local = bin(l[:-1])
32 else:
32 else:
33 bits = l[:-1].split("\0")
33 bits = l[:-1].split("\0")
34 self._state[bits[0]] = bits[1:]
34 self._state[bits[0]] = bits[1:]
35 f.close()
35 f.close()
36 except IOError, err:
36 except IOError, err:
37 if err.errno != errno.ENOENT:
37 if err.errno != errno.ENOENT:
38 raise
38 raise
39 self._dirty = False
39 self._dirty = False
40 def commit(self):
40 def commit(self):
41 if self._dirty:
41 if self._dirty:
42 f = self._repo.opener("merge/state", "w")
42 f = self._repo.opener("merge/state", "w")
43 f.write(hex(self._local) + "\n")
43 f.write(hex(self._local) + "\n")
44 for d, v in self._state.iteritems():
44 for d, v in self._state.iteritems():
45 f.write("\0".join([d] + v) + "\n")
45 f.write("\0".join([d] + v) + "\n")
46 f.close()
46 f.close()
47 self._dirty = False
47 self._dirty = False
48 def add(self, fcl, fco, fca, fd, flags):
48 def add(self, fcl, fco, fca, fd, flags):
49 hash = util.sha1(fcl.path()).hexdigest()
49 hash = util.sha1(fcl.path()).hexdigest()
50 self._repo.opener.write("merge/" + hash, fcl.data())
50 self._repo.opener.write("merge/" + hash, fcl.data())
51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
52 hex(fca.filenode()), fco.path(), flags]
52 hex(fca.filenode()), fco.path(), flags]
53 self._dirty = True
53 self._dirty = True
54 def __contains__(self, dfile):
54 def __contains__(self, dfile):
55 return dfile in self._state
55 return dfile in self._state
56 def __getitem__(self, dfile):
56 def __getitem__(self, dfile):
57 return self._state[dfile][0]
57 return self._state[dfile][0]
58 def __iter__(self):
58 def __iter__(self):
59 l = self._state.keys()
59 l = self._state.keys()
60 l.sort()
60 l.sort()
61 for f in l:
61 for f in l:
62 yield f
62 yield f
63 def mark(self, dfile, state):
63 def mark(self, dfile, state):
64 self._state[dfile][0] = state
64 self._state[dfile][0] = state
65 self._dirty = True
65 self._dirty = True
66 def resolve(self, dfile, wctx, octx):
66 def resolve(self, dfile, wctx, octx):
67 if self[dfile] == 'r':
67 if self[dfile] == 'r':
68 return 0
68 return 0
69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
70 f = self._repo.opener("merge/" + hash)
70 f = self._repo.opener("merge/" + hash)
71 self._repo.wwrite(dfile, f.read(), flags)
71 self._repo.wwrite(dfile, f.read(), flags)
72 f.close()
72 f.close()
73 fcd = wctx[dfile]
73 fcd = wctx[dfile]
74 fco = octx[ofile]
74 fco = octx[ofile]
75 fca = self._repo.filectx(afile, fileid=anode)
75 fca = self._repo.filectx(afile, fileid=anode)
76 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
76 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
77 if r is None:
77 if r is None:
78 # no real conflict
78 # no real conflict
79 del self._state[dfile]
79 del self._state[dfile]
80 elif not r:
80 elif not r:
81 self.mark(dfile, 'r')
81 self.mark(dfile, 'r')
82 return r
82 return r
83
83
84 def _checkunknownfile(repo, wctx, mctx, f):
84 def _checkunknownfile(repo, wctx, mctx, f):
85 return (not repo.dirstate._ignore(f)
85 return (not repo.dirstate._ignore(f)
86 and os.path.isfile(repo.wjoin(f))
86 and os.path.isfile(repo.wjoin(f))
87 and repo.dirstate.normalize(f) not in repo.dirstate
87 and repo.dirstate.normalize(f) not in repo.dirstate
88 and mctx[f].cmp(wctx[f]))
88 and mctx[f].cmp(wctx[f]))
89
89
90 def _checkunknown(repo, wctx, mctx):
90 def _checkunknown(repo, wctx, mctx):
91 "check for collisions between unknown files and files in mctx"
91 "check for collisions between unknown files and files in mctx"
92
92
93 error = False
93 error = False
94 for f in mctx:
94 for f in mctx:
95 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
95 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
96 error = True
96 error = True
97 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
97 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
98 if error:
98 if error:
99 raise util.Abort(_("untracked files in working directory differ "
99 raise util.Abort(_("untracked files in working directory differ "
100 "from files in requested revision"))
100 "from files in requested revision"))
101
101
102 def _remains(f, m, ma, workingctx=False):
102 def _remains(f, m, ma, workingctx=False):
103 """check whether specified file remains after merge.
103 """check whether specified file remains after merge.
104
104
105 It is assumed that specified file is not contained in the manifest
105 It is assumed that specified file is not contained in the manifest
106 of the other context.
106 of the other context.
107 """
107 """
108 if f in ma:
108 if f in ma:
109 n = m[f]
109 n = m[f]
110 if n != ma[f]:
110 if n != ma[f]:
111 return True # because it is changed locally
111 return True # because it is changed locally
112 # even though it doesn't remain, if "remote deleted" is
112 # even though it doesn't remain, if "remote deleted" is
113 # chosen in manifestmerge()
113 # chosen in manifestmerge()
114 elif workingctx and n[20:] == "a":
114 elif workingctx and n[20:] == "a":
115 return True # because it is added locally (linear merge specific)
115 return True # because it is added locally (linear merge specific)
116 else:
116 else:
117 return False # because it is removed remotely
117 return False # because it is removed remotely
118 else:
118 else:
119 return True # because it is added locally
119 return True # because it is added locally
120
120
121 def _checkcollision(mctx, extractxs):
121 def _checkcollision(mctx, extractxs):
122 "check for case folding collisions in the destination context"
122 "check for case folding collisions in the destination context"
123 folded = {}
123 folded = {}
124 for fn in mctx:
124 for fn in mctx:
125 fold = util.normcase(fn)
125 fold = util.normcase(fn)
126 if fold in folded:
126 if fold in folded:
127 raise util.Abort(_("case-folding collision between %s and %s")
127 raise util.Abort(_("case-folding collision between %s and %s")
128 % (fn, folded[fold]))
128 % (fn, folded[fold]))
129 folded[fold] = fn
129 folded[fold] = fn
130
130
131 if extractxs:
131 if extractxs:
132 wctx, actx = extractxs
132 wctx, actx = extractxs
133 # class to delay looking up copy mapping
133 # class to delay looking up copy mapping
134 class pathcopies(object):
134 class pathcopies(object):
135 @util.propertycache
135 @util.propertycache
136 def map(self):
136 def map(self):
137 # {dst@mctx: src@wctx} copy mapping
137 # {dst@mctx: src@wctx} copy mapping
138 return copies.pathcopies(wctx, mctx)
138 return copies.pathcopies(wctx, mctx)
139 pc = pathcopies()
139 pc = pathcopies()
140
140
141 for fn in wctx:
141 for fn in wctx:
142 fold = util.normcase(fn)
142 fold = util.normcase(fn)
143 mfn = folded.get(fold, None)
143 mfn = folded.get(fold, None)
144 if (mfn and mfn != fn and pc.map.get(mfn) != fn and
144 if (mfn and mfn != fn and pc.map.get(mfn) != fn and
145 _remains(fn, wctx.manifest(), actx.manifest(), True) and
145 _remains(fn, wctx.manifest(), actx.manifest(), True) and
146 _remains(mfn, mctx.manifest(), actx.manifest())):
146 _remains(mfn, mctx.manifest(), actx.manifest())):
147 raise util.Abort(_("case-folding collision between %s and %s")
147 raise util.Abort(_("case-folding collision between %s and %s")
148 % (mfn, fn))
148 % (mfn, fn))
149
149
150 def _forgetremoved(wctx, mctx, branchmerge):
150 def _forgetremoved(wctx, mctx, branchmerge):
151 """
151 """
152 Forget removed files
152 Forget removed files
153
153
154 If we're jumping between revisions (as opposed to merging), and if
154 If we're jumping between revisions (as opposed to merging), and if
155 neither the working directory nor the target rev has the file,
155 neither the working directory nor the target rev has the file,
156 then we need to remove it from the dirstate, to prevent the
156 then we need to remove it from the dirstate, to prevent the
157 dirstate from listing the file when it is no longer in the
157 dirstate from listing the file when it is no longer in the
158 manifest.
158 manifest.
159
159
160 If we're merging, and the other revision has removed a file
160 If we're merging, and the other revision has removed a file
161 that is not present in the working directory, we need to mark it
161 that is not present in the working directory, we need to mark it
162 as removed.
162 as removed.
163 """
163 """
164
164
165 action = []
165 action = []
166 state = branchmerge and 'r' or 'f'
166 state = branchmerge and 'r' or 'f'
167 for f in wctx.deleted():
167 for f in wctx.deleted():
168 if f not in mctx:
168 if f not in mctx:
169 action.append((f, state))
169 action.append((f, state))
170
170
171 if not branchmerge:
171 if not branchmerge:
172 for f in wctx.removed():
172 for f in wctx.removed():
173 if f not in mctx:
173 if f not in mctx:
174 action.append((f, "f"))
174 action.append((f, "f"))
175
175
176 return action
176 return action
177
177
178 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
178 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
179 """
179 """
180 Merge p1 and p2 with ancestor pa and generate merge action list
180 Merge p1 and p2 with ancestor pa and generate merge action list
181
181
182 overwrite = whether we clobber working files
182 overwrite = whether we clobber working files
183 partial = function to filter file lists
183 partial = function to filter file lists
184 """
184 """
185
185
186 def fmerge(f, f2, fa):
186 def fmerge(f, f2, fa):
187 """merge flags"""
187 """merge flags"""
188 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
188 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
189 if m == n: # flags agree
189 if m == n: # flags agree
190 return m # unchanged
190 return m # unchanged
191 if m and n and not a: # flags set, don't agree, differ from parent
191 if m and n and not a: # flags set, don't agree, differ from parent
192 r = repo.ui.promptchoice(
192 r = repo.ui.promptchoice(
193 _(" conflicting flags for %s\n"
193 _(" conflicting flags for %s\n"
194 "(n)one, e(x)ec or sym(l)ink?") % f,
194 "(n)one, e(x)ec or sym(l)ink?") % f,
195 (_("&None"), _("E&xec"), _("Sym&link")), 0)
195 (_("&None"), _("E&xec"), _("Sym&link")), 0)
196 if r == 1:
196 if r == 1:
197 return "x" # Exec
197 return "x" # Exec
198 if r == 2:
198 if r == 2:
199 return "l" # Symlink
199 return "l" # Symlink
200 return ""
200 return ""
201 if m and m != a: # changed from a to m
201 if m and m != a: # changed from a to m
202 return m
202 return m
203 if n and n != a: # changed from a to n
203 if n and n != a: # changed from a to n
204 if (n == 'l' or a == 'l') and m1.get(f) != ma.get(f):
204 if (n == 'l' or a == 'l') and m1.get(f) != ma.get(f):
205 # can't automatically merge symlink flag when there
205 # can't automatically merge symlink flag when there
206 # are file-level conflicts here, let filemerge take
206 # are file-level conflicts here, let filemerge take
207 # care of it
207 # care of it
208 return m
208 return m
209 return n
209 return n
210 return '' # flag was cleared
210 return '' # flag was cleared
211
211
212 def act(msg, m, f, *args):
212 def act(msg, m, f, *args):
213 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
213 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
214 action.append((f, m) + args)
214 action.append((f, m) + args)
215
215
216 action, copy, movewithdir = [], {}, {}
216 action, copy, movewithdir = [], {}, {}
217
217
218 if overwrite:
218 if overwrite:
219 pa = p1
219 pa = p1
220 elif pa == p2: # backwards
220 elif pa == p2: # backwards
221 pa = p1.p1()
221 pa = p1.p1()
222 elif pa and repo.ui.configbool("merge", "followcopies", True):
222 elif pa and repo.ui.configbool("merge", "followcopies", True):
223 ret = copies.mergecopies(repo, p1, p2, pa)
223 ret = copies.mergecopies(repo, p1, p2, pa)
224 copy, movewithdir, diverge, renamedelete = ret
224 copy, movewithdir, diverge, renamedelete = ret
225 for of, fl in diverge.iteritems():
225 for of, fl in diverge.iteritems():
226 act("divergent renames", "dr", of, fl)
226 act("divergent renames", "dr", of, fl)
227 for of, fl in renamedelete.iteritems():
227 for of, fl in renamedelete.iteritems():
228 act("rename and delete", "rd", of, fl)
228 act("rename and delete", "rd", of, fl)
229
229
230 repo.ui.note(_("resolving manifests\n"))
230 repo.ui.note(_("resolving manifests\n"))
231 repo.ui.debug(" overwrite: %s, partial: %s\n"
231 repo.ui.debug(" overwrite: %s, partial: %s\n"
232 % (bool(overwrite), bool(partial)))
232 % (bool(overwrite), bool(partial)))
233 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
233 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
234
234
235 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
235 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
236 copied = set(copy.values())
236 copied = set(copy.values())
237 copied.update(movewithdir.values())
237 copied.update(movewithdir.values())
238
238
239 if '.hgsubstate' in m1:
239 if '.hgsubstate' in m1:
240 # check whether sub state is modified
240 # check whether sub state is modified
241 for s in p1.substate:
241 for s in p1.substate:
242 if p1.sub(s).dirty():
242 if p1.sub(s).dirty():
243 m1['.hgsubstate'] += "+"
243 m1['.hgsubstate'] += "+"
244 break
244 break
245
245
246 # Compare manifests
246 # Compare manifests
247 for f, n in m1.iteritems():
247 for f, n in m1.iteritems():
248 if partial and not partial(f):
248 if partial and not partial(f):
249 continue
249 continue
250 if f in m2:
250 if f in m2:
251 rflags = fmerge(f, f, f)
251 rflags = fmerge(f, f, f)
252 a = ma.get(f, nullid)
252 a = ma.get(f, nullid)
253 if n == m2[f] or m2[f] == a: # same or local newer
253 if n == m2[f] or m2[f] == a: # same or local newer
254 # is file locally modified or flags need changing?
254 # is file locally modified or flags need changing?
255 # dirstate flags may need to be made current
255 # dirstate flags may need to be made current
256 if m1.flags(f) != rflags or n[20:]:
256 if m1.flags(f) != rflags or n[20:]:
257 act("update permissions", "e", f, rflags)
257 act("update permissions", "e", f, rflags)
258 elif n == a: # remote newer
258 elif n == a: # remote newer
259 act("remote is newer", "g", f, rflags)
259 act("remote is newer", "g", f, rflags)
260 else: # both changed
260 else: # both changed
261 act("versions differ", "m", f, f, f, rflags, False)
261 act("versions differ", "m", f, f, f, rflags, False)
262 elif f in copied: # files we'll deal with on m2 side
262 elif f in copied: # files we'll deal with on m2 side
263 pass
263 pass
264 elif f in movewithdir: # directory rename
264 elif f in movewithdir: # directory rename
265 f2 = movewithdir[f]
265 f2 = movewithdir[f]
266 act("remote renamed directory to " + f2, "d", f, None, f2,
266 act("remote renamed directory to " + f2, "d", f, None, f2,
267 m1.flags(f))
267 m1.flags(f))
268 elif f in copy: # case 2 A,B/B/B or case 4,21 A/B/B
268 elif f in copy: # case 2 A,B/B/B or case 4,21 A/B/B
269 f2 = copy[f]
269 f2 = copy[f]
270 act("local copied/moved to " + f2, "m", f, f2, f,
270 act("local copied/moved to " + f2, "m", f, f2, f,
271 fmerge(f, f2, f2), False)
271 fmerge(f, f2, f2), False)
272 elif f in ma: # clean, a different, no remote
272 elif f in ma: # clean, a different, no remote
273 if n != ma[f]:
273 if n != ma[f]:
274 if repo.ui.promptchoice(
274 if repo.ui.promptchoice(
275 _(" local changed %s which remote deleted\n"
275 _(" local changed %s which remote deleted\n"
276 "use (c)hanged version or (d)elete?") % f,
276 "use (c)hanged version or (d)elete?") % f,
277 (_("&Changed"), _("&Delete")), 0):
277 (_("&Changed"), _("&Delete")), 0):
278 act("prompt delete", "r", f)
278 act("prompt delete", "r", f)
279 else:
279 else:
280 act("prompt keep", "a", f)
280 act("prompt keep", "a", f)
281 elif n[20:] == "a": # added, no remote
281 elif n[20:] == "a": # added, no remote
282 act("remote deleted", "f", f)
282 act("remote deleted", "f", f)
283 else:
283 else:
284 act("other deleted", "r", f)
284 act("other deleted", "r", f)
285
285
286 for f, n in m2.iteritems():
286 for f, n in m2.iteritems():
287 if partial and not partial(f):
287 if partial and not partial(f):
288 continue
288 continue
289 if f in m1 or f in copied: # files already visited
289 if f in m1 or f in copied: # files already visited
290 continue
290 continue
291 if f in movewithdir:
291 if f in movewithdir:
292 f2 = movewithdir[f]
292 f2 = movewithdir[f]
293 act("local renamed directory to " + f2, "d", None, f, f2,
293 act("local renamed directory to " + f2, "d", None, f, f2,
294 m2.flags(f))
294 m2.flags(f))
295 elif f in copy:
295 elif f in copy:
296 f2 = copy[f]
296 f2 = copy[f]
297 if f2 in m2: # rename case 1, A/A,B/A
297 if f2 in m2: # rename case 1, A/A,B/A
298 act("remote copied to " + f, "m",
298 act("remote copied to " + f, "m",
299 f2, f, f, fmerge(f2, f, f2), False)
299 f2, f, f, fmerge(f2, f, f2), False)
300 else: # case 3,20 A/B/A
300 else: # case 3,20 A/B/A
301 act("remote moved to " + f, "m",
301 act("remote moved to " + f, "m",
302 f2, f, f, fmerge(f2, f, f2), True)
302 f2, f, f, fmerge(f2, f, f2), True)
303 elif f not in ma:
303 elif f not in ma:
304 if (not overwrite
304 if (not overwrite
305 and _checkunknownfile(repo, p1, p2, f)):
305 and _checkunknownfile(repo, p1, p2, f)):
306 rflags = fmerge(f, f, f)
306 rflags = fmerge(f, f, f)
307 act("remote differs from untracked local",
307 act("remote differs from untracked local",
308 "m", f, f, f, rflags, False)
308 "m", f, f, f, rflags, False)
309 else:
309 else:
310 act("remote created", "g", f, m2.flags(f))
310 act("remote created", "g", f, m2.flags(f))
311 elif n != ma[f]:
311 elif n != ma[f]:
312 if repo.ui.promptchoice(
312 if repo.ui.promptchoice(
313 _("remote changed %s which local deleted\n"
313 _("remote changed %s which local deleted\n"
314 "use (c)hanged version or leave (d)eleted?") % f,
314 "use (c)hanged version or leave (d)eleted?") % f,
315 (_("&Changed"), _("&Deleted")), 0) == 0:
315 (_("&Changed"), _("&Deleted")), 0) == 0:
316 act("prompt recreating", "g", f, m2.flags(f))
316 act("prompt recreating", "g", f, m2.flags(f))
317
317
318 return action
318 return action
319
319
320 def actionkey(a):
320 def actionkey(a):
321 return a[1] == 'r' and -1 or 0, a
321 return a[1] == 'r' and -1 or 0, a
322
322
323 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
323 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
324 """apply the merge action list to the working directory
324 """apply the merge action list to the working directory
325
325
326 wctx is the working copy context
326 wctx is the working copy context
327 mctx is the context to be merged into the working copy
327 mctx is the context to be merged into the working copy
328 actx is the context of the common ancestor
328 actx is the context of the common ancestor
329
329
330 Return a tuple of counts (updated, merged, removed, unresolved) that
330 Return a tuple of counts (updated, merged, removed, unresolved) that
331 describes how many files were affected by the update.
331 describes how many files were affected by the update.
332 """
332 """
333
333
334 updated, merged, removed, unresolved = 0, 0, 0, 0
334 updated, merged, removed, unresolved = 0, 0, 0, 0
335 ms = mergestate(repo)
335 ms = mergestate(repo)
336 ms.reset(wctx.p1().node())
336 ms.reset(wctx.p1().node())
337 moves = []
337 moves = []
338 action.sort(key=actionkey)
338 action.sort(key=actionkey)
339
339
340 # prescan for merges
340 # prescan for merges
341 for a in action:
341 for a in action:
342 f, m = a[:2]
342 f, m = a[:2]
343 if m == 'm': # merge
343 if m == 'm': # merge
344 f2, fd, flags, move = a[2:]
344 f2, fd, flags, move = a[2:]
345 if f == '.hgsubstate': # merged internally
345 if f == '.hgsubstate': # merged internally
346 continue
346 continue
347 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
347 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
348 fcl = wctx[f]
348 fcl = wctx[f]
349 fco = mctx[f2]
349 fco = mctx[f2]
350 if mctx == actx: # backwards, use working dir parent as ancestor
350 if mctx == actx: # backwards, use working dir parent as ancestor
351 if fcl.parents():
351 if fcl.parents():
352 fca = fcl.p1()
352 fca = fcl.p1()
353 else:
353 else:
354 fca = repo.filectx(f, fileid=nullrev)
354 fca = repo.filectx(f, fileid=nullrev)
355 else:
355 else:
356 fca = fcl.ancestor(fco, actx)
356 fca = fcl.ancestor(fco, actx)
357 if not fca:
357 if not fca:
358 fca = repo.filectx(f, fileid=nullrev)
358 fca = repo.filectx(f, fileid=nullrev)
359 ms.add(fcl, fco, fca, fd, flags)
359 ms.add(fcl, fco, fca, fd, flags)
360 if f != fd and move:
360 if f != fd and move:
361 moves.append(f)
361 moves.append(f)
362
362
363 audit = scmutil.pathauditor(repo.root)
363 audit = scmutil.pathauditor(repo.root)
364
364
365 # remove renamed files after safely stored
365 # remove renamed files after safely stored
366 for f in moves:
366 for f in moves:
367 if os.path.lexists(repo.wjoin(f)):
367 if os.path.lexists(repo.wjoin(f)):
368 repo.ui.debug("removing %s\n" % f)
368 repo.ui.debug("removing %s\n" % f)
369 audit(f)
369 audit(f)
370 os.unlink(repo.wjoin(f))
370 os.unlink(repo.wjoin(f))
371
371
372 numupdates = len(action)
372 numupdates = len(action)
373 for i, a in enumerate(action):
373 for i, a in enumerate(action):
374 f, m = a[:2]
374 f, m = a[:2]
375 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
375 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
376 unit=_('files'))
376 unit=_('files'))
377 if f and f[0] == "/":
377 if f and f[0] == "/":
378 continue
378 continue
379 if m == "r": # remove
379 if m == "r": # remove
380 repo.ui.note(_("removing %s\n") % f)
380 repo.ui.note(_("removing %s\n") % f)
381 audit(f)
381 audit(f)
382 if f == '.hgsubstate': # subrepo states need updating
382 if f == '.hgsubstate': # subrepo states need updating
383 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
383 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
384 try:
384 try:
385 util.unlinkpath(repo.wjoin(f))
385 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
386 except OSError, inst:
386 except OSError, inst:
387 if inst.errno != errno.ENOENT:
387 repo.ui.warn(_("update failed to remove %s: %s!\n") %
388 repo.ui.warn(_("update failed to remove %s: %s!\n") %
388 (f, inst.strerror))
389 (f, inst.strerror))
390 removed += 1
389 removed += 1
391 elif m == "m": # merge
390 elif m == "m": # merge
392 if f == '.hgsubstate': # subrepo states need updating
391 if f == '.hgsubstate': # subrepo states need updating
393 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
392 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
394 overwrite)
393 overwrite)
395 continue
394 continue
396 f2, fd, flags, move = a[2:]
395 f2, fd, flags, move = a[2:]
397 repo.wopener.audit(fd)
396 repo.wopener.audit(fd)
398 r = ms.resolve(fd, wctx, mctx)
397 r = ms.resolve(fd, wctx, mctx)
399 if r is not None and r > 0:
398 if r is not None and r > 0:
400 unresolved += 1
399 unresolved += 1
401 else:
400 else:
402 if r is None:
401 if r is None:
403 updated += 1
402 updated += 1
404 else:
403 else:
405 merged += 1
404 merged += 1
406 if (move and repo.dirstate.normalize(fd) != f
405 if (move and repo.dirstate.normalize(fd) != f
407 and os.path.lexists(repo.wjoin(f))):
406 and os.path.lexists(repo.wjoin(f))):
408 repo.ui.debug("removing %s\n" % f)
407 repo.ui.debug("removing %s\n" % f)
409 audit(f)
408 audit(f)
410 os.unlink(repo.wjoin(f))
409 os.unlink(repo.wjoin(f))
411 elif m == "g": # get
410 elif m == "g": # get
412 flags = a[2]
411 flags = a[2]
413 repo.ui.note(_("getting %s\n") % f)
412 repo.ui.note(_("getting %s\n") % f)
414 t = mctx.filectx(f).data()
413 t = mctx.filectx(f).data()
415 repo.wwrite(f, t, flags)
414 repo.wwrite(f, t, flags)
416 t = None
415 t = None
417 updated += 1
416 updated += 1
418 if f == '.hgsubstate': # subrepo states need updating
417 if f == '.hgsubstate': # subrepo states need updating
419 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
418 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
420 elif m == "d": # directory rename
419 elif m == "d": # directory rename
421 f2, fd, flags = a[2:]
420 f2, fd, flags = a[2:]
422 if f:
421 if f:
423 repo.ui.note(_("moving %s to %s\n") % (f, fd))
422 repo.ui.note(_("moving %s to %s\n") % (f, fd))
424 audit(f)
423 audit(f)
425 t = wctx.filectx(f).data()
424 t = wctx.filectx(f).data()
426 repo.wwrite(fd, t, flags)
425 repo.wwrite(fd, t, flags)
427 util.unlinkpath(repo.wjoin(f))
426 util.unlinkpath(repo.wjoin(f))
428 if f2:
427 if f2:
429 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
428 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
430 t = mctx.filectx(f2).data()
429 t = mctx.filectx(f2).data()
431 repo.wwrite(fd, t, flags)
430 repo.wwrite(fd, t, flags)
432 updated += 1
431 updated += 1
433 elif m == "dr": # divergent renames
432 elif m == "dr": # divergent renames
434 fl = a[2]
433 fl = a[2]
435 repo.ui.warn(_("note: possible conflict - %s was renamed "
434 repo.ui.warn(_("note: possible conflict - %s was renamed "
436 "multiple times to:\n") % f)
435 "multiple times to:\n") % f)
437 for nf in fl:
436 for nf in fl:
438 repo.ui.warn(" %s\n" % nf)
437 repo.ui.warn(" %s\n" % nf)
439 elif m == "rd": # rename and delete
438 elif m == "rd": # rename and delete
440 fl = a[2]
439 fl = a[2]
441 repo.ui.warn(_("note: possible conflict - %s was deleted "
440 repo.ui.warn(_("note: possible conflict - %s was deleted "
442 "and renamed to:\n") % f)
441 "and renamed to:\n") % f)
443 for nf in fl:
442 for nf in fl:
444 repo.ui.warn(" %s\n" % nf)
443 repo.ui.warn(" %s\n" % nf)
445 elif m == "e": # exec
444 elif m == "e": # exec
446 flags = a[2]
445 flags = a[2]
447 repo.wopener.audit(f)
446 repo.wopener.audit(f)
448 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
447 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
449 ms.commit()
448 ms.commit()
450 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
449 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
451
450
452 return updated, merged, removed, unresolved
451 return updated, merged, removed, unresolved
453
452
454 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
453 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
455 "Calculate the actions needed to merge mctx into tctx"
454 "Calculate the actions needed to merge mctx into tctx"
456 action = []
455 action = []
457 folding = not util.checkcase(repo.path)
456 folding = not util.checkcase(repo.path)
458 if folding:
457 if folding:
459 # collision check is not needed for clean update
458 # collision check is not needed for clean update
460 if (not branchmerge and
459 if (not branchmerge and
461 (force or not tctx.dirty(missing=True, branch=False))):
460 (force or not tctx.dirty(missing=True, branch=False))):
462 _checkcollision(mctx, None)
461 _checkcollision(mctx, None)
463 else:
462 else:
464 _checkcollision(mctx, (tctx, ancestor))
463 _checkcollision(mctx, (tctx, ancestor))
465 if not force:
464 if not force:
466 _checkunknown(repo, tctx, mctx)
465 _checkunknown(repo, tctx, mctx)
467 if tctx.rev() is None:
466 if tctx.rev() is None:
468 action += _forgetremoved(tctx, mctx, branchmerge)
467 action += _forgetremoved(tctx, mctx, branchmerge)
469 action += manifestmerge(repo, tctx, mctx,
468 action += manifestmerge(repo, tctx, mctx,
470 ancestor,
469 ancestor,
471 force and not branchmerge,
470 force and not branchmerge,
472 partial)
471 partial)
473 return action
472 return action
474
473
475 def recordupdates(repo, action, branchmerge):
474 def recordupdates(repo, action, branchmerge):
476 "record merge actions to the dirstate"
475 "record merge actions to the dirstate"
477
476
478 for a in action:
477 for a in action:
479 f, m = a[:2]
478 f, m = a[:2]
480 if m == "r": # remove
479 if m == "r": # remove
481 if branchmerge:
480 if branchmerge:
482 repo.dirstate.remove(f)
481 repo.dirstate.remove(f)
483 else:
482 else:
484 repo.dirstate.drop(f)
483 repo.dirstate.drop(f)
485 elif m == "a": # re-add
484 elif m == "a": # re-add
486 if not branchmerge:
485 if not branchmerge:
487 repo.dirstate.add(f)
486 repo.dirstate.add(f)
488 elif m == "f": # forget
487 elif m == "f": # forget
489 repo.dirstate.drop(f)
488 repo.dirstate.drop(f)
490 elif m == "e": # exec change
489 elif m == "e": # exec change
491 repo.dirstate.normallookup(f)
490 repo.dirstate.normallookup(f)
492 elif m == "g": # get
491 elif m == "g": # get
493 if branchmerge:
492 if branchmerge:
494 repo.dirstate.otherparent(f)
493 repo.dirstate.otherparent(f)
495 else:
494 else:
496 repo.dirstate.normal(f)
495 repo.dirstate.normal(f)
497 elif m == "m": # merge
496 elif m == "m": # merge
498 f2, fd, flag, move = a[2:]
497 f2, fd, flag, move = a[2:]
499 if branchmerge:
498 if branchmerge:
500 # We've done a branch merge, mark this file as merged
499 # We've done a branch merge, mark this file as merged
501 # so that we properly record the merger later
500 # so that we properly record the merger later
502 repo.dirstate.merge(fd)
501 repo.dirstate.merge(fd)
503 if f != f2: # copy/rename
502 if f != f2: # copy/rename
504 if move:
503 if move:
505 repo.dirstate.remove(f)
504 repo.dirstate.remove(f)
506 if f != fd:
505 if f != fd:
507 repo.dirstate.copy(f, fd)
506 repo.dirstate.copy(f, fd)
508 else:
507 else:
509 repo.dirstate.copy(f2, fd)
508 repo.dirstate.copy(f2, fd)
510 else:
509 else:
511 # We've update-merged a locally modified file, so
510 # We've update-merged a locally modified file, so
512 # we set the dirstate to emulate a normal checkout
511 # we set the dirstate to emulate a normal checkout
513 # of that file some time in the past. Thus our
512 # of that file some time in the past. Thus our
514 # merge will appear as a normal local file
513 # merge will appear as a normal local file
515 # modification.
514 # modification.
516 if f2 == fd: # file not locally copied/moved
515 if f2 == fd: # file not locally copied/moved
517 repo.dirstate.normallookup(fd)
516 repo.dirstate.normallookup(fd)
518 if move:
517 if move:
519 repo.dirstate.drop(f)
518 repo.dirstate.drop(f)
520 elif m == "d": # directory rename
519 elif m == "d": # directory rename
521 f2, fd, flag = a[2:]
520 f2, fd, flag = a[2:]
522 if not f2 and f not in repo.dirstate:
521 if not f2 and f not in repo.dirstate:
523 # untracked file moved
522 # untracked file moved
524 continue
523 continue
525 if branchmerge:
524 if branchmerge:
526 repo.dirstate.add(fd)
525 repo.dirstate.add(fd)
527 if f:
526 if f:
528 repo.dirstate.remove(f)
527 repo.dirstate.remove(f)
529 repo.dirstate.copy(f, fd)
528 repo.dirstate.copy(f, fd)
530 if f2:
529 if f2:
531 repo.dirstate.copy(f2, fd)
530 repo.dirstate.copy(f2, fd)
532 else:
531 else:
533 repo.dirstate.normal(fd)
532 repo.dirstate.normal(fd)
534 if f:
533 if f:
535 repo.dirstate.drop(f)
534 repo.dirstate.drop(f)
536
535
537 def update(repo, node, branchmerge, force, partial, ancestor=None,
536 def update(repo, node, branchmerge, force, partial, ancestor=None,
538 mergeancestor=False):
537 mergeancestor=False):
539 """
538 """
540 Perform a merge between the working directory and the given node
539 Perform a merge between the working directory and the given node
541
540
542 node = the node to update to, or None if unspecified
541 node = the node to update to, or None if unspecified
543 branchmerge = whether to merge between branches
542 branchmerge = whether to merge between branches
544 force = whether to force branch merging or file overwriting
543 force = whether to force branch merging or file overwriting
545 partial = a function to filter file lists (dirstate not updated)
544 partial = a function to filter file lists (dirstate not updated)
546 mergeancestor = if false, merging with an ancestor (fast-forward)
545 mergeancestor = if false, merging with an ancestor (fast-forward)
547 is only allowed between different named branches. This flag
546 is only allowed between different named branches. This flag
548 is used by rebase extension as a temporary fix and should be
547 is used by rebase extension as a temporary fix and should be
549 avoided in general.
548 avoided in general.
550
549
551 The table below shows all the behaviors of the update command
550 The table below shows all the behaviors of the update command
552 given the -c and -C or no options, whether the working directory
551 given the -c and -C or no options, whether the working directory
553 is dirty, whether a revision is specified, and the relationship of
552 is dirty, whether a revision is specified, and the relationship of
554 the parent rev to the target rev (linear, on the same named
553 the parent rev to the target rev (linear, on the same named
555 branch, or on another named branch).
554 branch, or on another named branch).
556
555
557 This logic is tested by test-update-branches.t.
556 This logic is tested by test-update-branches.t.
558
557
559 -c -C dirty rev | linear same cross
558 -c -C dirty rev | linear same cross
560 n n n n | ok (1) x
559 n n n n | ok (1) x
561 n n n y | ok ok ok
560 n n n y | ok ok ok
562 n n y * | merge (2) (2)
561 n n y * | merge (2) (2)
563 n y * * | --- discard ---
562 n y * * | --- discard ---
564 y n y * | --- (3) ---
563 y n y * | --- (3) ---
565 y n n * | --- ok ---
564 y n n * | --- ok ---
566 y y * * | --- (4) ---
565 y y * * | --- (4) ---
567
566
568 x = can't happen
567 x = can't happen
569 * = don't-care
568 * = don't-care
570 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
569 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
571 2 = abort: crosses branches (use 'hg merge' to merge or
570 2 = abort: crosses branches (use 'hg merge' to merge or
572 use 'hg update -C' to discard changes)
571 use 'hg update -C' to discard changes)
573 3 = abort: uncommitted local changes
572 3 = abort: uncommitted local changes
574 4 = incompatible options (checked in commands.py)
573 4 = incompatible options (checked in commands.py)
575
574
576 Return the same tuple as applyupdates().
575 Return the same tuple as applyupdates().
577 """
576 """
578
577
579 onode = node
578 onode = node
580 wlock = repo.wlock()
579 wlock = repo.wlock()
581 try:
580 try:
582 wc = repo[None]
581 wc = repo[None]
583 if node is None:
582 if node is None:
584 # tip of current branch
583 # tip of current branch
585 try:
584 try:
586 node = repo.branchtip(wc.branch())
585 node = repo.branchtip(wc.branch())
587 except error.RepoLookupError:
586 except error.RepoLookupError:
588 if wc.branch() == "default": # no default branch!
587 if wc.branch() == "default": # no default branch!
589 node = repo.lookup("tip") # update to tip
588 node = repo.lookup("tip") # update to tip
590 else:
589 else:
591 raise util.Abort(_("branch %s not found") % wc.branch())
590 raise util.Abort(_("branch %s not found") % wc.branch())
592 overwrite = force and not branchmerge
591 overwrite = force and not branchmerge
593 pl = wc.parents()
592 pl = wc.parents()
594 p1, p2 = pl[0], repo[node]
593 p1, p2 = pl[0], repo[node]
595 if ancestor:
594 if ancestor:
596 pa = repo[ancestor]
595 pa = repo[ancestor]
597 else:
596 else:
598 pa = p1.ancestor(p2)
597 pa = p1.ancestor(p2)
599
598
600 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
599 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
601
600
602 ### check phase
601 ### check phase
603 if not overwrite and len(pl) > 1:
602 if not overwrite and len(pl) > 1:
604 raise util.Abort(_("outstanding uncommitted merges"))
603 raise util.Abort(_("outstanding uncommitted merges"))
605 if branchmerge:
604 if branchmerge:
606 if pa == p2:
605 if pa == p2:
607 raise util.Abort(_("merging with a working directory ancestor"
606 raise util.Abort(_("merging with a working directory ancestor"
608 " has no effect"))
607 " has no effect"))
609 elif pa == p1:
608 elif pa == p1:
610 if not mergeancestor and p1.branch() == p2.branch():
609 if not mergeancestor and p1.branch() == p2.branch():
611 raise util.Abort(_("nothing to merge"),
610 raise util.Abort(_("nothing to merge"),
612 hint=_("use 'hg update' "
611 hint=_("use 'hg update' "
613 "or check 'hg heads'"))
612 "or check 'hg heads'"))
614 if not force and (wc.files() or wc.deleted()):
613 if not force and (wc.files() or wc.deleted()):
615 raise util.Abort(_("outstanding uncommitted changes"),
614 raise util.Abort(_("outstanding uncommitted changes"),
616 hint=_("use 'hg status' to list changes"))
615 hint=_("use 'hg status' to list changes"))
617 for s in wc.substate:
616 for s in wc.substate:
618 if wc.sub(s).dirty():
617 if wc.sub(s).dirty():
619 raise util.Abort(_("outstanding uncommitted changes in "
618 raise util.Abort(_("outstanding uncommitted changes in "
620 "subrepository '%s'") % s)
619 "subrepository '%s'") % s)
621
620
622 elif not overwrite:
621 elif not overwrite:
623 if pa == p1 or pa == p2: # linear
622 if pa == p1 or pa == p2: # linear
624 pass # all good
623 pass # all good
625 elif wc.dirty(missing=True):
624 elif wc.dirty(missing=True):
626 raise util.Abort(_("crosses branches (merge branches or use"
625 raise util.Abort(_("crosses branches (merge branches or use"
627 " --clean to discard changes)"))
626 " --clean to discard changes)"))
628 elif onode is None:
627 elif onode is None:
629 raise util.Abort(_("crosses branches (merge branches or update"
628 raise util.Abort(_("crosses branches (merge branches or update"
630 " --check to force update)"))
629 " --check to force update)"))
631 else:
630 else:
632 # Allow jumping branches if clean and specific rev given
631 # Allow jumping branches if clean and specific rev given
633 pa = p1
632 pa = p1
634
633
635 ### calculate phase
634 ### calculate phase
636 action = calculateupdates(repo, wc, p2, pa, branchmerge, force, partial)
635 action = calculateupdates(repo, wc, p2, pa, branchmerge, force, partial)
637
636
638 ### apply phase
637 ### apply phase
639 if not branchmerge: # just jump to the new rev
638 if not branchmerge: # just jump to the new rev
640 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
639 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
641 if not partial:
640 if not partial:
642 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
641 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
643
642
644 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
643 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
645
644
646 if not partial:
645 if not partial:
647 repo.setparents(fp1, fp2)
646 repo.setparents(fp1, fp2)
648 recordupdates(repo, action, branchmerge)
647 recordupdates(repo, action, branchmerge)
649 if not branchmerge:
648 if not branchmerge:
650 repo.dirstate.setbranch(p2.branch())
649 repo.dirstate.setbranch(p2.branch())
651 finally:
650 finally:
652 wlock.release()
651 wlock.release()
653
652
654 if not partial:
653 if not partial:
655 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
654 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
656 return stats
655 return stats
@@ -1,1890 +1,1886 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
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 import cStringIO, email.Parser, os, errno, re, posixpath
9 import cStringIO, email.Parser, os, errno, re, posixpath
10 import tempfile, zlib, shutil
10 import tempfile, zlib, shutil
11
11
12 from i18n import _
12 from i18n import _
13 from node import hex, nullid, short
13 from node import hex, nullid, short
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
15 import context
15 import context
16
16
17 gitre = re.compile('diff --git a/(.*) b/(.*)')
17 gitre = re.compile('diff --git a/(.*) b/(.*)')
18
18
19 class PatchError(Exception):
19 class PatchError(Exception):
20 pass
20 pass
21
21
22
22
23 # public functions
23 # public functions
24
24
25 def split(stream):
25 def split(stream):
26 '''return an iterator of individual patches from a stream'''
26 '''return an iterator of individual patches from a stream'''
27 def isheader(line, inheader):
27 def isheader(line, inheader):
28 if inheader and line[0] in (' ', '\t'):
28 if inheader and line[0] in (' ', '\t'):
29 # continuation
29 # continuation
30 return True
30 return True
31 if line[0] in (' ', '-', '+'):
31 if line[0] in (' ', '-', '+'):
32 # diff line - don't check for header pattern in there
32 # diff line - don't check for header pattern in there
33 return False
33 return False
34 l = line.split(': ', 1)
34 l = line.split(': ', 1)
35 return len(l) == 2 and ' ' not in l[0]
35 return len(l) == 2 and ' ' not in l[0]
36
36
37 def chunk(lines):
37 def chunk(lines):
38 return cStringIO.StringIO(''.join(lines))
38 return cStringIO.StringIO(''.join(lines))
39
39
40 def hgsplit(stream, cur):
40 def hgsplit(stream, cur):
41 inheader = True
41 inheader = True
42
42
43 for line in stream:
43 for line in stream:
44 if not line.strip():
44 if not line.strip():
45 inheader = False
45 inheader = False
46 if not inheader and line.startswith('# HG changeset patch'):
46 if not inheader and line.startswith('# HG changeset patch'):
47 yield chunk(cur)
47 yield chunk(cur)
48 cur = []
48 cur = []
49 inheader = True
49 inheader = True
50
50
51 cur.append(line)
51 cur.append(line)
52
52
53 if cur:
53 if cur:
54 yield chunk(cur)
54 yield chunk(cur)
55
55
56 def mboxsplit(stream, cur):
56 def mboxsplit(stream, cur):
57 for line in stream:
57 for line in stream:
58 if line.startswith('From '):
58 if line.startswith('From '):
59 for c in split(chunk(cur[1:])):
59 for c in split(chunk(cur[1:])):
60 yield c
60 yield c
61 cur = []
61 cur = []
62
62
63 cur.append(line)
63 cur.append(line)
64
64
65 if cur:
65 if cur:
66 for c in split(chunk(cur[1:])):
66 for c in split(chunk(cur[1:])):
67 yield c
67 yield c
68
68
69 def mimesplit(stream, cur):
69 def mimesplit(stream, cur):
70 def msgfp(m):
70 def msgfp(m):
71 fp = cStringIO.StringIO()
71 fp = cStringIO.StringIO()
72 g = email.Generator.Generator(fp, mangle_from_=False)
72 g = email.Generator.Generator(fp, mangle_from_=False)
73 g.flatten(m)
73 g.flatten(m)
74 fp.seek(0)
74 fp.seek(0)
75 return fp
75 return fp
76
76
77 for line in stream:
77 for line in stream:
78 cur.append(line)
78 cur.append(line)
79 c = chunk(cur)
79 c = chunk(cur)
80
80
81 m = email.Parser.Parser().parse(c)
81 m = email.Parser.Parser().parse(c)
82 if not m.is_multipart():
82 if not m.is_multipart():
83 yield msgfp(m)
83 yield msgfp(m)
84 else:
84 else:
85 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
85 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
86 for part in m.walk():
86 for part in m.walk():
87 ct = part.get_content_type()
87 ct = part.get_content_type()
88 if ct not in ok_types:
88 if ct not in ok_types:
89 continue
89 continue
90 yield msgfp(part)
90 yield msgfp(part)
91
91
92 def headersplit(stream, cur):
92 def headersplit(stream, cur):
93 inheader = False
93 inheader = False
94
94
95 for line in stream:
95 for line in stream:
96 if not inheader and isheader(line, inheader):
96 if not inheader and isheader(line, inheader):
97 yield chunk(cur)
97 yield chunk(cur)
98 cur = []
98 cur = []
99 inheader = True
99 inheader = True
100 if inheader and not isheader(line, inheader):
100 if inheader and not isheader(line, inheader):
101 inheader = False
101 inheader = False
102
102
103 cur.append(line)
103 cur.append(line)
104
104
105 if cur:
105 if cur:
106 yield chunk(cur)
106 yield chunk(cur)
107
107
108 def remainder(cur):
108 def remainder(cur):
109 yield chunk(cur)
109 yield chunk(cur)
110
110
111 class fiter(object):
111 class fiter(object):
112 def __init__(self, fp):
112 def __init__(self, fp):
113 self.fp = fp
113 self.fp = fp
114
114
115 def __iter__(self):
115 def __iter__(self):
116 return self
116 return self
117
117
118 def next(self):
118 def next(self):
119 l = self.fp.readline()
119 l = self.fp.readline()
120 if not l:
120 if not l:
121 raise StopIteration
121 raise StopIteration
122 return l
122 return l
123
123
124 inheader = False
124 inheader = False
125 cur = []
125 cur = []
126
126
127 mimeheaders = ['content-type']
127 mimeheaders = ['content-type']
128
128
129 if not util.safehasattr(stream, 'next'):
129 if not util.safehasattr(stream, 'next'):
130 # http responses, for example, have readline but not next
130 # http responses, for example, have readline but not next
131 stream = fiter(stream)
131 stream = fiter(stream)
132
132
133 for line in stream:
133 for line in stream:
134 cur.append(line)
134 cur.append(line)
135 if line.startswith('# HG changeset patch'):
135 if line.startswith('# HG changeset patch'):
136 return hgsplit(stream, cur)
136 return hgsplit(stream, cur)
137 elif line.startswith('From '):
137 elif line.startswith('From '):
138 return mboxsplit(stream, cur)
138 return mboxsplit(stream, cur)
139 elif isheader(line, inheader):
139 elif isheader(line, inheader):
140 inheader = True
140 inheader = True
141 if line.split(':', 1)[0].lower() in mimeheaders:
141 if line.split(':', 1)[0].lower() in mimeheaders:
142 # let email parser handle this
142 # let email parser handle this
143 return mimesplit(stream, cur)
143 return mimesplit(stream, cur)
144 elif line.startswith('--- ') and inheader:
144 elif line.startswith('--- ') and inheader:
145 # No evil headers seen by diff start, split by hand
145 # No evil headers seen by diff start, split by hand
146 return headersplit(stream, cur)
146 return headersplit(stream, cur)
147 # Not enough info, keep reading
147 # Not enough info, keep reading
148
148
149 # if we are here, we have a very plain patch
149 # if we are here, we have a very plain patch
150 return remainder(cur)
150 return remainder(cur)
151
151
152 def extract(ui, fileobj):
152 def extract(ui, fileobj):
153 '''extract patch from data read from fileobj.
153 '''extract patch from data read from fileobj.
154
154
155 patch can be a normal patch or contained in an email message.
155 patch can be a normal patch or contained in an email message.
156
156
157 return tuple (filename, message, user, date, branch, node, p1, p2).
157 return tuple (filename, message, user, date, branch, node, p1, p2).
158 Any item in the returned tuple can be None. If filename is None,
158 Any item in the returned tuple can be None. If filename is None,
159 fileobj did not contain a patch. Caller must unlink filename when done.'''
159 fileobj did not contain a patch. Caller must unlink filename when done.'''
160
160
161 # attempt to detect the start of a patch
161 # attempt to detect the start of a patch
162 # (this heuristic is borrowed from quilt)
162 # (this heuristic is borrowed from quilt)
163 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
163 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
164 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
164 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
165 r'---[ \t].*?^\+\+\+[ \t]|'
165 r'---[ \t].*?^\+\+\+[ \t]|'
166 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
166 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
167
167
168 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
168 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
169 tmpfp = os.fdopen(fd, 'w')
169 tmpfp = os.fdopen(fd, 'w')
170 try:
170 try:
171 msg = email.Parser.Parser().parse(fileobj)
171 msg = email.Parser.Parser().parse(fileobj)
172
172
173 subject = msg['Subject']
173 subject = msg['Subject']
174 user = msg['From']
174 user = msg['From']
175 if not subject and not user:
175 if not subject and not user:
176 # Not an email, restore parsed headers if any
176 # Not an email, restore parsed headers if any
177 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
177 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
178
178
179 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
179 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
180 # should try to parse msg['Date']
180 # should try to parse msg['Date']
181 date = None
181 date = None
182 nodeid = None
182 nodeid = None
183 branch = None
183 branch = None
184 parents = []
184 parents = []
185
185
186 if subject:
186 if subject:
187 if subject.startswith('[PATCH'):
187 if subject.startswith('[PATCH'):
188 pend = subject.find(']')
188 pend = subject.find(']')
189 if pend >= 0:
189 if pend >= 0:
190 subject = subject[pend + 1:].lstrip()
190 subject = subject[pend + 1:].lstrip()
191 subject = re.sub(r'\n[ \t]+', ' ', subject)
191 subject = re.sub(r'\n[ \t]+', ' ', subject)
192 ui.debug('Subject: %s\n' % subject)
192 ui.debug('Subject: %s\n' % subject)
193 if user:
193 if user:
194 ui.debug('From: %s\n' % user)
194 ui.debug('From: %s\n' % user)
195 diffs_seen = 0
195 diffs_seen = 0
196 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
196 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
197 message = ''
197 message = ''
198 for part in msg.walk():
198 for part in msg.walk():
199 content_type = part.get_content_type()
199 content_type = part.get_content_type()
200 ui.debug('Content-Type: %s\n' % content_type)
200 ui.debug('Content-Type: %s\n' % content_type)
201 if content_type not in ok_types:
201 if content_type not in ok_types:
202 continue
202 continue
203 payload = part.get_payload(decode=True)
203 payload = part.get_payload(decode=True)
204 m = diffre.search(payload)
204 m = diffre.search(payload)
205 if m:
205 if m:
206 hgpatch = False
206 hgpatch = False
207 hgpatchheader = False
207 hgpatchheader = False
208 ignoretext = False
208 ignoretext = False
209
209
210 ui.debug('found patch at byte %d\n' % m.start(0))
210 ui.debug('found patch at byte %d\n' % m.start(0))
211 diffs_seen += 1
211 diffs_seen += 1
212 cfp = cStringIO.StringIO()
212 cfp = cStringIO.StringIO()
213 for line in payload[:m.start(0)].splitlines():
213 for line in payload[:m.start(0)].splitlines():
214 if line.startswith('# HG changeset patch') and not hgpatch:
214 if line.startswith('# HG changeset patch') and not hgpatch:
215 ui.debug('patch generated by hg export\n')
215 ui.debug('patch generated by hg export\n')
216 hgpatch = True
216 hgpatch = True
217 hgpatchheader = True
217 hgpatchheader = True
218 # drop earlier commit message content
218 # drop earlier commit message content
219 cfp.seek(0)
219 cfp.seek(0)
220 cfp.truncate()
220 cfp.truncate()
221 subject = None
221 subject = None
222 elif hgpatchheader:
222 elif hgpatchheader:
223 if line.startswith('# User '):
223 if line.startswith('# User '):
224 user = line[7:]
224 user = line[7:]
225 ui.debug('From: %s\n' % user)
225 ui.debug('From: %s\n' % user)
226 elif line.startswith("# Date "):
226 elif line.startswith("# Date "):
227 date = line[7:]
227 date = line[7:]
228 elif line.startswith("# Branch "):
228 elif line.startswith("# Branch "):
229 branch = line[9:]
229 branch = line[9:]
230 elif line.startswith("# Node ID "):
230 elif line.startswith("# Node ID "):
231 nodeid = line[10:]
231 nodeid = line[10:]
232 elif line.startswith("# Parent "):
232 elif line.startswith("# Parent "):
233 parents.append(line[9:].lstrip())
233 parents.append(line[9:].lstrip())
234 elif not line.startswith("# "):
234 elif not line.startswith("# "):
235 hgpatchheader = False
235 hgpatchheader = False
236 elif line == '---' and gitsendmail:
236 elif line == '---' and gitsendmail:
237 ignoretext = True
237 ignoretext = True
238 if not hgpatchheader and not ignoretext:
238 if not hgpatchheader and not ignoretext:
239 cfp.write(line)
239 cfp.write(line)
240 cfp.write('\n')
240 cfp.write('\n')
241 message = cfp.getvalue()
241 message = cfp.getvalue()
242 if tmpfp:
242 if tmpfp:
243 tmpfp.write(payload)
243 tmpfp.write(payload)
244 if not payload.endswith('\n'):
244 if not payload.endswith('\n'):
245 tmpfp.write('\n')
245 tmpfp.write('\n')
246 elif not diffs_seen and message and content_type == 'text/plain':
246 elif not diffs_seen and message and content_type == 'text/plain':
247 message += '\n' + payload
247 message += '\n' + payload
248 except: # re-raises
248 except: # re-raises
249 tmpfp.close()
249 tmpfp.close()
250 os.unlink(tmpname)
250 os.unlink(tmpname)
251 raise
251 raise
252
252
253 if subject and not message.startswith(subject):
253 if subject and not message.startswith(subject):
254 message = '%s\n%s' % (subject, message)
254 message = '%s\n%s' % (subject, message)
255 tmpfp.close()
255 tmpfp.close()
256 if not diffs_seen:
256 if not diffs_seen:
257 os.unlink(tmpname)
257 os.unlink(tmpname)
258 return None, message, user, date, branch, None, None, None
258 return None, message, user, date, branch, None, None, None
259 p1 = parents and parents.pop(0) or None
259 p1 = parents and parents.pop(0) or None
260 p2 = parents and parents.pop(0) or None
260 p2 = parents and parents.pop(0) or None
261 return tmpname, message, user, date, branch, nodeid, p1, p2
261 return tmpname, message, user, date, branch, nodeid, p1, p2
262
262
263 class patchmeta(object):
263 class patchmeta(object):
264 """Patched file metadata
264 """Patched file metadata
265
265
266 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
266 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
267 or COPY. 'path' is patched file path. 'oldpath' is set to the
267 or COPY. 'path' is patched file path. 'oldpath' is set to the
268 origin file when 'op' is either COPY or RENAME, None otherwise. If
268 origin file when 'op' is either COPY or RENAME, None otherwise. If
269 file mode is changed, 'mode' is a tuple (islink, isexec) where
269 file mode is changed, 'mode' is a tuple (islink, isexec) where
270 'islink' is True if the file is a symlink and 'isexec' is True if
270 'islink' is True if the file is a symlink and 'isexec' is True if
271 the file is executable. Otherwise, 'mode' is None.
271 the file is executable. Otherwise, 'mode' is None.
272 """
272 """
273 def __init__(self, path):
273 def __init__(self, path):
274 self.path = path
274 self.path = path
275 self.oldpath = None
275 self.oldpath = None
276 self.mode = None
276 self.mode = None
277 self.op = 'MODIFY'
277 self.op = 'MODIFY'
278 self.binary = False
278 self.binary = False
279
279
280 def setmode(self, mode):
280 def setmode(self, mode):
281 islink = mode & 020000
281 islink = mode & 020000
282 isexec = mode & 0100
282 isexec = mode & 0100
283 self.mode = (islink, isexec)
283 self.mode = (islink, isexec)
284
284
285 def copy(self):
285 def copy(self):
286 other = patchmeta(self.path)
286 other = patchmeta(self.path)
287 other.oldpath = self.oldpath
287 other.oldpath = self.oldpath
288 other.mode = self.mode
288 other.mode = self.mode
289 other.op = self.op
289 other.op = self.op
290 other.binary = self.binary
290 other.binary = self.binary
291 return other
291 return other
292
292
293 def _ispatchinga(self, afile):
293 def _ispatchinga(self, afile):
294 if afile == '/dev/null':
294 if afile == '/dev/null':
295 return self.op == 'ADD'
295 return self.op == 'ADD'
296 return afile == 'a/' + (self.oldpath or self.path)
296 return afile == 'a/' + (self.oldpath or self.path)
297
297
298 def _ispatchingb(self, bfile):
298 def _ispatchingb(self, bfile):
299 if bfile == '/dev/null':
299 if bfile == '/dev/null':
300 return self.op == 'DELETE'
300 return self.op == 'DELETE'
301 return bfile == 'b/' + self.path
301 return bfile == 'b/' + self.path
302
302
303 def ispatching(self, afile, bfile):
303 def ispatching(self, afile, bfile):
304 return self._ispatchinga(afile) and self._ispatchingb(bfile)
304 return self._ispatchinga(afile) and self._ispatchingb(bfile)
305
305
306 def __repr__(self):
306 def __repr__(self):
307 return "<patchmeta %s %r>" % (self.op, self.path)
307 return "<patchmeta %s %r>" % (self.op, self.path)
308
308
309 def readgitpatch(lr):
309 def readgitpatch(lr):
310 """extract git-style metadata about patches from <patchname>"""
310 """extract git-style metadata about patches from <patchname>"""
311
311
312 # Filter patch for git information
312 # Filter patch for git information
313 gp = None
313 gp = None
314 gitpatches = []
314 gitpatches = []
315 for line in lr:
315 for line in lr:
316 line = line.rstrip(' \r\n')
316 line = line.rstrip(' \r\n')
317 if line.startswith('diff --git'):
317 if line.startswith('diff --git'):
318 m = gitre.match(line)
318 m = gitre.match(line)
319 if m:
319 if m:
320 if gp:
320 if gp:
321 gitpatches.append(gp)
321 gitpatches.append(gp)
322 dst = m.group(2)
322 dst = m.group(2)
323 gp = patchmeta(dst)
323 gp = patchmeta(dst)
324 elif gp:
324 elif gp:
325 if line.startswith('--- '):
325 if line.startswith('--- '):
326 gitpatches.append(gp)
326 gitpatches.append(gp)
327 gp = None
327 gp = None
328 continue
328 continue
329 if line.startswith('rename from '):
329 if line.startswith('rename from '):
330 gp.op = 'RENAME'
330 gp.op = 'RENAME'
331 gp.oldpath = line[12:]
331 gp.oldpath = line[12:]
332 elif line.startswith('rename to '):
332 elif line.startswith('rename to '):
333 gp.path = line[10:]
333 gp.path = line[10:]
334 elif line.startswith('copy from '):
334 elif line.startswith('copy from '):
335 gp.op = 'COPY'
335 gp.op = 'COPY'
336 gp.oldpath = line[10:]
336 gp.oldpath = line[10:]
337 elif line.startswith('copy to '):
337 elif line.startswith('copy to '):
338 gp.path = line[8:]
338 gp.path = line[8:]
339 elif line.startswith('deleted file'):
339 elif line.startswith('deleted file'):
340 gp.op = 'DELETE'
340 gp.op = 'DELETE'
341 elif line.startswith('new file mode '):
341 elif line.startswith('new file mode '):
342 gp.op = 'ADD'
342 gp.op = 'ADD'
343 gp.setmode(int(line[-6:], 8))
343 gp.setmode(int(line[-6:], 8))
344 elif line.startswith('new mode '):
344 elif line.startswith('new mode '):
345 gp.setmode(int(line[-6:], 8))
345 gp.setmode(int(line[-6:], 8))
346 elif line.startswith('GIT binary patch'):
346 elif line.startswith('GIT binary patch'):
347 gp.binary = True
347 gp.binary = True
348 if gp:
348 if gp:
349 gitpatches.append(gp)
349 gitpatches.append(gp)
350
350
351 return gitpatches
351 return gitpatches
352
352
353 class linereader(object):
353 class linereader(object):
354 # simple class to allow pushing lines back into the input stream
354 # simple class to allow pushing lines back into the input stream
355 def __init__(self, fp):
355 def __init__(self, fp):
356 self.fp = fp
356 self.fp = fp
357 self.buf = []
357 self.buf = []
358
358
359 def push(self, line):
359 def push(self, line):
360 if line is not None:
360 if line is not None:
361 self.buf.append(line)
361 self.buf.append(line)
362
362
363 def readline(self):
363 def readline(self):
364 if self.buf:
364 if self.buf:
365 l = self.buf[0]
365 l = self.buf[0]
366 del self.buf[0]
366 del self.buf[0]
367 return l
367 return l
368 return self.fp.readline()
368 return self.fp.readline()
369
369
370 def __iter__(self):
370 def __iter__(self):
371 while True:
371 while True:
372 l = self.readline()
372 l = self.readline()
373 if not l:
373 if not l:
374 break
374 break
375 yield l
375 yield l
376
376
377 class abstractbackend(object):
377 class abstractbackend(object):
378 def __init__(self, ui):
378 def __init__(self, ui):
379 self.ui = ui
379 self.ui = ui
380
380
381 def getfile(self, fname):
381 def getfile(self, fname):
382 """Return target file data and flags as a (data, (islink,
382 """Return target file data and flags as a (data, (islink,
383 isexec)) tuple.
383 isexec)) tuple.
384 """
384 """
385 raise NotImplementedError
385 raise NotImplementedError
386
386
387 def setfile(self, fname, data, mode, copysource):
387 def setfile(self, fname, data, mode, copysource):
388 """Write data to target file fname and set its mode. mode is a
388 """Write data to target file fname and set its mode. mode is a
389 (islink, isexec) tuple. If data is None, the file content should
389 (islink, isexec) tuple. If data is None, the file content should
390 be left unchanged. If the file is modified after being copied,
390 be left unchanged. If the file is modified after being copied,
391 copysource is set to the original file name.
391 copysource is set to the original file name.
392 """
392 """
393 raise NotImplementedError
393 raise NotImplementedError
394
394
395 def unlink(self, fname):
395 def unlink(self, fname):
396 """Unlink target file."""
396 """Unlink target file."""
397 raise NotImplementedError
397 raise NotImplementedError
398
398
399 def writerej(self, fname, failed, total, lines):
399 def writerej(self, fname, failed, total, lines):
400 """Write rejected lines for fname. total is the number of hunks
400 """Write rejected lines for fname. total is the number of hunks
401 which failed to apply and total the total number of hunks for this
401 which failed to apply and total the total number of hunks for this
402 files.
402 files.
403 """
403 """
404 pass
404 pass
405
405
406 def exists(self, fname):
406 def exists(self, fname):
407 raise NotImplementedError
407 raise NotImplementedError
408
408
409 class fsbackend(abstractbackend):
409 class fsbackend(abstractbackend):
410 def __init__(self, ui, basedir):
410 def __init__(self, ui, basedir):
411 super(fsbackend, self).__init__(ui)
411 super(fsbackend, self).__init__(ui)
412 self.opener = scmutil.opener(basedir)
412 self.opener = scmutil.opener(basedir)
413
413
414 def _join(self, f):
414 def _join(self, f):
415 return os.path.join(self.opener.base, f)
415 return os.path.join(self.opener.base, f)
416
416
417 def getfile(self, fname):
417 def getfile(self, fname):
418 path = self._join(fname)
418 path = self._join(fname)
419 if os.path.islink(path):
419 if os.path.islink(path):
420 return (os.readlink(path), (True, False))
420 return (os.readlink(path), (True, False))
421 isexec = False
421 isexec = False
422 try:
422 try:
423 isexec = os.lstat(path).st_mode & 0100 != 0
423 isexec = os.lstat(path).st_mode & 0100 != 0
424 except OSError, e:
424 except OSError, e:
425 if e.errno != errno.ENOENT:
425 if e.errno != errno.ENOENT:
426 raise
426 raise
427 return (self.opener.read(fname), (False, isexec))
427 return (self.opener.read(fname), (False, isexec))
428
428
429 def setfile(self, fname, data, mode, copysource):
429 def setfile(self, fname, data, mode, copysource):
430 islink, isexec = mode
430 islink, isexec = mode
431 if data is None:
431 if data is None:
432 util.setflags(self._join(fname), islink, isexec)
432 util.setflags(self._join(fname), islink, isexec)
433 return
433 return
434 if islink:
434 if islink:
435 self.opener.symlink(data, fname)
435 self.opener.symlink(data, fname)
436 else:
436 else:
437 self.opener.write(fname, data)
437 self.opener.write(fname, data)
438 if isexec:
438 if isexec:
439 util.setflags(self._join(fname), False, True)
439 util.setflags(self._join(fname), False, True)
440
440
441 def unlink(self, fname):
441 def unlink(self, fname):
442 try:
442 util.unlinkpath(self._join(fname), ignoremissing=True)
443 util.unlinkpath(self._join(fname))
444 except OSError, inst:
445 if inst.errno != errno.ENOENT:
446 raise
447
443
448 def writerej(self, fname, failed, total, lines):
444 def writerej(self, fname, failed, total, lines):
449 fname = fname + ".rej"
445 fname = fname + ".rej"
450 self.ui.warn(
446 self.ui.warn(
451 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
447 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
452 (failed, total, fname))
448 (failed, total, fname))
453 fp = self.opener(fname, 'w')
449 fp = self.opener(fname, 'w')
454 fp.writelines(lines)
450 fp.writelines(lines)
455 fp.close()
451 fp.close()
456
452
457 def exists(self, fname):
453 def exists(self, fname):
458 return os.path.lexists(self._join(fname))
454 return os.path.lexists(self._join(fname))
459
455
460 class workingbackend(fsbackend):
456 class workingbackend(fsbackend):
461 def __init__(self, ui, repo, similarity):
457 def __init__(self, ui, repo, similarity):
462 super(workingbackend, self).__init__(ui, repo.root)
458 super(workingbackend, self).__init__(ui, repo.root)
463 self.repo = repo
459 self.repo = repo
464 self.similarity = similarity
460 self.similarity = similarity
465 self.removed = set()
461 self.removed = set()
466 self.changed = set()
462 self.changed = set()
467 self.copied = []
463 self.copied = []
468
464
469 def _checkknown(self, fname):
465 def _checkknown(self, fname):
470 if self.repo.dirstate[fname] == '?' and self.exists(fname):
466 if self.repo.dirstate[fname] == '?' and self.exists(fname):
471 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
467 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
472
468
473 def setfile(self, fname, data, mode, copysource):
469 def setfile(self, fname, data, mode, copysource):
474 self._checkknown(fname)
470 self._checkknown(fname)
475 super(workingbackend, self).setfile(fname, data, mode, copysource)
471 super(workingbackend, self).setfile(fname, data, mode, copysource)
476 if copysource is not None:
472 if copysource is not None:
477 self.copied.append((copysource, fname))
473 self.copied.append((copysource, fname))
478 self.changed.add(fname)
474 self.changed.add(fname)
479
475
480 def unlink(self, fname):
476 def unlink(self, fname):
481 self._checkknown(fname)
477 self._checkknown(fname)
482 super(workingbackend, self).unlink(fname)
478 super(workingbackend, self).unlink(fname)
483 self.removed.add(fname)
479 self.removed.add(fname)
484 self.changed.add(fname)
480 self.changed.add(fname)
485
481
486 def close(self):
482 def close(self):
487 wctx = self.repo[None]
483 wctx = self.repo[None]
488 addremoved = set(self.changed)
484 addremoved = set(self.changed)
489 for src, dst in self.copied:
485 for src, dst in self.copied:
490 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
486 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
491 if self.removed:
487 if self.removed:
492 wctx.forget(sorted(self.removed))
488 wctx.forget(sorted(self.removed))
493 for f in self.removed:
489 for f in self.removed:
494 if f not in self.repo.dirstate:
490 if f not in self.repo.dirstate:
495 # File was deleted and no longer belongs to the
491 # File was deleted and no longer belongs to the
496 # dirstate, it was probably marked added then
492 # dirstate, it was probably marked added then
497 # deleted, and should not be considered by
493 # deleted, and should not be considered by
498 # addremove().
494 # addremove().
499 addremoved.discard(f)
495 addremoved.discard(f)
500 if addremoved:
496 if addremoved:
501 cwd = self.repo.getcwd()
497 cwd = self.repo.getcwd()
502 if cwd:
498 if cwd:
503 addremoved = [util.pathto(self.repo.root, cwd, f)
499 addremoved = [util.pathto(self.repo.root, cwd, f)
504 for f in addremoved]
500 for f in addremoved]
505 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
501 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
506 return sorted(self.changed)
502 return sorted(self.changed)
507
503
508 class filestore(object):
504 class filestore(object):
509 def __init__(self, maxsize=None):
505 def __init__(self, maxsize=None):
510 self.opener = None
506 self.opener = None
511 self.files = {}
507 self.files = {}
512 self.created = 0
508 self.created = 0
513 self.maxsize = maxsize
509 self.maxsize = maxsize
514 if self.maxsize is None:
510 if self.maxsize is None:
515 self.maxsize = 4*(2**20)
511 self.maxsize = 4*(2**20)
516 self.size = 0
512 self.size = 0
517 self.data = {}
513 self.data = {}
518
514
519 def setfile(self, fname, data, mode, copied=None):
515 def setfile(self, fname, data, mode, copied=None):
520 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
516 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
521 self.data[fname] = (data, mode, copied)
517 self.data[fname] = (data, mode, copied)
522 self.size += len(data)
518 self.size += len(data)
523 else:
519 else:
524 if self.opener is None:
520 if self.opener is None:
525 root = tempfile.mkdtemp(prefix='hg-patch-')
521 root = tempfile.mkdtemp(prefix='hg-patch-')
526 self.opener = scmutil.opener(root)
522 self.opener = scmutil.opener(root)
527 # Avoid filename issues with these simple names
523 # Avoid filename issues with these simple names
528 fn = str(self.created)
524 fn = str(self.created)
529 self.opener.write(fn, data)
525 self.opener.write(fn, data)
530 self.created += 1
526 self.created += 1
531 self.files[fname] = (fn, mode, copied)
527 self.files[fname] = (fn, mode, copied)
532
528
533 def getfile(self, fname):
529 def getfile(self, fname):
534 if fname in self.data:
530 if fname in self.data:
535 return self.data[fname]
531 return self.data[fname]
536 if not self.opener or fname not in self.files:
532 if not self.opener or fname not in self.files:
537 raise IOError
533 raise IOError
538 fn, mode, copied = self.files[fname]
534 fn, mode, copied = self.files[fname]
539 return self.opener.read(fn), mode, copied
535 return self.opener.read(fn), mode, copied
540
536
541 def close(self):
537 def close(self):
542 if self.opener:
538 if self.opener:
543 shutil.rmtree(self.opener.base)
539 shutil.rmtree(self.opener.base)
544
540
545 class repobackend(abstractbackend):
541 class repobackend(abstractbackend):
546 def __init__(self, ui, repo, ctx, store):
542 def __init__(self, ui, repo, ctx, store):
547 super(repobackend, self).__init__(ui)
543 super(repobackend, self).__init__(ui)
548 self.repo = repo
544 self.repo = repo
549 self.ctx = ctx
545 self.ctx = ctx
550 self.store = store
546 self.store = store
551 self.changed = set()
547 self.changed = set()
552 self.removed = set()
548 self.removed = set()
553 self.copied = {}
549 self.copied = {}
554
550
555 def _checkknown(self, fname):
551 def _checkknown(self, fname):
556 if fname not in self.ctx:
552 if fname not in self.ctx:
557 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
553 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
558
554
559 def getfile(self, fname):
555 def getfile(self, fname):
560 try:
556 try:
561 fctx = self.ctx[fname]
557 fctx = self.ctx[fname]
562 except error.LookupError:
558 except error.LookupError:
563 raise IOError
559 raise IOError
564 flags = fctx.flags()
560 flags = fctx.flags()
565 return fctx.data(), ('l' in flags, 'x' in flags)
561 return fctx.data(), ('l' in flags, 'x' in flags)
566
562
567 def setfile(self, fname, data, mode, copysource):
563 def setfile(self, fname, data, mode, copysource):
568 if copysource:
564 if copysource:
569 self._checkknown(copysource)
565 self._checkknown(copysource)
570 if data is None:
566 if data is None:
571 data = self.ctx[fname].data()
567 data = self.ctx[fname].data()
572 self.store.setfile(fname, data, mode, copysource)
568 self.store.setfile(fname, data, mode, copysource)
573 self.changed.add(fname)
569 self.changed.add(fname)
574 if copysource:
570 if copysource:
575 self.copied[fname] = copysource
571 self.copied[fname] = copysource
576
572
577 def unlink(self, fname):
573 def unlink(self, fname):
578 self._checkknown(fname)
574 self._checkknown(fname)
579 self.removed.add(fname)
575 self.removed.add(fname)
580
576
581 def exists(self, fname):
577 def exists(self, fname):
582 return fname in self.ctx
578 return fname in self.ctx
583
579
584 def close(self):
580 def close(self):
585 return self.changed | self.removed
581 return self.changed | self.removed
586
582
587 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
583 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
588 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
584 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
589 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
585 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
590 eolmodes = ['strict', 'crlf', 'lf', 'auto']
586 eolmodes = ['strict', 'crlf', 'lf', 'auto']
591
587
592 class patchfile(object):
588 class patchfile(object):
593 def __init__(self, ui, gp, backend, store, eolmode='strict'):
589 def __init__(self, ui, gp, backend, store, eolmode='strict'):
594 self.fname = gp.path
590 self.fname = gp.path
595 self.eolmode = eolmode
591 self.eolmode = eolmode
596 self.eol = None
592 self.eol = None
597 self.backend = backend
593 self.backend = backend
598 self.ui = ui
594 self.ui = ui
599 self.lines = []
595 self.lines = []
600 self.exists = False
596 self.exists = False
601 self.missing = True
597 self.missing = True
602 self.mode = gp.mode
598 self.mode = gp.mode
603 self.copysource = gp.oldpath
599 self.copysource = gp.oldpath
604 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
600 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
605 self.remove = gp.op == 'DELETE'
601 self.remove = gp.op == 'DELETE'
606 try:
602 try:
607 if self.copysource is None:
603 if self.copysource is None:
608 data, mode = backend.getfile(self.fname)
604 data, mode = backend.getfile(self.fname)
609 self.exists = True
605 self.exists = True
610 else:
606 else:
611 data, mode = store.getfile(self.copysource)[:2]
607 data, mode = store.getfile(self.copysource)[:2]
612 self.exists = backend.exists(self.fname)
608 self.exists = backend.exists(self.fname)
613 self.missing = False
609 self.missing = False
614 if data:
610 if data:
615 self.lines = mdiff.splitnewlines(data)
611 self.lines = mdiff.splitnewlines(data)
616 if self.mode is None:
612 if self.mode is None:
617 self.mode = mode
613 self.mode = mode
618 if self.lines:
614 if self.lines:
619 # Normalize line endings
615 # Normalize line endings
620 if self.lines[0].endswith('\r\n'):
616 if self.lines[0].endswith('\r\n'):
621 self.eol = '\r\n'
617 self.eol = '\r\n'
622 elif self.lines[0].endswith('\n'):
618 elif self.lines[0].endswith('\n'):
623 self.eol = '\n'
619 self.eol = '\n'
624 if eolmode != 'strict':
620 if eolmode != 'strict':
625 nlines = []
621 nlines = []
626 for l in self.lines:
622 for l in self.lines:
627 if l.endswith('\r\n'):
623 if l.endswith('\r\n'):
628 l = l[:-2] + '\n'
624 l = l[:-2] + '\n'
629 nlines.append(l)
625 nlines.append(l)
630 self.lines = nlines
626 self.lines = nlines
631 except IOError:
627 except IOError:
632 if self.create:
628 if self.create:
633 self.missing = False
629 self.missing = False
634 if self.mode is None:
630 if self.mode is None:
635 self.mode = (False, False)
631 self.mode = (False, False)
636 if self.missing:
632 if self.missing:
637 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
633 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
638
634
639 self.hash = {}
635 self.hash = {}
640 self.dirty = 0
636 self.dirty = 0
641 self.offset = 0
637 self.offset = 0
642 self.skew = 0
638 self.skew = 0
643 self.rej = []
639 self.rej = []
644 self.fileprinted = False
640 self.fileprinted = False
645 self.printfile(False)
641 self.printfile(False)
646 self.hunks = 0
642 self.hunks = 0
647
643
648 def writelines(self, fname, lines, mode):
644 def writelines(self, fname, lines, mode):
649 if self.eolmode == 'auto':
645 if self.eolmode == 'auto':
650 eol = self.eol
646 eol = self.eol
651 elif self.eolmode == 'crlf':
647 elif self.eolmode == 'crlf':
652 eol = '\r\n'
648 eol = '\r\n'
653 else:
649 else:
654 eol = '\n'
650 eol = '\n'
655
651
656 if self.eolmode != 'strict' and eol and eol != '\n':
652 if self.eolmode != 'strict' and eol and eol != '\n':
657 rawlines = []
653 rawlines = []
658 for l in lines:
654 for l in lines:
659 if l and l[-1] == '\n':
655 if l and l[-1] == '\n':
660 l = l[:-1] + eol
656 l = l[:-1] + eol
661 rawlines.append(l)
657 rawlines.append(l)
662 lines = rawlines
658 lines = rawlines
663
659
664 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
660 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
665
661
666 def printfile(self, warn):
662 def printfile(self, warn):
667 if self.fileprinted:
663 if self.fileprinted:
668 return
664 return
669 if warn or self.ui.verbose:
665 if warn or self.ui.verbose:
670 self.fileprinted = True
666 self.fileprinted = True
671 s = _("patching file %s\n") % self.fname
667 s = _("patching file %s\n") % self.fname
672 if warn:
668 if warn:
673 self.ui.warn(s)
669 self.ui.warn(s)
674 else:
670 else:
675 self.ui.note(s)
671 self.ui.note(s)
676
672
677
673
678 def findlines(self, l, linenum):
674 def findlines(self, l, linenum):
679 # looks through the hash and finds candidate lines. The
675 # looks through the hash and finds candidate lines. The
680 # result is a list of line numbers sorted based on distance
676 # result is a list of line numbers sorted based on distance
681 # from linenum
677 # from linenum
682
678
683 cand = self.hash.get(l, [])
679 cand = self.hash.get(l, [])
684 if len(cand) > 1:
680 if len(cand) > 1:
685 # resort our list of potentials forward then back.
681 # resort our list of potentials forward then back.
686 cand.sort(key=lambda x: abs(x - linenum))
682 cand.sort(key=lambda x: abs(x - linenum))
687 return cand
683 return cand
688
684
689 def write_rej(self):
685 def write_rej(self):
690 # our rejects are a little different from patch(1). This always
686 # our rejects are a little different from patch(1). This always
691 # creates rejects in the same form as the original patch. A file
687 # creates rejects in the same form as the original patch. A file
692 # header is inserted so that you can run the reject through patch again
688 # header is inserted so that you can run the reject through patch again
693 # without having to type the filename.
689 # without having to type the filename.
694 if not self.rej:
690 if not self.rej:
695 return
691 return
696 base = os.path.basename(self.fname)
692 base = os.path.basename(self.fname)
697 lines = ["--- %s\n+++ %s\n" % (base, base)]
693 lines = ["--- %s\n+++ %s\n" % (base, base)]
698 for x in self.rej:
694 for x in self.rej:
699 for l in x.hunk:
695 for l in x.hunk:
700 lines.append(l)
696 lines.append(l)
701 if l[-1] != '\n':
697 if l[-1] != '\n':
702 lines.append("\n\ No newline at end of file\n")
698 lines.append("\n\ No newline at end of file\n")
703 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
699 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
704
700
705 def apply(self, h):
701 def apply(self, h):
706 if not h.complete():
702 if not h.complete():
707 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
703 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
708 (h.number, h.desc, len(h.a), h.lena, len(h.b),
704 (h.number, h.desc, len(h.a), h.lena, len(h.b),
709 h.lenb))
705 h.lenb))
710
706
711 self.hunks += 1
707 self.hunks += 1
712
708
713 if self.missing:
709 if self.missing:
714 self.rej.append(h)
710 self.rej.append(h)
715 return -1
711 return -1
716
712
717 if self.exists and self.create:
713 if self.exists and self.create:
718 if self.copysource:
714 if self.copysource:
719 self.ui.warn(_("cannot create %s: destination already "
715 self.ui.warn(_("cannot create %s: destination already "
720 "exists\n" % self.fname))
716 "exists\n" % self.fname))
721 else:
717 else:
722 self.ui.warn(_("file %s already exists\n") % self.fname)
718 self.ui.warn(_("file %s already exists\n") % self.fname)
723 self.rej.append(h)
719 self.rej.append(h)
724 return -1
720 return -1
725
721
726 if isinstance(h, binhunk):
722 if isinstance(h, binhunk):
727 if self.remove:
723 if self.remove:
728 self.backend.unlink(self.fname)
724 self.backend.unlink(self.fname)
729 else:
725 else:
730 self.lines[:] = h.new()
726 self.lines[:] = h.new()
731 self.offset += len(h.new())
727 self.offset += len(h.new())
732 self.dirty = True
728 self.dirty = True
733 return 0
729 return 0
734
730
735 horig = h
731 horig = h
736 if (self.eolmode in ('crlf', 'lf')
732 if (self.eolmode in ('crlf', 'lf')
737 or self.eolmode == 'auto' and self.eol):
733 or self.eolmode == 'auto' and self.eol):
738 # If new eols are going to be normalized, then normalize
734 # If new eols are going to be normalized, then normalize
739 # hunk data before patching. Otherwise, preserve input
735 # hunk data before patching. Otherwise, preserve input
740 # line-endings.
736 # line-endings.
741 h = h.getnormalized()
737 h = h.getnormalized()
742
738
743 # fast case first, no offsets, no fuzz
739 # fast case first, no offsets, no fuzz
744 old, oldstart, new, newstart = h.fuzzit(0, False)
740 old, oldstart, new, newstart = h.fuzzit(0, False)
745 oldstart += self.offset
741 oldstart += self.offset
746 orig_start = oldstart
742 orig_start = oldstart
747 # if there's skew we want to emit the "(offset %d lines)" even
743 # if there's skew we want to emit the "(offset %d lines)" even
748 # when the hunk cleanly applies at start + skew, so skip the
744 # when the hunk cleanly applies at start + skew, so skip the
749 # fast case code
745 # fast case code
750 if (self.skew == 0 and
746 if (self.skew == 0 and
751 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
747 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
752 if self.remove:
748 if self.remove:
753 self.backend.unlink(self.fname)
749 self.backend.unlink(self.fname)
754 else:
750 else:
755 self.lines[oldstart:oldstart + len(old)] = new
751 self.lines[oldstart:oldstart + len(old)] = new
756 self.offset += len(new) - len(old)
752 self.offset += len(new) - len(old)
757 self.dirty = True
753 self.dirty = True
758 return 0
754 return 0
759
755
760 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
756 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
761 self.hash = {}
757 self.hash = {}
762 for x, s in enumerate(self.lines):
758 for x, s in enumerate(self.lines):
763 self.hash.setdefault(s, []).append(x)
759 self.hash.setdefault(s, []).append(x)
764
760
765 for fuzzlen in xrange(3):
761 for fuzzlen in xrange(3):
766 for toponly in [True, False]:
762 for toponly in [True, False]:
767 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
763 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
768 oldstart = oldstart + self.offset + self.skew
764 oldstart = oldstart + self.offset + self.skew
769 oldstart = min(oldstart, len(self.lines))
765 oldstart = min(oldstart, len(self.lines))
770 if old:
766 if old:
771 cand = self.findlines(old[0][1:], oldstart)
767 cand = self.findlines(old[0][1:], oldstart)
772 else:
768 else:
773 # Only adding lines with no or fuzzed context, just
769 # Only adding lines with no or fuzzed context, just
774 # take the skew in account
770 # take the skew in account
775 cand = [oldstart]
771 cand = [oldstart]
776
772
777 for l in cand:
773 for l in cand:
778 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
774 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
779 self.lines[l : l + len(old)] = new
775 self.lines[l : l + len(old)] = new
780 self.offset += len(new) - len(old)
776 self.offset += len(new) - len(old)
781 self.skew = l - orig_start
777 self.skew = l - orig_start
782 self.dirty = True
778 self.dirty = True
783 offset = l - orig_start - fuzzlen
779 offset = l - orig_start - fuzzlen
784 if fuzzlen:
780 if fuzzlen:
785 msg = _("Hunk #%d succeeded at %d "
781 msg = _("Hunk #%d succeeded at %d "
786 "with fuzz %d "
782 "with fuzz %d "
787 "(offset %d lines).\n")
783 "(offset %d lines).\n")
788 self.printfile(True)
784 self.printfile(True)
789 self.ui.warn(msg %
785 self.ui.warn(msg %
790 (h.number, l + 1, fuzzlen, offset))
786 (h.number, l + 1, fuzzlen, offset))
791 else:
787 else:
792 msg = _("Hunk #%d succeeded at %d "
788 msg = _("Hunk #%d succeeded at %d "
793 "(offset %d lines).\n")
789 "(offset %d lines).\n")
794 self.ui.note(msg % (h.number, l + 1, offset))
790 self.ui.note(msg % (h.number, l + 1, offset))
795 return fuzzlen
791 return fuzzlen
796 self.printfile(True)
792 self.printfile(True)
797 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
793 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
798 self.rej.append(horig)
794 self.rej.append(horig)
799 return -1
795 return -1
800
796
801 def close(self):
797 def close(self):
802 if self.dirty:
798 if self.dirty:
803 self.writelines(self.fname, self.lines, self.mode)
799 self.writelines(self.fname, self.lines, self.mode)
804 self.write_rej()
800 self.write_rej()
805 return len(self.rej)
801 return len(self.rej)
806
802
807 class hunk(object):
803 class hunk(object):
808 def __init__(self, desc, num, lr, context):
804 def __init__(self, desc, num, lr, context):
809 self.number = num
805 self.number = num
810 self.desc = desc
806 self.desc = desc
811 self.hunk = [desc]
807 self.hunk = [desc]
812 self.a = []
808 self.a = []
813 self.b = []
809 self.b = []
814 self.starta = self.lena = None
810 self.starta = self.lena = None
815 self.startb = self.lenb = None
811 self.startb = self.lenb = None
816 if lr is not None:
812 if lr is not None:
817 if context:
813 if context:
818 self.read_context_hunk(lr)
814 self.read_context_hunk(lr)
819 else:
815 else:
820 self.read_unified_hunk(lr)
816 self.read_unified_hunk(lr)
821
817
822 def getnormalized(self):
818 def getnormalized(self):
823 """Return a copy with line endings normalized to LF."""
819 """Return a copy with line endings normalized to LF."""
824
820
825 def normalize(lines):
821 def normalize(lines):
826 nlines = []
822 nlines = []
827 for line in lines:
823 for line in lines:
828 if line.endswith('\r\n'):
824 if line.endswith('\r\n'):
829 line = line[:-2] + '\n'
825 line = line[:-2] + '\n'
830 nlines.append(line)
826 nlines.append(line)
831 return nlines
827 return nlines
832
828
833 # Dummy object, it is rebuilt manually
829 # Dummy object, it is rebuilt manually
834 nh = hunk(self.desc, self.number, None, None)
830 nh = hunk(self.desc, self.number, None, None)
835 nh.number = self.number
831 nh.number = self.number
836 nh.desc = self.desc
832 nh.desc = self.desc
837 nh.hunk = self.hunk
833 nh.hunk = self.hunk
838 nh.a = normalize(self.a)
834 nh.a = normalize(self.a)
839 nh.b = normalize(self.b)
835 nh.b = normalize(self.b)
840 nh.starta = self.starta
836 nh.starta = self.starta
841 nh.startb = self.startb
837 nh.startb = self.startb
842 nh.lena = self.lena
838 nh.lena = self.lena
843 nh.lenb = self.lenb
839 nh.lenb = self.lenb
844 return nh
840 return nh
845
841
846 def read_unified_hunk(self, lr):
842 def read_unified_hunk(self, lr):
847 m = unidesc.match(self.desc)
843 m = unidesc.match(self.desc)
848 if not m:
844 if not m:
849 raise PatchError(_("bad hunk #%d") % self.number)
845 raise PatchError(_("bad hunk #%d") % self.number)
850 self.starta, self.lena, self.startb, self.lenb = m.groups()
846 self.starta, self.lena, self.startb, self.lenb = m.groups()
851 if self.lena is None:
847 if self.lena is None:
852 self.lena = 1
848 self.lena = 1
853 else:
849 else:
854 self.lena = int(self.lena)
850 self.lena = int(self.lena)
855 if self.lenb is None:
851 if self.lenb is None:
856 self.lenb = 1
852 self.lenb = 1
857 else:
853 else:
858 self.lenb = int(self.lenb)
854 self.lenb = int(self.lenb)
859 self.starta = int(self.starta)
855 self.starta = int(self.starta)
860 self.startb = int(self.startb)
856 self.startb = int(self.startb)
861 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
857 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
862 self.b)
858 self.b)
863 # if we hit eof before finishing out the hunk, the last line will
859 # if we hit eof before finishing out the hunk, the last line will
864 # be zero length. Lets try to fix it up.
860 # be zero length. Lets try to fix it up.
865 while len(self.hunk[-1]) == 0:
861 while len(self.hunk[-1]) == 0:
866 del self.hunk[-1]
862 del self.hunk[-1]
867 del self.a[-1]
863 del self.a[-1]
868 del self.b[-1]
864 del self.b[-1]
869 self.lena -= 1
865 self.lena -= 1
870 self.lenb -= 1
866 self.lenb -= 1
871 self._fixnewline(lr)
867 self._fixnewline(lr)
872
868
873 def read_context_hunk(self, lr):
869 def read_context_hunk(self, lr):
874 self.desc = lr.readline()
870 self.desc = lr.readline()
875 m = contextdesc.match(self.desc)
871 m = contextdesc.match(self.desc)
876 if not m:
872 if not m:
877 raise PatchError(_("bad hunk #%d") % self.number)
873 raise PatchError(_("bad hunk #%d") % self.number)
878 self.starta, aend = m.groups()
874 self.starta, aend = m.groups()
879 self.starta = int(self.starta)
875 self.starta = int(self.starta)
880 if aend is None:
876 if aend is None:
881 aend = self.starta
877 aend = self.starta
882 self.lena = int(aend) - self.starta
878 self.lena = int(aend) - self.starta
883 if self.starta:
879 if self.starta:
884 self.lena += 1
880 self.lena += 1
885 for x in xrange(self.lena):
881 for x in xrange(self.lena):
886 l = lr.readline()
882 l = lr.readline()
887 if l.startswith('---'):
883 if l.startswith('---'):
888 # lines addition, old block is empty
884 # lines addition, old block is empty
889 lr.push(l)
885 lr.push(l)
890 break
886 break
891 s = l[2:]
887 s = l[2:]
892 if l.startswith('- ') or l.startswith('! '):
888 if l.startswith('- ') or l.startswith('! '):
893 u = '-' + s
889 u = '-' + s
894 elif l.startswith(' '):
890 elif l.startswith(' '):
895 u = ' ' + s
891 u = ' ' + s
896 else:
892 else:
897 raise PatchError(_("bad hunk #%d old text line %d") %
893 raise PatchError(_("bad hunk #%d old text line %d") %
898 (self.number, x))
894 (self.number, x))
899 self.a.append(u)
895 self.a.append(u)
900 self.hunk.append(u)
896 self.hunk.append(u)
901
897
902 l = lr.readline()
898 l = lr.readline()
903 if l.startswith('\ '):
899 if l.startswith('\ '):
904 s = self.a[-1][:-1]
900 s = self.a[-1][:-1]
905 self.a[-1] = s
901 self.a[-1] = s
906 self.hunk[-1] = s
902 self.hunk[-1] = s
907 l = lr.readline()
903 l = lr.readline()
908 m = contextdesc.match(l)
904 m = contextdesc.match(l)
909 if not m:
905 if not m:
910 raise PatchError(_("bad hunk #%d") % self.number)
906 raise PatchError(_("bad hunk #%d") % self.number)
911 self.startb, bend = m.groups()
907 self.startb, bend = m.groups()
912 self.startb = int(self.startb)
908 self.startb = int(self.startb)
913 if bend is None:
909 if bend is None:
914 bend = self.startb
910 bend = self.startb
915 self.lenb = int(bend) - self.startb
911 self.lenb = int(bend) - self.startb
916 if self.startb:
912 if self.startb:
917 self.lenb += 1
913 self.lenb += 1
918 hunki = 1
914 hunki = 1
919 for x in xrange(self.lenb):
915 for x in xrange(self.lenb):
920 l = lr.readline()
916 l = lr.readline()
921 if l.startswith('\ '):
917 if l.startswith('\ '):
922 # XXX: the only way to hit this is with an invalid line range.
918 # XXX: the only way to hit this is with an invalid line range.
923 # The no-eol marker is not counted in the line range, but I
919 # The no-eol marker is not counted in the line range, but I
924 # guess there are diff(1) out there which behave differently.
920 # guess there are diff(1) out there which behave differently.
925 s = self.b[-1][:-1]
921 s = self.b[-1][:-1]
926 self.b[-1] = s
922 self.b[-1] = s
927 self.hunk[hunki - 1] = s
923 self.hunk[hunki - 1] = s
928 continue
924 continue
929 if not l:
925 if not l:
930 # line deletions, new block is empty and we hit EOF
926 # line deletions, new block is empty and we hit EOF
931 lr.push(l)
927 lr.push(l)
932 break
928 break
933 s = l[2:]
929 s = l[2:]
934 if l.startswith('+ ') or l.startswith('! '):
930 if l.startswith('+ ') or l.startswith('! '):
935 u = '+' + s
931 u = '+' + s
936 elif l.startswith(' '):
932 elif l.startswith(' '):
937 u = ' ' + s
933 u = ' ' + s
938 elif len(self.b) == 0:
934 elif len(self.b) == 0:
939 # line deletions, new block is empty
935 # line deletions, new block is empty
940 lr.push(l)
936 lr.push(l)
941 break
937 break
942 else:
938 else:
943 raise PatchError(_("bad hunk #%d old text line %d") %
939 raise PatchError(_("bad hunk #%d old text line %d") %
944 (self.number, x))
940 (self.number, x))
945 self.b.append(s)
941 self.b.append(s)
946 while True:
942 while True:
947 if hunki >= len(self.hunk):
943 if hunki >= len(self.hunk):
948 h = ""
944 h = ""
949 else:
945 else:
950 h = self.hunk[hunki]
946 h = self.hunk[hunki]
951 hunki += 1
947 hunki += 1
952 if h == u:
948 if h == u:
953 break
949 break
954 elif h.startswith('-'):
950 elif h.startswith('-'):
955 continue
951 continue
956 else:
952 else:
957 self.hunk.insert(hunki - 1, u)
953 self.hunk.insert(hunki - 1, u)
958 break
954 break
959
955
960 if not self.a:
956 if not self.a:
961 # this happens when lines were only added to the hunk
957 # this happens when lines were only added to the hunk
962 for x in self.hunk:
958 for x in self.hunk:
963 if x.startswith('-') or x.startswith(' '):
959 if x.startswith('-') or x.startswith(' '):
964 self.a.append(x)
960 self.a.append(x)
965 if not self.b:
961 if not self.b:
966 # this happens when lines were only deleted from the hunk
962 # this happens when lines were only deleted from the hunk
967 for x in self.hunk:
963 for x in self.hunk:
968 if x.startswith('+') or x.startswith(' '):
964 if x.startswith('+') or x.startswith(' '):
969 self.b.append(x[1:])
965 self.b.append(x[1:])
970 # @@ -start,len +start,len @@
966 # @@ -start,len +start,len @@
971 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
967 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
972 self.startb, self.lenb)
968 self.startb, self.lenb)
973 self.hunk[0] = self.desc
969 self.hunk[0] = self.desc
974 self._fixnewline(lr)
970 self._fixnewline(lr)
975
971
976 def _fixnewline(self, lr):
972 def _fixnewline(self, lr):
977 l = lr.readline()
973 l = lr.readline()
978 if l.startswith('\ '):
974 if l.startswith('\ '):
979 diffhelpers.fix_newline(self.hunk, self.a, self.b)
975 diffhelpers.fix_newline(self.hunk, self.a, self.b)
980 else:
976 else:
981 lr.push(l)
977 lr.push(l)
982
978
983 def complete(self):
979 def complete(self):
984 return len(self.a) == self.lena and len(self.b) == self.lenb
980 return len(self.a) == self.lena and len(self.b) == self.lenb
985
981
986 def _fuzzit(self, old, new, fuzz, toponly):
982 def _fuzzit(self, old, new, fuzz, toponly):
987 # this removes context lines from the top and bottom of list 'l'. It
983 # this removes context lines from the top and bottom of list 'l'. It
988 # checks the hunk to make sure only context lines are removed, and then
984 # checks the hunk to make sure only context lines are removed, and then
989 # returns a new shortened list of lines.
985 # returns a new shortened list of lines.
990 fuzz = min(fuzz, len(old))
986 fuzz = min(fuzz, len(old))
991 if fuzz:
987 if fuzz:
992 top = 0
988 top = 0
993 bot = 0
989 bot = 0
994 hlen = len(self.hunk)
990 hlen = len(self.hunk)
995 for x in xrange(hlen - 1):
991 for x in xrange(hlen - 1):
996 # the hunk starts with the @@ line, so use x+1
992 # the hunk starts with the @@ line, so use x+1
997 if self.hunk[x + 1][0] == ' ':
993 if self.hunk[x + 1][0] == ' ':
998 top += 1
994 top += 1
999 else:
995 else:
1000 break
996 break
1001 if not toponly:
997 if not toponly:
1002 for x in xrange(hlen - 1):
998 for x in xrange(hlen - 1):
1003 if self.hunk[hlen - bot - 1][0] == ' ':
999 if self.hunk[hlen - bot - 1][0] == ' ':
1004 bot += 1
1000 bot += 1
1005 else:
1001 else:
1006 break
1002 break
1007
1003
1008 bot = min(fuzz, bot)
1004 bot = min(fuzz, bot)
1009 top = min(fuzz, top)
1005 top = min(fuzz, top)
1010 return old[top:len(old) - bot], new[top:len(new) - bot], top
1006 return old[top:len(old) - bot], new[top:len(new) - bot], top
1011 return old, new, 0
1007 return old, new, 0
1012
1008
1013 def fuzzit(self, fuzz, toponly):
1009 def fuzzit(self, fuzz, toponly):
1014 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1010 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1015 oldstart = self.starta + top
1011 oldstart = self.starta + top
1016 newstart = self.startb + top
1012 newstart = self.startb + top
1017 # zero length hunk ranges already have their start decremented
1013 # zero length hunk ranges already have their start decremented
1018 if self.lena and oldstart > 0:
1014 if self.lena and oldstart > 0:
1019 oldstart -= 1
1015 oldstart -= 1
1020 if self.lenb and newstart > 0:
1016 if self.lenb and newstart > 0:
1021 newstart -= 1
1017 newstart -= 1
1022 return old, oldstart, new, newstart
1018 return old, oldstart, new, newstart
1023
1019
1024 class binhunk(object):
1020 class binhunk(object):
1025 'A binary patch file. Only understands literals so far.'
1021 'A binary patch file. Only understands literals so far.'
1026 def __init__(self, lr, fname):
1022 def __init__(self, lr, fname):
1027 self.text = None
1023 self.text = None
1028 self.hunk = ['GIT binary patch\n']
1024 self.hunk = ['GIT binary patch\n']
1029 self._fname = fname
1025 self._fname = fname
1030 self._read(lr)
1026 self._read(lr)
1031
1027
1032 def complete(self):
1028 def complete(self):
1033 return self.text is not None
1029 return self.text is not None
1034
1030
1035 def new(self):
1031 def new(self):
1036 return [self.text]
1032 return [self.text]
1037
1033
1038 def _read(self, lr):
1034 def _read(self, lr):
1039 def getline(lr, hunk):
1035 def getline(lr, hunk):
1040 l = lr.readline()
1036 l = lr.readline()
1041 hunk.append(l)
1037 hunk.append(l)
1042 return l.rstrip('\r\n')
1038 return l.rstrip('\r\n')
1043
1039
1044 while True:
1040 while True:
1045 line = getline(lr, self.hunk)
1041 line = getline(lr, self.hunk)
1046 if not line:
1042 if not line:
1047 raise PatchError(_('could not extract "%s" binary data')
1043 raise PatchError(_('could not extract "%s" binary data')
1048 % self._fname)
1044 % self._fname)
1049 if line.startswith('literal '):
1045 if line.startswith('literal '):
1050 break
1046 break
1051 size = int(line[8:].rstrip())
1047 size = int(line[8:].rstrip())
1052 dec = []
1048 dec = []
1053 line = getline(lr, self.hunk)
1049 line = getline(lr, self.hunk)
1054 while len(line) > 1:
1050 while len(line) > 1:
1055 l = line[0]
1051 l = line[0]
1056 if l <= 'Z' and l >= 'A':
1052 if l <= 'Z' and l >= 'A':
1057 l = ord(l) - ord('A') + 1
1053 l = ord(l) - ord('A') + 1
1058 else:
1054 else:
1059 l = ord(l) - ord('a') + 27
1055 l = ord(l) - ord('a') + 27
1060 try:
1056 try:
1061 dec.append(base85.b85decode(line[1:])[:l])
1057 dec.append(base85.b85decode(line[1:])[:l])
1062 except ValueError, e:
1058 except ValueError, e:
1063 raise PatchError(_('could not decode "%s" binary patch: %s')
1059 raise PatchError(_('could not decode "%s" binary patch: %s')
1064 % (self._fname, str(e)))
1060 % (self._fname, str(e)))
1065 line = getline(lr, self.hunk)
1061 line = getline(lr, self.hunk)
1066 text = zlib.decompress(''.join(dec))
1062 text = zlib.decompress(''.join(dec))
1067 if len(text) != size:
1063 if len(text) != size:
1068 raise PatchError(_('"%s" length is %d bytes, should be %d')
1064 raise PatchError(_('"%s" length is %d bytes, should be %d')
1069 % (self._fname, len(text), size))
1065 % (self._fname, len(text), size))
1070 self.text = text
1066 self.text = text
1071
1067
1072 def parsefilename(str):
1068 def parsefilename(str):
1073 # --- filename \t|space stuff
1069 # --- filename \t|space stuff
1074 s = str[4:].rstrip('\r\n')
1070 s = str[4:].rstrip('\r\n')
1075 i = s.find('\t')
1071 i = s.find('\t')
1076 if i < 0:
1072 if i < 0:
1077 i = s.find(' ')
1073 i = s.find(' ')
1078 if i < 0:
1074 if i < 0:
1079 return s
1075 return s
1080 return s[:i]
1076 return s[:i]
1081
1077
1082 def pathstrip(path, strip):
1078 def pathstrip(path, strip):
1083 pathlen = len(path)
1079 pathlen = len(path)
1084 i = 0
1080 i = 0
1085 if strip == 0:
1081 if strip == 0:
1086 return '', path.rstrip()
1082 return '', path.rstrip()
1087 count = strip
1083 count = strip
1088 while count > 0:
1084 while count > 0:
1089 i = path.find('/', i)
1085 i = path.find('/', i)
1090 if i == -1:
1086 if i == -1:
1091 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1087 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1092 (count, strip, path))
1088 (count, strip, path))
1093 i += 1
1089 i += 1
1094 # consume '//' in the path
1090 # consume '//' in the path
1095 while i < pathlen - 1 and path[i] == '/':
1091 while i < pathlen - 1 and path[i] == '/':
1096 i += 1
1092 i += 1
1097 count -= 1
1093 count -= 1
1098 return path[:i].lstrip(), path[i:].rstrip()
1094 return path[:i].lstrip(), path[i:].rstrip()
1099
1095
1100 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
1096 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
1101 nulla = afile_orig == "/dev/null"
1097 nulla = afile_orig == "/dev/null"
1102 nullb = bfile_orig == "/dev/null"
1098 nullb = bfile_orig == "/dev/null"
1103 create = nulla and hunk.starta == 0 and hunk.lena == 0
1099 create = nulla and hunk.starta == 0 and hunk.lena == 0
1104 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1100 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1105 abase, afile = pathstrip(afile_orig, strip)
1101 abase, afile = pathstrip(afile_orig, strip)
1106 gooda = not nulla and backend.exists(afile)
1102 gooda = not nulla and backend.exists(afile)
1107 bbase, bfile = pathstrip(bfile_orig, strip)
1103 bbase, bfile = pathstrip(bfile_orig, strip)
1108 if afile == bfile:
1104 if afile == bfile:
1109 goodb = gooda
1105 goodb = gooda
1110 else:
1106 else:
1111 goodb = not nullb and backend.exists(bfile)
1107 goodb = not nullb and backend.exists(bfile)
1112 missing = not goodb and not gooda and not create
1108 missing = not goodb and not gooda and not create
1113
1109
1114 # some diff programs apparently produce patches where the afile is
1110 # some diff programs apparently produce patches where the afile is
1115 # not /dev/null, but afile starts with bfile
1111 # not /dev/null, but afile starts with bfile
1116 abasedir = afile[:afile.rfind('/') + 1]
1112 abasedir = afile[:afile.rfind('/') + 1]
1117 bbasedir = bfile[:bfile.rfind('/') + 1]
1113 bbasedir = bfile[:bfile.rfind('/') + 1]
1118 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1114 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1119 and hunk.starta == 0 and hunk.lena == 0):
1115 and hunk.starta == 0 and hunk.lena == 0):
1120 create = True
1116 create = True
1121 missing = False
1117 missing = False
1122
1118
1123 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1119 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1124 # diff is between a file and its backup. In this case, the original
1120 # diff is between a file and its backup. In this case, the original
1125 # file should be patched (see original mpatch code).
1121 # file should be patched (see original mpatch code).
1126 isbackup = (abase == bbase and bfile.startswith(afile))
1122 isbackup = (abase == bbase and bfile.startswith(afile))
1127 fname = None
1123 fname = None
1128 if not missing:
1124 if not missing:
1129 if gooda and goodb:
1125 if gooda and goodb:
1130 fname = isbackup and afile or bfile
1126 fname = isbackup and afile or bfile
1131 elif gooda:
1127 elif gooda:
1132 fname = afile
1128 fname = afile
1133
1129
1134 if not fname:
1130 if not fname:
1135 if not nullb:
1131 if not nullb:
1136 fname = isbackup and afile or bfile
1132 fname = isbackup and afile or bfile
1137 elif not nulla:
1133 elif not nulla:
1138 fname = afile
1134 fname = afile
1139 else:
1135 else:
1140 raise PatchError(_("undefined source and destination files"))
1136 raise PatchError(_("undefined source and destination files"))
1141
1137
1142 gp = patchmeta(fname)
1138 gp = patchmeta(fname)
1143 if create:
1139 if create:
1144 gp.op = 'ADD'
1140 gp.op = 'ADD'
1145 elif remove:
1141 elif remove:
1146 gp.op = 'DELETE'
1142 gp.op = 'DELETE'
1147 return gp
1143 return gp
1148
1144
1149 def scangitpatch(lr, firstline):
1145 def scangitpatch(lr, firstline):
1150 """
1146 """
1151 Git patches can emit:
1147 Git patches can emit:
1152 - rename a to b
1148 - rename a to b
1153 - change b
1149 - change b
1154 - copy a to c
1150 - copy a to c
1155 - change c
1151 - change c
1156
1152
1157 We cannot apply this sequence as-is, the renamed 'a' could not be
1153 We cannot apply this sequence as-is, the renamed 'a' could not be
1158 found for it would have been renamed already. And we cannot copy
1154 found for it would have been renamed already. And we cannot copy
1159 from 'b' instead because 'b' would have been changed already. So
1155 from 'b' instead because 'b' would have been changed already. So
1160 we scan the git patch for copy and rename commands so we can
1156 we scan the git patch for copy and rename commands so we can
1161 perform the copies ahead of time.
1157 perform the copies ahead of time.
1162 """
1158 """
1163 pos = 0
1159 pos = 0
1164 try:
1160 try:
1165 pos = lr.fp.tell()
1161 pos = lr.fp.tell()
1166 fp = lr.fp
1162 fp = lr.fp
1167 except IOError:
1163 except IOError:
1168 fp = cStringIO.StringIO(lr.fp.read())
1164 fp = cStringIO.StringIO(lr.fp.read())
1169 gitlr = linereader(fp)
1165 gitlr = linereader(fp)
1170 gitlr.push(firstline)
1166 gitlr.push(firstline)
1171 gitpatches = readgitpatch(gitlr)
1167 gitpatches = readgitpatch(gitlr)
1172 fp.seek(pos)
1168 fp.seek(pos)
1173 return gitpatches
1169 return gitpatches
1174
1170
1175 def iterhunks(fp):
1171 def iterhunks(fp):
1176 """Read a patch and yield the following events:
1172 """Read a patch and yield the following events:
1177 - ("file", afile, bfile, firsthunk): select a new target file.
1173 - ("file", afile, bfile, firsthunk): select a new target file.
1178 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1174 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1179 "file" event.
1175 "file" event.
1180 - ("git", gitchanges): current diff is in git format, gitchanges
1176 - ("git", gitchanges): current diff is in git format, gitchanges
1181 maps filenames to gitpatch records. Unique event.
1177 maps filenames to gitpatch records. Unique event.
1182 """
1178 """
1183 afile = ""
1179 afile = ""
1184 bfile = ""
1180 bfile = ""
1185 state = None
1181 state = None
1186 hunknum = 0
1182 hunknum = 0
1187 emitfile = newfile = False
1183 emitfile = newfile = False
1188 gitpatches = None
1184 gitpatches = None
1189
1185
1190 # our states
1186 # our states
1191 BFILE = 1
1187 BFILE = 1
1192 context = None
1188 context = None
1193 lr = linereader(fp)
1189 lr = linereader(fp)
1194
1190
1195 while True:
1191 while True:
1196 x = lr.readline()
1192 x = lr.readline()
1197 if not x:
1193 if not x:
1198 break
1194 break
1199 if state == BFILE and (
1195 if state == BFILE and (
1200 (not context and x[0] == '@')
1196 (not context and x[0] == '@')
1201 or (context is not False and x.startswith('***************'))
1197 or (context is not False and x.startswith('***************'))
1202 or x.startswith('GIT binary patch')):
1198 or x.startswith('GIT binary patch')):
1203 gp = None
1199 gp = None
1204 if (gitpatches and
1200 if (gitpatches and
1205 gitpatches[-1].ispatching(afile, bfile)):
1201 gitpatches[-1].ispatching(afile, bfile)):
1206 gp = gitpatches.pop()
1202 gp = gitpatches.pop()
1207 if x.startswith('GIT binary patch'):
1203 if x.startswith('GIT binary patch'):
1208 h = binhunk(lr, gp.path)
1204 h = binhunk(lr, gp.path)
1209 else:
1205 else:
1210 if context is None and x.startswith('***************'):
1206 if context is None and x.startswith('***************'):
1211 context = True
1207 context = True
1212 h = hunk(x, hunknum + 1, lr, context)
1208 h = hunk(x, hunknum + 1, lr, context)
1213 hunknum += 1
1209 hunknum += 1
1214 if emitfile:
1210 if emitfile:
1215 emitfile = False
1211 emitfile = False
1216 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1212 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1217 yield 'hunk', h
1213 yield 'hunk', h
1218 elif x.startswith('diff --git'):
1214 elif x.startswith('diff --git'):
1219 m = gitre.match(x.rstrip(' \r\n'))
1215 m = gitre.match(x.rstrip(' \r\n'))
1220 if not m:
1216 if not m:
1221 continue
1217 continue
1222 if gitpatches is None:
1218 if gitpatches is None:
1223 # scan whole input for git metadata
1219 # scan whole input for git metadata
1224 gitpatches = scangitpatch(lr, x)
1220 gitpatches = scangitpatch(lr, x)
1225 yield 'git', [g.copy() for g in gitpatches
1221 yield 'git', [g.copy() for g in gitpatches
1226 if g.op in ('COPY', 'RENAME')]
1222 if g.op in ('COPY', 'RENAME')]
1227 gitpatches.reverse()
1223 gitpatches.reverse()
1228 afile = 'a/' + m.group(1)
1224 afile = 'a/' + m.group(1)
1229 bfile = 'b/' + m.group(2)
1225 bfile = 'b/' + m.group(2)
1230 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1226 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1231 gp = gitpatches.pop()
1227 gp = gitpatches.pop()
1232 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1228 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1233 if not gitpatches:
1229 if not gitpatches:
1234 raise PatchError(_('failed to synchronize metadata for "%s"')
1230 raise PatchError(_('failed to synchronize metadata for "%s"')
1235 % afile[2:])
1231 % afile[2:])
1236 gp = gitpatches[-1]
1232 gp = gitpatches[-1]
1237 newfile = True
1233 newfile = True
1238 elif x.startswith('---'):
1234 elif x.startswith('---'):
1239 # check for a unified diff
1235 # check for a unified diff
1240 l2 = lr.readline()
1236 l2 = lr.readline()
1241 if not l2.startswith('+++'):
1237 if not l2.startswith('+++'):
1242 lr.push(l2)
1238 lr.push(l2)
1243 continue
1239 continue
1244 newfile = True
1240 newfile = True
1245 context = False
1241 context = False
1246 afile = parsefilename(x)
1242 afile = parsefilename(x)
1247 bfile = parsefilename(l2)
1243 bfile = parsefilename(l2)
1248 elif x.startswith('***'):
1244 elif x.startswith('***'):
1249 # check for a context diff
1245 # check for a context diff
1250 l2 = lr.readline()
1246 l2 = lr.readline()
1251 if not l2.startswith('---'):
1247 if not l2.startswith('---'):
1252 lr.push(l2)
1248 lr.push(l2)
1253 continue
1249 continue
1254 l3 = lr.readline()
1250 l3 = lr.readline()
1255 lr.push(l3)
1251 lr.push(l3)
1256 if not l3.startswith("***************"):
1252 if not l3.startswith("***************"):
1257 lr.push(l2)
1253 lr.push(l2)
1258 continue
1254 continue
1259 newfile = True
1255 newfile = True
1260 context = True
1256 context = True
1261 afile = parsefilename(x)
1257 afile = parsefilename(x)
1262 bfile = parsefilename(l2)
1258 bfile = parsefilename(l2)
1263
1259
1264 if newfile:
1260 if newfile:
1265 newfile = False
1261 newfile = False
1266 emitfile = True
1262 emitfile = True
1267 state = BFILE
1263 state = BFILE
1268 hunknum = 0
1264 hunknum = 0
1269
1265
1270 while gitpatches:
1266 while gitpatches:
1271 gp = gitpatches.pop()
1267 gp = gitpatches.pop()
1272 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1268 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1273
1269
1274 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
1270 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
1275 """Reads a patch from fp and tries to apply it.
1271 """Reads a patch from fp and tries to apply it.
1276
1272
1277 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1273 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1278 there was any fuzz.
1274 there was any fuzz.
1279
1275
1280 If 'eolmode' is 'strict', the patch content and patched file are
1276 If 'eolmode' is 'strict', the patch content and patched file are
1281 read in binary mode. Otherwise, line endings are ignored when
1277 read in binary mode. Otherwise, line endings are ignored when
1282 patching then normalized according to 'eolmode'.
1278 patching then normalized according to 'eolmode'.
1283 """
1279 """
1284 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1280 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1285 eolmode=eolmode)
1281 eolmode=eolmode)
1286
1282
1287 def _applydiff(ui, fp, patcher, backend, store, strip=1,
1283 def _applydiff(ui, fp, patcher, backend, store, strip=1,
1288 eolmode='strict'):
1284 eolmode='strict'):
1289
1285
1290 def pstrip(p):
1286 def pstrip(p):
1291 return pathstrip(p, strip - 1)[1]
1287 return pathstrip(p, strip - 1)[1]
1292
1288
1293 rejects = 0
1289 rejects = 0
1294 err = 0
1290 err = 0
1295 current_file = None
1291 current_file = None
1296
1292
1297 for state, values in iterhunks(fp):
1293 for state, values in iterhunks(fp):
1298 if state == 'hunk':
1294 if state == 'hunk':
1299 if not current_file:
1295 if not current_file:
1300 continue
1296 continue
1301 ret = current_file.apply(values)
1297 ret = current_file.apply(values)
1302 if ret > 0:
1298 if ret > 0:
1303 err = 1
1299 err = 1
1304 elif state == 'file':
1300 elif state == 'file':
1305 if current_file:
1301 if current_file:
1306 rejects += current_file.close()
1302 rejects += current_file.close()
1307 current_file = None
1303 current_file = None
1308 afile, bfile, first_hunk, gp = values
1304 afile, bfile, first_hunk, gp = values
1309 if gp:
1305 if gp:
1310 gp.path = pstrip(gp.path)
1306 gp.path = pstrip(gp.path)
1311 if gp.oldpath:
1307 if gp.oldpath:
1312 gp.oldpath = pstrip(gp.oldpath)
1308 gp.oldpath = pstrip(gp.oldpath)
1313 else:
1309 else:
1314 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1310 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1315 if gp.op == 'RENAME':
1311 if gp.op == 'RENAME':
1316 backend.unlink(gp.oldpath)
1312 backend.unlink(gp.oldpath)
1317 if not first_hunk:
1313 if not first_hunk:
1318 if gp.op == 'DELETE':
1314 if gp.op == 'DELETE':
1319 backend.unlink(gp.path)
1315 backend.unlink(gp.path)
1320 continue
1316 continue
1321 data, mode = None, None
1317 data, mode = None, None
1322 if gp.op in ('RENAME', 'COPY'):
1318 if gp.op in ('RENAME', 'COPY'):
1323 data, mode = store.getfile(gp.oldpath)[:2]
1319 data, mode = store.getfile(gp.oldpath)[:2]
1324 if gp.mode:
1320 if gp.mode:
1325 mode = gp.mode
1321 mode = gp.mode
1326 if gp.op == 'ADD':
1322 if gp.op == 'ADD':
1327 # Added files without content have no hunk and
1323 # Added files without content have no hunk and
1328 # must be created
1324 # must be created
1329 data = ''
1325 data = ''
1330 if data or mode:
1326 if data or mode:
1331 if (gp.op in ('ADD', 'RENAME', 'COPY')
1327 if (gp.op in ('ADD', 'RENAME', 'COPY')
1332 and backend.exists(gp.path)):
1328 and backend.exists(gp.path)):
1333 raise PatchError(_("cannot create %s: destination "
1329 raise PatchError(_("cannot create %s: destination "
1334 "already exists") % gp.path)
1330 "already exists") % gp.path)
1335 backend.setfile(gp.path, data, mode, gp.oldpath)
1331 backend.setfile(gp.path, data, mode, gp.oldpath)
1336 continue
1332 continue
1337 try:
1333 try:
1338 current_file = patcher(ui, gp, backend, store,
1334 current_file = patcher(ui, gp, backend, store,
1339 eolmode=eolmode)
1335 eolmode=eolmode)
1340 except PatchError, inst:
1336 except PatchError, inst:
1341 ui.warn(str(inst) + '\n')
1337 ui.warn(str(inst) + '\n')
1342 current_file = None
1338 current_file = None
1343 rejects += 1
1339 rejects += 1
1344 continue
1340 continue
1345 elif state == 'git':
1341 elif state == 'git':
1346 for gp in values:
1342 for gp in values:
1347 path = pstrip(gp.oldpath)
1343 path = pstrip(gp.oldpath)
1348 try:
1344 try:
1349 data, mode = backend.getfile(path)
1345 data, mode = backend.getfile(path)
1350 except IOError, e:
1346 except IOError, e:
1351 if e.errno != errno.ENOENT:
1347 if e.errno != errno.ENOENT:
1352 raise
1348 raise
1353 # The error ignored here will trigger a getfile()
1349 # The error ignored here will trigger a getfile()
1354 # error in a place more appropriate for error
1350 # error in a place more appropriate for error
1355 # handling, and will not interrupt the patching
1351 # handling, and will not interrupt the patching
1356 # process.
1352 # process.
1357 else:
1353 else:
1358 store.setfile(path, data, mode)
1354 store.setfile(path, data, mode)
1359 else:
1355 else:
1360 raise util.Abort(_('unsupported parser state: %s') % state)
1356 raise util.Abort(_('unsupported parser state: %s') % state)
1361
1357
1362 if current_file:
1358 if current_file:
1363 rejects += current_file.close()
1359 rejects += current_file.close()
1364
1360
1365 if rejects:
1361 if rejects:
1366 return -1
1362 return -1
1367 return err
1363 return err
1368
1364
1369 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1365 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1370 similarity):
1366 similarity):
1371 """use <patcher> to apply <patchname> to the working directory.
1367 """use <patcher> to apply <patchname> to the working directory.
1372 returns whether patch was applied with fuzz factor."""
1368 returns whether patch was applied with fuzz factor."""
1373
1369
1374 fuzz = False
1370 fuzz = False
1375 args = []
1371 args = []
1376 cwd = repo.root
1372 cwd = repo.root
1377 if cwd:
1373 if cwd:
1378 args.append('-d %s' % util.shellquote(cwd))
1374 args.append('-d %s' % util.shellquote(cwd))
1379 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1375 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1380 util.shellquote(patchname)))
1376 util.shellquote(patchname)))
1381 try:
1377 try:
1382 for line in fp:
1378 for line in fp:
1383 line = line.rstrip()
1379 line = line.rstrip()
1384 ui.note(line + '\n')
1380 ui.note(line + '\n')
1385 if line.startswith('patching file '):
1381 if line.startswith('patching file '):
1386 pf = util.parsepatchoutput(line)
1382 pf = util.parsepatchoutput(line)
1387 printed_file = False
1383 printed_file = False
1388 files.add(pf)
1384 files.add(pf)
1389 elif line.find('with fuzz') >= 0:
1385 elif line.find('with fuzz') >= 0:
1390 fuzz = True
1386 fuzz = True
1391 if not printed_file:
1387 if not printed_file:
1392 ui.warn(pf + '\n')
1388 ui.warn(pf + '\n')
1393 printed_file = True
1389 printed_file = True
1394 ui.warn(line + '\n')
1390 ui.warn(line + '\n')
1395 elif line.find('saving rejects to file') >= 0:
1391 elif line.find('saving rejects to file') >= 0:
1396 ui.warn(line + '\n')
1392 ui.warn(line + '\n')
1397 elif line.find('FAILED') >= 0:
1393 elif line.find('FAILED') >= 0:
1398 if not printed_file:
1394 if not printed_file:
1399 ui.warn(pf + '\n')
1395 ui.warn(pf + '\n')
1400 printed_file = True
1396 printed_file = True
1401 ui.warn(line + '\n')
1397 ui.warn(line + '\n')
1402 finally:
1398 finally:
1403 if files:
1399 if files:
1404 cfiles = list(files)
1400 cfiles = list(files)
1405 cwd = repo.getcwd()
1401 cwd = repo.getcwd()
1406 if cwd:
1402 if cwd:
1407 cfiles = [util.pathto(repo.root, cwd, f)
1403 cfiles = [util.pathto(repo.root, cwd, f)
1408 for f in cfiles]
1404 for f in cfiles]
1409 scmutil.addremove(repo, cfiles, similarity=similarity)
1405 scmutil.addremove(repo, cfiles, similarity=similarity)
1410 code = fp.close()
1406 code = fp.close()
1411 if code:
1407 if code:
1412 raise PatchError(_("patch command failed: %s") %
1408 raise PatchError(_("patch command failed: %s") %
1413 util.explainexit(code)[0])
1409 util.explainexit(code)[0])
1414 return fuzz
1410 return fuzz
1415
1411
1416 def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
1412 def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
1417 if files is None:
1413 if files is None:
1418 files = set()
1414 files = set()
1419 if eolmode is None:
1415 if eolmode is None:
1420 eolmode = ui.config('patch', 'eol', 'strict')
1416 eolmode = ui.config('patch', 'eol', 'strict')
1421 if eolmode.lower() not in eolmodes:
1417 if eolmode.lower() not in eolmodes:
1422 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1418 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1423 eolmode = eolmode.lower()
1419 eolmode = eolmode.lower()
1424
1420
1425 store = filestore()
1421 store = filestore()
1426 try:
1422 try:
1427 fp = open(patchobj, 'rb')
1423 fp = open(patchobj, 'rb')
1428 except TypeError:
1424 except TypeError:
1429 fp = patchobj
1425 fp = patchobj
1430 try:
1426 try:
1431 ret = applydiff(ui, fp, backend, store, strip=strip,
1427 ret = applydiff(ui, fp, backend, store, strip=strip,
1432 eolmode=eolmode)
1428 eolmode=eolmode)
1433 finally:
1429 finally:
1434 if fp != patchobj:
1430 if fp != patchobj:
1435 fp.close()
1431 fp.close()
1436 files.update(backend.close())
1432 files.update(backend.close())
1437 store.close()
1433 store.close()
1438 if ret < 0:
1434 if ret < 0:
1439 raise PatchError(_('patch failed to apply'))
1435 raise PatchError(_('patch failed to apply'))
1440 return ret > 0
1436 return ret > 0
1441
1437
1442 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1438 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1443 similarity=0):
1439 similarity=0):
1444 """use builtin patch to apply <patchobj> to the working directory.
1440 """use builtin patch to apply <patchobj> to the working directory.
1445 returns whether patch was applied with fuzz factor."""
1441 returns whether patch was applied with fuzz factor."""
1446 backend = workingbackend(ui, repo, similarity)
1442 backend = workingbackend(ui, repo, similarity)
1447 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1443 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1448
1444
1449 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1445 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1450 eolmode='strict'):
1446 eolmode='strict'):
1451 backend = repobackend(ui, repo, ctx, store)
1447 backend = repobackend(ui, repo, ctx, store)
1452 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1448 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1453
1449
1454 def makememctx(repo, parents, text, user, date, branch, files, store,
1450 def makememctx(repo, parents, text, user, date, branch, files, store,
1455 editor=None):
1451 editor=None):
1456 def getfilectx(repo, memctx, path):
1452 def getfilectx(repo, memctx, path):
1457 data, (islink, isexec), copied = store.getfile(path)
1453 data, (islink, isexec), copied = store.getfile(path)
1458 return context.memfilectx(path, data, islink=islink, isexec=isexec,
1454 return context.memfilectx(path, data, islink=islink, isexec=isexec,
1459 copied=copied)
1455 copied=copied)
1460 extra = {}
1456 extra = {}
1461 if branch:
1457 if branch:
1462 extra['branch'] = encoding.fromlocal(branch)
1458 extra['branch'] = encoding.fromlocal(branch)
1463 ctx = context.memctx(repo, parents, text, files, getfilectx, user,
1459 ctx = context.memctx(repo, parents, text, files, getfilectx, user,
1464 date, extra)
1460 date, extra)
1465 if editor:
1461 if editor:
1466 ctx._text = editor(repo, ctx, [])
1462 ctx._text = editor(repo, ctx, [])
1467 return ctx
1463 return ctx
1468
1464
1469 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1465 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1470 similarity=0):
1466 similarity=0):
1471 """Apply <patchname> to the working directory.
1467 """Apply <patchname> to the working directory.
1472
1468
1473 'eolmode' specifies how end of lines should be handled. It can be:
1469 'eolmode' specifies how end of lines should be handled. It can be:
1474 - 'strict': inputs are read in binary mode, EOLs are preserved
1470 - 'strict': inputs are read in binary mode, EOLs are preserved
1475 - 'crlf': EOLs are ignored when patching and reset to CRLF
1471 - 'crlf': EOLs are ignored when patching and reset to CRLF
1476 - 'lf': EOLs are ignored when patching and reset to LF
1472 - 'lf': EOLs are ignored when patching and reset to LF
1477 - None: get it from user settings, default to 'strict'
1473 - None: get it from user settings, default to 'strict'
1478 'eolmode' is ignored when using an external patcher program.
1474 'eolmode' is ignored when using an external patcher program.
1479
1475
1480 Returns whether patch was applied with fuzz factor.
1476 Returns whether patch was applied with fuzz factor.
1481 """
1477 """
1482 patcher = ui.config('ui', 'patch')
1478 patcher = ui.config('ui', 'patch')
1483 if files is None:
1479 if files is None:
1484 files = set()
1480 files = set()
1485 try:
1481 try:
1486 if patcher:
1482 if patcher:
1487 return _externalpatch(ui, repo, patcher, patchname, strip,
1483 return _externalpatch(ui, repo, patcher, patchname, strip,
1488 files, similarity)
1484 files, similarity)
1489 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1485 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1490 similarity)
1486 similarity)
1491 except PatchError, err:
1487 except PatchError, err:
1492 raise util.Abort(str(err))
1488 raise util.Abort(str(err))
1493
1489
1494 def changedfiles(ui, repo, patchpath, strip=1):
1490 def changedfiles(ui, repo, patchpath, strip=1):
1495 backend = fsbackend(ui, repo.root)
1491 backend = fsbackend(ui, repo.root)
1496 fp = open(patchpath, 'rb')
1492 fp = open(patchpath, 'rb')
1497 try:
1493 try:
1498 changed = set()
1494 changed = set()
1499 for state, values in iterhunks(fp):
1495 for state, values in iterhunks(fp):
1500 if state == 'file':
1496 if state == 'file':
1501 afile, bfile, first_hunk, gp = values
1497 afile, bfile, first_hunk, gp = values
1502 if gp:
1498 if gp:
1503 gp.path = pathstrip(gp.path, strip - 1)[1]
1499 gp.path = pathstrip(gp.path, strip - 1)[1]
1504 if gp.oldpath:
1500 if gp.oldpath:
1505 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1501 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1506 else:
1502 else:
1507 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1503 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1508 changed.add(gp.path)
1504 changed.add(gp.path)
1509 if gp.op == 'RENAME':
1505 if gp.op == 'RENAME':
1510 changed.add(gp.oldpath)
1506 changed.add(gp.oldpath)
1511 elif state not in ('hunk', 'git'):
1507 elif state not in ('hunk', 'git'):
1512 raise util.Abort(_('unsupported parser state: %s') % state)
1508 raise util.Abort(_('unsupported parser state: %s') % state)
1513 return changed
1509 return changed
1514 finally:
1510 finally:
1515 fp.close()
1511 fp.close()
1516
1512
1517 class GitDiffRequired(Exception):
1513 class GitDiffRequired(Exception):
1518 pass
1514 pass
1519
1515
1520 def diffopts(ui, opts=None, untrusted=False, section='diff'):
1516 def diffopts(ui, opts=None, untrusted=False, section='diff'):
1521 def get(key, name=None, getter=ui.configbool):
1517 def get(key, name=None, getter=ui.configbool):
1522 return ((opts and opts.get(key)) or
1518 return ((opts and opts.get(key)) or
1523 getter(section, name or key, None, untrusted=untrusted))
1519 getter(section, name or key, None, untrusted=untrusted))
1524 return mdiff.diffopts(
1520 return mdiff.diffopts(
1525 text=opts and opts.get('text'),
1521 text=opts and opts.get('text'),
1526 git=get('git'),
1522 git=get('git'),
1527 nodates=get('nodates'),
1523 nodates=get('nodates'),
1528 showfunc=get('show_function', 'showfunc'),
1524 showfunc=get('show_function', 'showfunc'),
1529 ignorews=get('ignore_all_space', 'ignorews'),
1525 ignorews=get('ignore_all_space', 'ignorews'),
1530 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1526 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1531 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1527 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1532 context=get('unified', getter=ui.config))
1528 context=get('unified', getter=ui.config))
1533
1529
1534 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1530 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1535 losedatafn=None, prefix=''):
1531 losedatafn=None, prefix=''):
1536 '''yields diff of changes to files between two nodes, or node and
1532 '''yields diff of changes to files between two nodes, or node and
1537 working directory.
1533 working directory.
1538
1534
1539 if node1 is None, use first dirstate parent instead.
1535 if node1 is None, use first dirstate parent instead.
1540 if node2 is None, compare node1 with working directory.
1536 if node2 is None, compare node1 with working directory.
1541
1537
1542 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1538 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1543 every time some change cannot be represented with the current
1539 every time some change cannot be represented with the current
1544 patch format. Return False to upgrade to git patch format, True to
1540 patch format. Return False to upgrade to git patch format, True to
1545 accept the loss or raise an exception to abort the diff. It is
1541 accept the loss or raise an exception to abort the diff. It is
1546 called with the name of current file being diffed as 'fn'. If set
1542 called with the name of current file being diffed as 'fn'. If set
1547 to None, patches will always be upgraded to git format when
1543 to None, patches will always be upgraded to git format when
1548 necessary.
1544 necessary.
1549
1545
1550 prefix is a filename prefix that is prepended to all filenames on
1546 prefix is a filename prefix that is prepended to all filenames on
1551 display (used for subrepos).
1547 display (used for subrepos).
1552 '''
1548 '''
1553
1549
1554 if opts is None:
1550 if opts is None:
1555 opts = mdiff.defaultopts
1551 opts = mdiff.defaultopts
1556
1552
1557 if not node1 and not node2:
1553 if not node1 and not node2:
1558 node1 = repo.dirstate.p1()
1554 node1 = repo.dirstate.p1()
1559
1555
1560 def lrugetfilectx():
1556 def lrugetfilectx():
1561 cache = {}
1557 cache = {}
1562 order = util.deque()
1558 order = util.deque()
1563 def getfilectx(f, ctx):
1559 def getfilectx(f, ctx):
1564 fctx = ctx.filectx(f, filelog=cache.get(f))
1560 fctx = ctx.filectx(f, filelog=cache.get(f))
1565 if f not in cache:
1561 if f not in cache:
1566 if len(cache) > 20:
1562 if len(cache) > 20:
1567 del cache[order.popleft()]
1563 del cache[order.popleft()]
1568 cache[f] = fctx.filelog()
1564 cache[f] = fctx.filelog()
1569 else:
1565 else:
1570 order.remove(f)
1566 order.remove(f)
1571 order.append(f)
1567 order.append(f)
1572 return fctx
1568 return fctx
1573 return getfilectx
1569 return getfilectx
1574 getfilectx = lrugetfilectx()
1570 getfilectx = lrugetfilectx()
1575
1571
1576 ctx1 = repo[node1]
1572 ctx1 = repo[node1]
1577 ctx2 = repo[node2]
1573 ctx2 = repo[node2]
1578
1574
1579 if not changes:
1575 if not changes:
1580 changes = repo.status(ctx1, ctx2, match=match)
1576 changes = repo.status(ctx1, ctx2, match=match)
1581 modified, added, removed = changes[:3]
1577 modified, added, removed = changes[:3]
1582
1578
1583 if not modified and not added and not removed:
1579 if not modified and not added and not removed:
1584 return []
1580 return []
1585
1581
1586 revs = None
1582 revs = None
1587 hexfunc = repo.ui.debugflag and hex or short
1583 hexfunc = repo.ui.debugflag and hex or short
1588 revs = [hexfunc(node) for node in [node1, node2] if node]
1584 revs = [hexfunc(node) for node in [node1, node2] if node]
1589
1585
1590 copy = {}
1586 copy = {}
1591 if opts.git or opts.upgrade:
1587 if opts.git or opts.upgrade:
1592 copy = copies.pathcopies(ctx1, ctx2)
1588 copy = copies.pathcopies(ctx1, ctx2)
1593
1589
1594 def difffn(opts, losedata):
1590 def difffn(opts, losedata):
1595 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1591 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1596 copy, getfilectx, opts, losedata, prefix)
1592 copy, getfilectx, opts, losedata, prefix)
1597 if opts.upgrade and not opts.git:
1593 if opts.upgrade and not opts.git:
1598 try:
1594 try:
1599 def losedata(fn):
1595 def losedata(fn):
1600 if not losedatafn or not losedatafn(fn=fn):
1596 if not losedatafn or not losedatafn(fn=fn):
1601 raise GitDiffRequired
1597 raise GitDiffRequired
1602 # Buffer the whole output until we are sure it can be generated
1598 # Buffer the whole output until we are sure it can be generated
1603 return list(difffn(opts.copy(git=False), losedata))
1599 return list(difffn(opts.copy(git=False), losedata))
1604 except GitDiffRequired:
1600 except GitDiffRequired:
1605 return difffn(opts.copy(git=True), None)
1601 return difffn(opts.copy(git=True), None)
1606 else:
1602 else:
1607 return difffn(opts, None)
1603 return difffn(opts, None)
1608
1604
1609 def difflabel(func, *args, **kw):
1605 def difflabel(func, *args, **kw):
1610 '''yields 2-tuples of (output, label) based on the output of func()'''
1606 '''yields 2-tuples of (output, label) based on the output of func()'''
1611 headprefixes = [('diff', 'diff.diffline'),
1607 headprefixes = [('diff', 'diff.diffline'),
1612 ('copy', 'diff.extended'),
1608 ('copy', 'diff.extended'),
1613 ('rename', 'diff.extended'),
1609 ('rename', 'diff.extended'),
1614 ('old', 'diff.extended'),
1610 ('old', 'diff.extended'),
1615 ('new', 'diff.extended'),
1611 ('new', 'diff.extended'),
1616 ('deleted', 'diff.extended'),
1612 ('deleted', 'diff.extended'),
1617 ('---', 'diff.file_a'),
1613 ('---', 'diff.file_a'),
1618 ('+++', 'diff.file_b')]
1614 ('+++', 'diff.file_b')]
1619 textprefixes = [('@', 'diff.hunk'),
1615 textprefixes = [('@', 'diff.hunk'),
1620 ('-', 'diff.deleted'),
1616 ('-', 'diff.deleted'),
1621 ('+', 'diff.inserted')]
1617 ('+', 'diff.inserted')]
1622 head = False
1618 head = False
1623 for chunk in func(*args, **kw):
1619 for chunk in func(*args, **kw):
1624 lines = chunk.split('\n')
1620 lines = chunk.split('\n')
1625 for i, line in enumerate(lines):
1621 for i, line in enumerate(lines):
1626 if i != 0:
1622 if i != 0:
1627 yield ('\n', '')
1623 yield ('\n', '')
1628 if head:
1624 if head:
1629 if line.startswith('@'):
1625 if line.startswith('@'):
1630 head = False
1626 head = False
1631 else:
1627 else:
1632 if line and line[0] not in ' +-@\\':
1628 if line and line[0] not in ' +-@\\':
1633 head = True
1629 head = True
1634 stripline = line
1630 stripline = line
1635 if not head and line and line[0] in '+-':
1631 if not head and line and line[0] in '+-':
1636 # highlight trailing whitespace, but only in changed lines
1632 # highlight trailing whitespace, but only in changed lines
1637 stripline = line.rstrip()
1633 stripline = line.rstrip()
1638 prefixes = textprefixes
1634 prefixes = textprefixes
1639 if head:
1635 if head:
1640 prefixes = headprefixes
1636 prefixes = headprefixes
1641 for prefix, label in prefixes:
1637 for prefix, label in prefixes:
1642 if stripline.startswith(prefix):
1638 if stripline.startswith(prefix):
1643 yield (stripline, label)
1639 yield (stripline, label)
1644 break
1640 break
1645 else:
1641 else:
1646 yield (line, '')
1642 yield (line, '')
1647 if line != stripline:
1643 if line != stripline:
1648 yield (line[len(stripline):], 'diff.trailingwhitespace')
1644 yield (line[len(stripline):], 'diff.trailingwhitespace')
1649
1645
1650 def diffui(*args, **kw):
1646 def diffui(*args, **kw):
1651 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1647 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1652 return difflabel(diff, *args, **kw)
1648 return difflabel(diff, *args, **kw)
1653
1649
1654 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1650 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1655 copy, getfilectx, opts, losedatafn, prefix):
1651 copy, getfilectx, opts, losedatafn, prefix):
1656
1652
1657 def join(f):
1653 def join(f):
1658 return posixpath.join(prefix, f)
1654 return posixpath.join(prefix, f)
1659
1655
1660 def addmodehdr(header, omode, nmode):
1656 def addmodehdr(header, omode, nmode):
1661 if omode != nmode:
1657 if omode != nmode:
1662 header.append('old mode %s\n' % omode)
1658 header.append('old mode %s\n' % omode)
1663 header.append('new mode %s\n' % nmode)
1659 header.append('new mode %s\n' % nmode)
1664
1660
1665 def addindexmeta(meta, revs):
1661 def addindexmeta(meta, revs):
1666 if opts.git:
1662 if opts.git:
1667 i = len(revs)
1663 i = len(revs)
1668 if i==2:
1664 if i==2:
1669 meta.append('index %s..%s\n' % tuple(revs))
1665 meta.append('index %s..%s\n' % tuple(revs))
1670 elif i==3:
1666 elif i==3:
1671 meta.append('index %s,%s..%s\n' % tuple(revs))
1667 meta.append('index %s,%s..%s\n' % tuple(revs))
1672
1668
1673 def gitindex(text):
1669 def gitindex(text):
1674 if not text:
1670 if not text:
1675 return hex(nullid)
1671 return hex(nullid)
1676 l = len(text)
1672 l = len(text)
1677 s = util.sha1('blob %d\0' % l)
1673 s = util.sha1('blob %d\0' % l)
1678 s.update(text)
1674 s.update(text)
1679 return s.hexdigest()
1675 return s.hexdigest()
1680
1676
1681 def diffline(a, b, revs):
1677 def diffline(a, b, revs):
1682 if opts.git:
1678 if opts.git:
1683 line = 'diff --git a/%s b/%s\n' % (a, b)
1679 line = 'diff --git a/%s b/%s\n' % (a, b)
1684 elif not repo.ui.quiet:
1680 elif not repo.ui.quiet:
1685 if revs:
1681 if revs:
1686 revinfo = ' '.join(["-r %s" % rev for rev in revs])
1682 revinfo = ' '.join(["-r %s" % rev for rev in revs])
1687 line = 'diff %s %s\n' % (revinfo, a)
1683 line = 'diff %s %s\n' % (revinfo, a)
1688 else:
1684 else:
1689 line = 'diff %s\n' % a
1685 line = 'diff %s\n' % a
1690 else:
1686 else:
1691 line = ''
1687 line = ''
1692 return line
1688 return line
1693
1689
1694 date1 = util.datestr(ctx1.date())
1690 date1 = util.datestr(ctx1.date())
1695 man1 = ctx1.manifest()
1691 man1 = ctx1.manifest()
1696
1692
1697 gone = set()
1693 gone = set()
1698 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1694 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1699
1695
1700 copyto = dict([(v, k) for k, v in copy.items()])
1696 copyto = dict([(v, k) for k, v in copy.items()])
1701
1697
1702 if opts.git:
1698 if opts.git:
1703 revs = None
1699 revs = None
1704
1700
1705 for f in sorted(modified + added + removed):
1701 for f in sorted(modified + added + removed):
1706 to = None
1702 to = None
1707 tn = None
1703 tn = None
1708 dodiff = True
1704 dodiff = True
1709 header = []
1705 header = []
1710 if f in man1:
1706 if f in man1:
1711 to = getfilectx(f, ctx1).data()
1707 to = getfilectx(f, ctx1).data()
1712 if f not in removed:
1708 if f not in removed:
1713 tn = getfilectx(f, ctx2).data()
1709 tn = getfilectx(f, ctx2).data()
1714 a, b = f, f
1710 a, b = f, f
1715 if opts.git or losedatafn:
1711 if opts.git or losedatafn:
1716 if f in added:
1712 if f in added:
1717 mode = gitmode[ctx2.flags(f)]
1713 mode = gitmode[ctx2.flags(f)]
1718 if f in copy or f in copyto:
1714 if f in copy or f in copyto:
1719 if opts.git:
1715 if opts.git:
1720 if f in copy:
1716 if f in copy:
1721 a = copy[f]
1717 a = copy[f]
1722 else:
1718 else:
1723 a = copyto[f]
1719 a = copyto[f]
1724 omode = gitmode[man1.flags(a)]
1720 omode = gitmode[man1.flags(a)]
1725 addmodehdr(header, omode, mode)
1721 addmodehdr(header, omode, mode)
1726 if a in removed and a not in gone:
1722 if a in removed and a not in gone:
1727 op = 'rename'
1723 op = 'rename'
1728 gone.add(a)
1724 gone.add(a)
1729 else:
1725 else:
1730 op = 'copy'
1726 op = 'copy'
1731 header.append('%s from %s\n' % (op, join(a)))
1727 header.append('%s from %s\n' % (op, join(a)))
1732 header.append('%s to %s\n' % (op, join(f)))
1728 header.append('%s to %s\n' % (op, join(f)))
1733 to = getfilectx(a, ctx1).data()
1729 to = getfilectx(a, ctx1).data()
1734 else:
1730 else:
1735 losedatafn(f)
1731 losedatafn(f)
1736 else:
1732 else:
1737 if opts.git:
1733 if opts.git:
1738 header.append('new file mode %s\n' % mode)
1734 header.append('new file mode %s\n' % mode)
1739 elif ctx2.flags(f):
1735 elif ctx2.flags(f):
1740 losedatafn(f)
1736 losedatafn(f)
1741 # In theory, if tn was copied or renamed we should check
1737 # In theory, if tn was copied or renamed we should check
1742 # if the source is binary too but the copy record already
1738 # if the source is binary too but the copy record already
1743 # forces git mode.
1739 # forces git mode.
1744 if util.binary(tn):
1740 if util.binary(tn):
1745 if opts.git:
1741 if opts.git:
1746 dodiff = 'binary'
1742 dodiff = 'binary'
1747 else:
1743 else:
1748 losedatafn(f)
1744 losedatafn(f)
1749 if not opts.git and not tn:
1745 if not opts.git and not tn:
1750 # regular diffs cannot represent new empty file
1746 # regular diffs cannot represent new empty file
1751 losedatafn(f)
1747 losedatafn(f)
1752 elif f in removed:
1748 elif f in removed:
1753 if opts.git:
1749 if opts.git:
1754 # have we already reported a copy above?
1750 # have we already reported a copy above?
1755 if ((f in copy and copy[f] in added
1751 if ((f in copy and copy[f] in added
1756 and copyto[copy[f]] == f) or
1752 and copyto[copy[f]] == f) or
1757 (f in copyto and copyto[f] in added
1753 (f in copyto and copyto[f] in added
1758 and copy[copyto[f]] == f)):
1754 and copy[copyto[f]] == f)):
1759 dodiff = False
1755 dodiff = False
1760 else:
1756 else:
1761 header.append('deleted file mode %s\n' %
1757 header.append('deleted file mode %s\n' %
1762 gitmode[man1.flags(f)])
1758 gitmode[man1.flags(f)])
1763 elif not to or util.binary(to):
1759 elif not to or util.binary(to):
1764 # regular diffs cannot represent empty file deletion
1760 # regular diffs cannot represent empty file deletion
1765 losedatafn(f)
1761 losedatafn(f)
1766 else:
1762 else:
1767 oflag = man1.flags(f)
1763 oflag = man1.flags(f)
1768 nflag = ctx2.flags(f)
1764 nflag = ctx2.flags(f)
1769 binary = util.binary(to) or util.binary(tn)
1765 binary = util.binary(to) or util.binary(tn)
1770 if opts.git:
1766 if opts.git:
1771 addmodehdr(header, gitmode[oflag], gitmode[nflag])
1767 addmodehdr(header, gitmode[oflag], gitmode[nflag])
1772 if binary:
1768 if binary:
1773 dodiff = 'binary'
1769 dodiff = 'binary'
1774 elif binary or nflag != oflag:
1770 elif binary or nflag != oflag:
1775 losedatafn(f)
1771 losedatafn(f)
1776
1772
1777 if dodiff:
1773 if dodiff:
1778 if opts.git or revs:
1774 if opts.git or revs:
1779 header.insert(0, diffline(join(a), join(b), revs))
1775 header.insert(0, diffline(join(a), join(b), revs))
1780 if dodiff == 'binary':
1776 if dodiff == 'binary':
1781 text = mdiff.b85diff(to, tn)
1777 text = mdiff.b85diff(to, tn)
1782 if text:
1778 if text:
1783 addindexmeta(header, [gitindex(to), gitindex(tn)])
1779 addindexmeta(header, [gitindex(to), gitindex(tn)])
1784 else:
1780 else:
1785 text = mdiff.unidiff(to, date1,
1781 text = mdiff.unidiff(to, date1,
1786 # ctx2 date may be dynamic
1782 # ctx2 date may be dynamic
1787 tn, util.datestr(ctx2.date()),
1783 tn, util.datestr(ctx2.date()),
1788 join(a), join(b), opts=opts)
1784 join(a), join(b), opts=opts)
1789 if header and (text or len(header) > 1):
1785 if header and (text or len(header) > 1):
1790 yield ''.join(header)
1786 yield ''.join(header)
1791 if text:
1787 if text:
1792 yield text
1788 yield text
1793
1789
1794 def diffstatsum(stats):
1790 def diffstatsum(stats):
1795 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1791 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1796 for f, a, r, b in stats:
1792 for f, a, r, b in stats:
1797 maxfile = max(maxfile, encoding.colwidth(f))
1793 maxfile = max(maxfile, encoding.colwidth(f))
1798 maxtotal = max(maxtotal, a + r)
1794 maxtotal = max(maxtotal, a + r)
1799 addtotal += a
1795 addtotal += a
1800 removetotal += r
1796 removetotal += r
1801 binary = binary or b
1797 binary = binary or b
1802
1798
1803 return maxfile, maxtotal, addtotal, removetotal, binary
1799 return maxfile, maxtotal, addtotal, removetotal, binary
1804
1800
1805 def diffstatdata(lines):
1801 def diffstatdata(lines):
1806 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1802 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1807
1803
1808 results = []
1804 results = []
1809 filename, adds, removes, isbinary = None, 0, 0, False
1805 filename, adds, removes, isbinary = None, 0, 0, False
1810
1806
1811 def addresult():
1807 def addresult():
1812 if filename:
1808 if filename:
1813 results.append((filename, adds, removes, isbinary))
1809 results.append((filename, adds, removes, isbinary))
1814
1810
1815 for line in lines:
1811 for line in lines:
1816 if line.startswith('diff'):
1812 if line.startswith('diff'):
1817 addresult()
1813 addresult()
1818 # set numbers to 0 anyway when starting new file
1814 # set numbers to 0 anyway when starting new file
1819 adds, removes, isbinary = 0, 0, False
1815 adds, removes, isbinary = 0, 0, False
1820 if line.startswith('diff --git'):
1816 if line.startswith('diff --git'):
1821 filename = gitre.search(line).group(1)
1817 filename = gitre.search(line).group(1)
1822 elif line.startswith('diff -r'):
1818 elif line.startswith('diff -r'):
1823 # format: "diff -r ... -r ... filename"
1819 # format: "diff -r ... -r ... filename"
1824 filename = diffre.search(line).group(1)
1820 filename = diffre.search(line).group(1)
1825 elif line.startswith('+') and not line.startswith('+++ '):
1821 elif line.startswith('+') and not line.startswith('+++ '):
1826 adds += 1
1822 adds += 1
1827 elif line.startswith('-') and not line.startswith('--- '):
1823 elif line.startswith('-') and not line.startswith('--- '):
1828 removes += 1
1824 removes += 1
1829 elif (line.startswith('GIT binary patch') or
1825 elif (line.startswith('GIT binary patch') or
1830 line.startswith('Binary file')):
1826 line.startswith('Binary file')):
1831 isbinary = True
1827 isbinary = True
1832 addresult()
1828 addresult()
1833 return results
1829 return results
1834
1830
1835 def diffstat(lines, width=80, git=False):
1831 def diffstat(lines, width=80, git=False):
1836 output = []
1832 output = []
1837 stats = diffstatdata(lines)
1833 stats = diffstatdata(lines)
1838 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1834 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1839
1835
1840 countwidth = len(str(maxtotal))
1836 countwidth = len(str(maxtotal))
1841 if hasbinary and countwidth < 3:
1837 if hasbinary and countwidth < 3:
1842 countwidth = 3
1838 countwidth = 3
1843 graphwidth = width - countwidth - maxname - 6
1839 graphwidth = width - countwidth - maxname - 6
1844 if graphwidth < 10:
1840 if graphwidth < 10:
1845 graphwidth = 10
1841 graphwidth = 10
1846
1842
1847 def scale(i):
1843 def scale(i):
1848 if maxtotal <= graphwidth:
1844 if maxtotal <= graphwidth:
1849 return i
1845 return i
1850 # If diffstat runs out of room it doesn't print anything,
1846 # If diffstat runs out of room it doesn't print anything,
1851 # which isn't very useful, so always print at least one + or -
1847 # which isn't very useful, so always print at least one + or -
1852 # if there were at least some changes.
1848 # if there were at least some changes.
1853 return max(i * graphwidth // maxtotal, int(bool(i)))
1849 return max(i * graphwidth // maxtotal, int(bool(i)))
1854
1850
1855 for filename, adds, removes, isbinary in stats:
1851 for filename, adds, removes, isbinary in stats:
1856 if isbinary:
1852 if isbinary:
1857 count = 'Bin'
1853 count = 'Bin'
1858 else:
1854 else:
1859 count = adds + removes
1855 count = adds + removes
1860 pluses = '+' * scale(adds)
1856 pluses = '+' * scale(adds)
1861 minuses = '-' * scale(removes)
1857 minuses = '-' * scale(removes)
1862 output.append(' %s%s | %*s %s%s\n' %
1858 output.append(' %s%s | %*s %s%s\n' %
1863 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1859 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1864 countwidth, count, pluses, minuses))
1860 countwidth, count, pluses, minuses))
1865
1861
1866 if stats:
1862 if stats:
1867 output.append(_(' %d files changed, %d insertions(+), '
1863 output.append(_(' %d files changed, %d insertions(+), '
1868 '%d deletions(-)\n')
1864 '%d deletions(-)\n')
1869 % (len(stats), totaladds, totalremoves))
1865 % (len(stats), totaladds, totalremoves))
1870
1866
1871 return ''.join(output)
1867 return ''.join(output)
1872
1868
1873 def diffstatui(*args, **kw):
1869 def diffstatui(*args, **kw):
1874 '''like diffstat(), but yields 2-tuples of (output, label) for
1870 '''like diffstat(), but yields 2-tuples of (output, label) for
1875 ui.write()
1871 ui.write()
1876 '''
1872 '''
1877
1873
1878 for line in diffstat(*args, **kw).splitlines():
1874 for line in diffstat(*args, **kw).splitlines():
1879 if line and line[-1] in '+-':
1875 if line and line[-1] in '+-':
1880 name, graph = line.rsplit(' ', 1)
1876 name, graph = line.rsplit(' ', 1)
1881 yield (name + ' ', '')
1877 yield (name + ' ', '')
1882 m = re.search(r'\++', graph)
1878 m = re.search(r'\++', graph)
1883 if m:
1879 if m:
1884 yield (m.group(0), 'diffstat.inserted')
1880 yield (m.group(0), 'diffstat.inserted')
1885 m = re.search(r'-+', graph)
1881 m = re.search(r'-+', graph)
1886 if m:
1882 if m:
1887 yield (m.group(0), 'diffstat.deleted')
1883 yield (m.group(0), 'diffstat.deleted')
1888 else:
1884 else:
1889 yield (line, '')
1885 yield (line, '')
1890 yield ('\n', '')
1886 yield ('\n', '')
@@ -1,525 +1,529 b''
1 # posix.py - Posix utility function implementations for Mercurial
1 # posix.py - Posix utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-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 encoding
9 import encoding
10 import os, sys, errno, stat, getpass, pwd, grp, socket, tempfile, unicodedata
10 import os, sys, errno, stat, getpass, pwd, grp, socket, tempfile, unicodedata
11
11
12 posixfile = open
12 posixfile = open
13 normpath = os.path.normpath
13 normpath = os.path.normpath
14 samestat = os.path.samestat
14 samestat = os.path.samestat
15 oslink = os.link
15 oslink = os.link
16 unlink = os.unlink
16 unlink = os.unlink
17 rename = os.rename
17 rename = os.rename
18 expandglobs = False
18 expandglobs = False
19
19
20 umask = os.umask(0)
20 umask = os.umask(0)
21 os.umask(umask)
21 os.umask(umask)
22
22
23 def split(p):
23 def split(p):
24 '''Same as os.path.split, but faster'''
24 '''Same as os.path.split, but faster'''
25 ht = p.rsplit('/', 1)
25 ht = p.rsplit('/', 1)
26 if len(ht) == 1:
26 if len(ht) == 1:
27 return '', p
27 return '', p
28 nh = ht[0].rstrip('/')
28 nh = ht[0].rstrip('/')
29 if nh:
29 if nh:
30 return nh, ht[1]
30 return nh, ht[1]
31 return ht
31 return ht
32
32
33 def openhardlinks():
33 def openhardlinks():
34 '''return true if it is safe to hold open file handles to hardlinks'''
34 '''return true if it is safe to hold open file handles to hardlinks'''
35 return True
35 return True
36
36
37 def nlinks(name):
37 def nlinks(name):
38 '''return number of hardlinks for the given file'''
38 '''return number of hardlinks for the given file'''
39 return os.lstat(name).st_nlink
39 return os.lstat(name).st_nlink
40
40
41 def parsepatchoutput(output_line):
41 def parsepatchoutput(output_line):
42 """parses the output produced by patch and returns the filename"""
42 """parses the output produced by patch and returns the filename"""
43 pf = output_line[14:]
43 pf = output_line[14:]
44 if os.sys.platform == 'OpenVMS':
44 if os.sys.platform == 'OpenVMS':
45 if pf[0] == '`':
45 if pf[0] == '`':
46 pf = pf[1:-1] # Remove the quotes
46 pf = pf[1:-1] # Remove the quotes
47 else:
47 else:
48 if pf.startswith("'") and pf.endswith("'") and " " in pf:
48 if pf.startswith("'") and pf.endswith("'") and " " in pf:
49 pf = pf[1:-1] # Remove the quotes
49 pf = pf[1:-1] # Remove the quotes
50 return pf
50 return pf
51
51
52 def sshargs(sshcmd, host, user, port):
52 def sshargs(sshcmd, host, user, port):
53 '''Build argument list for ssh'''
53 '''Build argument list for ssh'''
54 args = user and ("%s@%s" % (user, host)) or host
54 args = user and ("%s@%s" % (user, host)) or host
55 return port and ("%s -p %s" % (args, port)) or args
55 return port and ("%s -p %s" % (args, port)) or args
56
56
57 def isexec(f):
57 def isexec(f):
58 """check whether a file is executable"""
58 """check whether a file is executable"""
59 return (os.lstat(f).st_mode & 0100 != 0)
59 return (os.lstat(f).st_mode & 0100 != 0)
60
60
61 def setflags(f, l, x):
61 def setflags(f, l, x):
62 s = os.lstat(f).st_mode
62 s = os.lstat(f).st_mode
63 if l:
63 if l:
64 if not stat.S_ISLNK(s):
64 if not stat.S_ISLNK(s):
65 # switch file to link
65 # switch file to link
66 fp = open(f)
66 fp = open(f)
67 data = fp.read()
67 data = fp.read()
68 fp.close()
68 fp.close()
69 os.unlink(f)
69 os.unlink(f)
70 try:
70 try:
71 os.symlink(data, f)
71 os.symlink(data, f)
72 except OSError:
72 except OSError:
73 # failed to make a link, rewrite file
73 # failed to make a link, rewrite file
74 fp = open(f, "w")
74 fp = open(f, "w")
75 fp.write(data)
75 fp.write(data)
76 fp.close()
76 fp.close()
77 # no chmod needed at this point
77 # no chmod needed at this point
78 return
78 return
79 if stat.S_ISLNK(s):
79 if stat.S_ISLNK(s):
80 # switch link to file
80 # switch link to file
81 data = os.readlink(f)
81 data = os.readlink(f)
82 os.unlink(f)
82 os.unlink(f)
83 fp = open(f, "w")
83 fp = open(f, "w")
84 fp.write(data)
84 fp.write(data)
85 fp.close()
85 fp.close()
86 s = 0666 & ~umask # avoid restatting for chmod
86 s = 0666 & ~umask # avoid restatting for chmod
87
87
88 sx = s & 0100
88 sx = s & 0100
89 if x and not sx:
89 if x and not sx:
90 # Turn on +x for every +r bit when making a file executable
90 # Turn on +x for every +r bit when making a file executable
91 # and obey umask.
91 # and obey umask.
92 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
92 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
93 elif not x and sx:
93 elif not x and sx:
94 # Turn off all +x bits
94 # Turn off all +x bits
95 os.chmod(f, s & 0666)
95 os.chmod(f, s & 0666)
96
96
97 def copymode(src, dst, mode=None):
97 def copymode(src, dst, mode=None):
98 '''Copy the file mode from the file at path src to dst.
98 '''Copy the file mode from the file at path src to dst.
99 If src doesn't exist, we're using mode instead. If mode is None, we're
99 If src doesn't exist, we're using mode instead. If mode is None, we're
100 using umask.'''
100 using umask.'''
101 try:
101 try:
102 st_mode = os.lstat(src).st_mode & 0777
102 st_mode = os.lstat(src).st_mode & 0777
103 except OSError, inst:
103 except OSError, inst:
104 if inst.errno != errno.ENOENT:
104 if inst.errno != errno.ENOENT:
105 raise
105 raise
106 st_mode = mode
106 st_mode = mode
107 if st_mode is None:
107 if st_mode is None:
108 st_mode = ~umask
108 st_mode = ~umask
109 st_mode &= 0666
109 st_mode &= 0666
110 os.chmod(dst, st_mode)
110 os.chmod(dst, st_mode)
111
111
112 def checkexec(path):
112 def checkexec(path):
113 """
113 """
114 Check whether the given path is on a filesystem with UNIX-like exec flags
114 Check whether the given path is on a filesystem with UNIX-like exec flags
115
115
116 Requires a directory (like /foo/.hg)
116 Requires a directory (like /foo/.hg)
117 """
117 """
118
118
119 # VFAT on some Linux versions can flip mode but it doesn't persist
119 # VFAT on some Linux versions can flip mode but it doesn't persist
120 # a FS remount. Frequently we can detect it if files are created
120 # a FS remount. Frequently we can detect it if files are created
121 # with exec bit on.
121 # with exec bit on.
122
122
123 try:
123 try:
124 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
124 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
125 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
125 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
126 try:
126 try:
127 os.close(fh)
127 os.close(fh)
128 m = os.stat(fn).st_mode & 0777
128 m = os.stat(fn).st_mode & 0777
129 new_file_has_exec = m & EXECFLAGS
129 new_file_has_exec = m & EXECFLAGS
130 os.chmod(fn, m ^ EXECFLAGS)
130 os.chmod(fn, m ^ EXECFLAGS)
131 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
131 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
132 finally:
132 finally:
133 os.unlink(fn)
133 os.unlink(fn)
134 except (IOError, OSError):
134 except (IOError, OSError):
135 # we don't care, the user probably won't be able to commit anyway
135 # we don't care, the user probably won't be able to commit anyway
136 return False
136 return False
137 return not (new_file_has_exec or exec_flags_cannot_flip)
137 return not (new_file_has_exec or exec_flags_cannot_flip)
138
138
139 def checklink(path):
139 def checklink(path):
140 """check whether the given path is on a symlink-capable filesystem"""
140 """check whether the given path is on a symlink-capable filesystem"""
141 # mktemp is not racy because symlink creation will fail if the
141 # mktemp is not racy because symlink creation will fail if the
142 # file already exists
142 # file already exists
143 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
143 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
144 try:
144 try:
145 os.symlink(".", name)
145 os.symlink(".", name)
146 os.unlink(name)
146 os.unlink(name)
147 return True
147 return True
148 except (OSError, AttributeError):
148 except (OSError, AttributeError):
149 return False
149 return False
150
150
151 def checkosfilename(path):
151 def checkosfilename(path):
152 '''Check that the base-relative path is a valid filename on this platform.
152 '''Check that the base-relative path is a valid filename on this platform.
153 Returns None if the path is ok, or a UI string describing the problem.'''
153 Returns None if the path is ok, or a UI string describing the problem.'''
154 pass # on posix platforms, every path is ok
154 pass # on posix platforms, every path is ok
155
155
156 def setbinary(fd):
156 def setbinary(fd):
157 pass
157 pass
158
158
159 def pconvert(path):
159 def pconvert(path):
160 return path
160 return path
161
161
162 def localpath(path):
162 def localpath(path):
163 return path
163 return path
164
164
165 def samefile(fpath1, fpath2):
165 def samefile(fpath1, fpath2):
166 """Returns whether path1 and path2 refer to the same file. This is only
166 """Returns whether path1 and path2 refer to the same file. This is only
167 guaranteed to work for files, not directories."""
167 guaranteed to work for files, not directories."""
168 return os.path.samefile(fpath1, fpath2)
168 return os.path.samefile(fpath1, fpath2)
169
169
170 def samedevice(fpath1, fpath2):
170 def samedevice(fpath1, fpath2):
171 """Returns whether fpath1 and fpath2 are on the same device. This is only
171 """Returns whether fpath1 and fpath2 are on the same device. This is only
172 guaranteed to work for files, not directories."""
172 guaranteed to work for files, not directories."""
173 st1 = os.lstat(fpath1)
173 st1 = os.lstat(fpath1)
174 st2 = os.lstat(fpath2)
174 st2 = os.lstat(fpath2)
175 return st1.st_dev == st2.st_dev
175 return st1.st_dev == st2.st_dev
176
176
177 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
177 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
178 def normcase(path):
178 def normcase(path):
179 return path.lower()
179 return path.lower()
180
180
181 if sys.platform == 'darwin':
181 if sys.platform == 'darwin':
182 import fcntl # only needed on darwin, missing on jython
182 import fcntl # only needed on darwin, missing on jython
183
183
184 def normcase(path):
184 def normcase(path):
185 try:
185 try:
186 u = path.decode('utf-8')
186 u = path.decode('utf-8')
187 except UnicodeDecodeError:
187 except UnicodeDecodeError:
188 # percent-encode any characters that don't round-trip
188 # percent-encode any characters that don't round-trip
189 p2 = path.decode('utf-8', 'ignore').encode('utf-8')
189 p2 = path.decode('utf-8', 'ignore').encode('utf-8')
190 s = ""
190 s = ""
191 pos = 0
191 pos = 0
192 for c in path:
192 for c in path:
193 if p2[pos:pos + 1] == c:
193 if p2[pos:pos + 1] == c:
194 s += c
194 s += c
195 pos += 1
195 pos += 1
196 else:
196 else:
197 s += "%%%02X" % ord(c)
197 s += "%%%02X" % ord(c)
198 u = s.decode('utf-8')
198 u = s.decode('utf-8')
199
199
200 # Decompose then lowercase (HFS+ technote specifies lower)
200 # Decompose then lowercase (HFS+ technote specifies lower)
201 return unicodedata.normalize('NFD', u).lower().encode('utf-8')
201 return unicodedata.normalize('NFD', u).lower().encode('utf-8')
202
202
203 def realpath(path):
203 def realpath(path):
204 '''
204 '''
205 Returns the true, canonical file system path equivalent to the given
205 Returns the true, canonical file system path equivalent to the given
206 path.
206 path.
207
207
208 Equivalent means, in this case, resulting in the same, unique
208 Equivalent means, in this case, resulting in the same, unique
209 file system link to the path. Every file system entry, whether a file,
209 file system link to the path. Every file system entry, whether a file,
210 directory, hard link or symbolic link or special, will have a single
210 directory, hard link or symbolic link or special, will have a single
211 path preferred by the system, but may allow multiple, differing path
211 path preferred by the system, but may allow multiple, differing path
212 lookups to point to it.
212 lookups to point to it.
213
213
214 Most regular UNIX file systems only allow a file system entry to be
214 Most regular UNIX file systems only allow a file system entry to be
215 looked up by its distinct path. Obviously, this does not apply to case
215 looked up by its distinct path. Obviously, this does not apply to case
216 insensitive file systems, whether case preserving or not. The most
216 insensitive file systems, whether case preserving or not. The most
217 complex issue to deal with is file systems transparently reencoding the
217 complex issue to deal with is file systems transparently reencoding the
218 path, such as the non-standard Unicode normalisation required for HFS+
218 path, such as the non-standard Unicode normalisation required for HFS+
219 and HFSX.
219 and HFSX.
220 '''
220 '''
221 # Constants copied from /usr/include/sys/fcntl.h
221 # Constants copied from /usr/include/sys/fcntl.h
222 F_GETPATH = 50
222 F_GETPATH = 50
223 O_SYMLINK = 0x200000
223 O_SYMLINK = 0x200000
224
224
225 try:
225 try:
226 fd = os.open(path, O_SYMLINK)
226 fd = os.open(path, O_SYMLINK)
227 except OSError, err:
227 except OSError, err:
228 if err.errno == errno.ENOENT:
228 if err.errno == errno.ENOENT:
229 return path
229 return path
230 raise
230 raise
231
231
232 try:
232 try:
233 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
233 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
234 finally:
234 finally:
235 os.close(fd)
235 os.close(fd)
236 elif sys.version_info < (2, 4, 2, 'final'):
236 elif sys.version_info < (2, 4, 2, 'final'):
237 # Workaround for http://bugs.python.org/issue1213894 (os.path.realpath
237 # Workaround for http://bugs.python.org/issue1213894 (os.path.realpath
238 # didn't resolve symlinks that were the first component of the path.)
238 # didn't resolve symlinks that were the first component of the path.)
239 def realpath(path):
239 def realpath(path):
240 if os.path.isabs(path):
240 if os.path.isabs(path):
241 return os.path.realpath(path)
241 return os.path.realpath(path)
242 else:
242 else:
243 return os.path.realpath('./' + path)
243 return os.path.realpath('./' + path)
244 else:
244 else:
245 # Fallback to the likely inadequate Python builtin function.
245 # Fallback to the likely inadequate Python builtin function.
246 realpath = os.path.realpath
246 realpath = os.path.realpath
247
247
248 if sys.platform == 'cygwin':
248 if sys.platform == 'cygwin':
249 # workaround for cygwin, in which mount point part of path is
249 # workaround for cygwin, in which mount point part of path is
250 # treated as case sensitive, even though underlying NTFS is case
250 # treated as case sensitive, even though underlying NTFS is case
251 # insensitive.
251 # insensitive.
252
252
253 # default mount points
253 # default mount points
254 cygwinmountpoints = sorted([
254 cygwinmountpoints = sorted([
255 "/usr/bin",
255 "/usr/bin",
256 "/usr/lib",
256 "/usr/lib",
257 "/cygdrive",
257 "/cygdrive",
258 ], reverse=True)
258 ], reverse=True)
259
259
260 # use upper-ing as normcase as same as NTFS workaround
260 # use upper-ing as normcase as same as NTFS workaround
261 def normcase(path):
261 def normcase(path):
262 pathlen = len(path)
262 pathlen = len(path)
263 if (pathlen == 0) or (path[0] != os.sep):
263 if (pathlen == 0) or (path[0] != os.sep):
264 # treat as relative
264 # treat as relative
265 return encoding.upper(path)
265 return encoding.upper(path)
266
266
267 # to preserve case of mountpoint part
267 # to preserve case of mountpoint part
268 for mp in cygwinmountpoints:
268 for mp in cygwinmountpoints:
269 if not path.startswith(mp):
269 if not path.startswith(mp):
270 continue
270 continue
271
271
272 mplen = len(mp)
272 mplen = len(mp)
273 if mplen == pathlen: # mount point itself
273 if mplen == pathlen: # mount point itself
274 return mp
274 return mp
275 if path[mplen] == os.sep:
275 if path[mplen] == os.sep:
276 return mp + encoding.upper(path[mplen:])
276 return mp + encoding.upper(path[mplen:])
277
277
278 return encoding.upper(path)
278 return encoding.upper(path)
279
279
280 # Cygwin translates native ACLs to POSIX permissions,
280 # Cygwin translates native ACLs to POSIX permissions,
281 # but these translations are not supported by native
281 # but these translations are not supported by native
282 # tools, so the exec bit tends to be set erroneously.
282 # tools, so the exec bit tends to be set erroneously.
283 # Therefore, disable executable bit access on Cygwin.
283 # Therefore, disable executable bit access on Cygwin.
284 def checkexec(path):
284 def checkexec(path):
285 return False
285 return False
286
286
287 # Similarly, Cygwin's symlink emulation is likely to create
287 # Similarly, Cygwin's symlink emulation is likely to create
288 # problems when Mercurial is used from both Cygwin and native
288 # problems when Mercurial is used from both Cygwin and native
289 # Windows, with other native tools, or on shared volumes
289 # Windows, with other native tools, or on shared volumes
290 def checklink(path):
290 def checklink(path):
291 return False
291 return False
292
292
293 def shellquote(s):
293 def shellquote(s):
294 if os.sys.platform == 'OpenVMS':
294 if os.sys.platform == 'OpenVMS':
295 return '"%s"' % s
295 return '"%s"' % s
296 else:
296 else:
297 return "'%s'" % s.replace("'", "'\\''")
297 return "'%s'" % s.replace("'", "'\\''")
298
298
299 def quotecommand(cmd):
299 def quotecommand(cmd):
300 return cmd
300 return cmd
301
301
302 def popen(command, mode='r'):
302 def popen(command, mode='r'):
303 return os.popen(command, mode)
303 return os.popen(command, mode)
304
304
305 def testpid(pid):
305 def testpid(pid):
306 '''return False if pid dead, True if running or not sure'''
306 '''return False if pid dead, True if running or not sure'''
307 if os.sys.platform == 'OpenVMS':
307 if os.sys.platform == 'OpenVMS':
308 return True
308 return True
309 try:
309 try:
310 os.kill(pid, 0)
310 os.kill(pid, 0)
311 return True
311 return True
312 except OSError, inst:
312 except OSError, inst:
313 return inst.errno != errno.ESRCH
313 return inst.errno != errno.ESRCH
314
314
315 def explainexit(code):
315 def explainexit(code):
316 """return a 2-tuple (desc, code) describing a subprocess status
316 """return a 2-tuple (desc, code) describing a subprocess status
317 (codes from kill are negative - not os.system/wait encoding)"""
317 (codes from kill are negative - not os.system/wait encoding)"""
318 if code >= 0:
318 if code >= 0:
319 return _("exited with status %d") % code, code
319 return _("exited with status %d") % code, code
320 return _("killed by signal %d") % -code, -code
320 return _("killed by signal %d") % -code, -code
321
321
322 def isowner(st):
322 def isowner(st):
323 """Return True if the stat object st is from the current user."""
323 """Return True if the stat object st is from the current user."""
324 return st.st_uid == os.getuid()
324 return st.st_uid == os.getuid()
325
325
326 def findexe(command):
326 def findexe(command):
327 '''Find executable for command searching like which does.
327 '''Find executable for command searching like which does.
328 If command is a basename then PATH is searched for command.
328 If command is a basename then PATH is searched for command.
329 PATH isn't searched if command is an absolute or relative path.
329 PATH isn't searched if command is an absolute or relative path.
330 If command isn't found None is returned.'''
330 If command isn't found None is returned.'''
331 if sys.platform == 'OpenVMS':
331 if sys.platform == 'OpenVMS':
332 return command
332 return command
333
333
334 def findexisting(executable):
334 def findexisting(executable):
335 'Will return executable if existing file'
335 'Will return executable if existing file'
336 if os.path.isfile(executable) and os.access(executable, os.X_OK):
336 if os.path.isfile(executable) and os.access(executable, os.X_OK):
337 return executable
337 return executable
338 return None
338 return None
339
339
340 if os.sep in command:
340 if os.sep in command:
341 return findexisting(command)
341 return findexisting(command)
342
342
343 if sys.platform == 'plan9':
343 if sys.platform == 'plan9':
344 return findexisting(os.path.join('/bin', command))
344 return findexisting(os.path.join('/bin', command))
345
345
346 for path in os.environ.get('PATH', '').split(os.pathsep):
346 for path in os.environ.get('PATH', '').split(os.pathsep):
347 executable = findexisting(os.path.join(path, command))
347 executable = findexisting(os.path.join(path, command))
348 if executable is not None:
348 if executable is not None:
349 return executable
349 return executable
350 return None
350 return None
351
351
352 def setsignalhandler():
352 def setsignalhandler():
353 pass
353 pass
354
354
355 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
355 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
356
356
357 def statfiles(files):
357 def statfiles(files):
358 '''Stat each file in files. Yield each stat, or None if a file does not
358 '''Stat each file in files. Yield each stat, or None if a file does not
359 exist or has a type we don't care about.'''
359 exist or has a type we don't care about.'''
360 lstat = os.lstat
360 lstat = os.lstat
361 getkind = stat.S_IFMT
361 getkind = stat.S_IFMT
362 for nf in files:
362 for nf in files:
363 try:
363 try:
364 st = lstat(nf)
364 st = lstat(nf)
365 if getkind(st.st_mode) not in _wantedkinds:
365 if getkind(st.st_mode) not in _wantedkinds:
366 st = None
366 st = None
367 except OSError, err:
367 except OSError, err:
368 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
368 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
369 raise
369 raise
370 st = None
370 st = None
371 yield st
371 yield st
372
372
373 def getuser():
373 def getuser():
374 '''return name of current user'''
374 '''return name of current user'''
375 return getpass.getuser()
375 return getpass.getuser()
376
376
377 def username(uid=None):
377 def username(uid=None):
378 """Return the name of the user with the given uid.
378 """Return the name of the user with the given uid.
379
379
380 If uid is None, return the name of the current user."""
380 If uid is None, return the name of the current user."""
381
381
382 if uid is None:
382 if uid is None:
383 uid = os.getuid()
383 uid = os.getuid()
384 try:
384 try:
385 return pwd.getpwuid(uid)[0]
385 return pwd.getpwuid(uid)[0]
386 except KeyError:
386 except KeyError:
387 return str(uid)
387 return str(uid)
388
388
389 def groupname(gid=None):
389 def groupname(gid=None):
390 """Return the name of the group with the given gid.
390 """Return the name of the group with the given gid.
391
391
392 If gid is None, return the name of the current group."""
392 If gid is None, return the name of the current group."""
393
393
394 if gid is None:
394 if gid is None:
395 gid = os.getgid()
395 gid = os.getgid()
396 try:
396 try:
397 return grp.getgrgid(gid)[0]
397 return grp.getgrgid(gid)[0]
398 except KeyError:
398 except KeyError:
399 return str(gid)
399 return str(gid)
400
400
401 def groupmembers(name):
401 def groupmembers(name):
402 """Return the list of members of the group with the given
402 """Return the list of members of the group with the given
403 name, KeyError if the group does not exist.
403 name, KeyError if the group does not exist.
404 """
404 """
405 return list(grp.getgrnam(name).gr_mem)
405 return list(grp.getgrnam(name).gr_mem)
406
406
407 def spawndetached(args):
407 def spawndetached(args):
408 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
408 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
409 args[0], args)
409 args[0], args)
410
410
411 def gethgcmd():
411 def gethgcmd():
412 return sys.argv[:1]
412 return sys.argv[:1]
413
413
414 def termwidth():
414 def termwidth():
415 try:
415 try:
416 import termios, array, fcntl
416 import termios, array, fcntl
417 for dev in (sys.stderr, sys.stdout, sys.stdin):
417 for dev in (sys.stderr, sys.stdout, sys.stdin):
418 try:
418 try:
419 try:
419 try:
420 fd = dev.fileno()
420 fd = dev.fileno()
421 except AttributeError:
421 except AttributeError:
422 continue
422 continue
423 if not os.isatty(fd):
423 if not os.isatty(fd):
424 continue
424 continue
425 try:
425 try:
426 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
426 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
427 width = array.array('h', arri)[1]
427 width = array.array('h', arri)[1]
428 if width > 0:
428 if width > 0:
429 return width
429 return width
430 except AttributeError:
430 except AttributeError:
431 pass
431 pass
432 except ValueError:
432 except ValueError:
433 pass
433 pass
434 except IOError, e:
434 except IOError, e:
435 if e[0] == errno.EINVAL:
435 if e[0] == errno.EINVAL:
436 pass
436 pass
437 else:
437 else:
438 raise
438 raise
439 except ImportError:
439 except ImportError:
440 pass
440 pass
441 return 80
441 return 80
442
442
443 def makedir(path, notindexed):
443 def makedir(path, notindexed):
444 os.mkdir(path)
444 os.mkdir(path)
445
445
446 def unlinkpath(f):
446 def unlinkpath(f, ignoremissing=False):
447 """unlink and remove the directory if it is empty"""
447 """unlink and remove the directory if it is empty"""
448 os.unlink(f)
448 try:
449 os.unlink(f)
450 except OSError, e:
451 if not (ignoremissing and e.errno == errno.ENOENT):
452 raise
449 # try removing directories that might now be empty
453 # try removing directories that might now be empty
450 try:
454 try:
451 os.removedirs(os.path.dirname(f))
455 os.removedirs(os.path.dirname(f))
452 except OSError:
456 except OSError:
453 pass
457 pass
454
458
455 def lookupreg(key, name=None, scope=None):
459 def lookupreg(key, name=None, scope=None):
456 return None
460 return None
457
461
458 def hidewindow():
462 def hidewindow():
459 """Hide current shell window.
463 """Hide current shell window.
460
464
461 Used to hide the window opened when starting asynchronous
465 Used to hide the window opened when starting asynchronous
462 child process under Windows, unneeded on other systems.
466 child process under Windows, unneeded on other systems.
463 """
467 """
464 pass
468 pass
465
469
466 class cachestat(object):
470 class cachestat(object):
467 def __init__(self, path):
471 def __init__(self, path):
468 self.stat = os.stat(path)
472 self.stat = os.stat(path)
469
473
470 def cacheable(self):
474 def cacheable(self):
471 return bool(self.stat.st_ino)
475 return bool(self.stat.st_ino)
472
476
473 __hash__ = object.__hash__
477 __hash__ = object.__hash__
474
478
475 def __eq__(self, other):
479 def __eq__(self, other):
476 try:
480 try:
477 return self.stat == other.stat
481 return self.stat == other.stat
478 except AttributeError:
482 except AttributeError:
479 return False
483 return False
480
484
481 def __ne__(self, other):
485 def __ne__(self, other):
482 return not self == other
486 return not self == other
483
487
484 def executablepath():
488 def executablepath():
485 return None # available on Windows only
489 return None # available on Windows only
486
490
487 class unixdomainserver(socket.socket):
491 class unixdomainserver(socket.socket):
488 def __init__(self, join, subsystem):
492 def __init__(self, join, subsystem):
489 '''Create a unix domain socket with the given prefix.'''
493 '''Create a unix domain socket with the given prefix.'''
490 super(unixdomainserver, self).__init__(socket.AF_UNIX)
494 super(unixdomainserver, self).__init__(socket.AF_UNIX)
491 sockname = subsystem + '.sock'
495 sockname = subsystem + '.sock'
492 self.realpath = self.path = join(sockname)
496 self.realpath = self.path = join(sockname)
493 if os.path.islink(self.path):
497 if os.path.islink(self.path):
494 if os.path.exists(self.path):
498 if os.path.exists(self.path):
495 self.realpath = os.readlink(self.path)
499 self.realpath = os.readlink(self.path)
496 else:
500 else:
497 os.unlink(self.path)
501 os.unlink(self.path)
498 try:
502 try:
499 self.bind(self.realpath)
503 self.bind(self.realpath)
500 except socket.error, err:
504 except socket.error, err:
501 if err.args[0] == 'AF_UNIX path too long':
505 if err.args[0] == 'AF_UNIX path too long':
502 tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
506 tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
503 self.realpath = os.path.join(tmpdir, sockname)
507 self.realpath = os.path.join(tmpdir, sockname)
504 try:
508 try:
505 self.bind(self.realpath)
509 self.bind(self.realpath)
506 os.symlink(self.realpath, self.path)
510 os.symlink(self.realpath, self.path)
507 except (OSError, socket.error):
511 except (OSError, socket.error):
508 self.cleanup()
512 self.cleanup()
509 raise
513 raise
510 else:
514 else:
511 raise
515 raise
512 self.listen(5)
516 self.listen(5)
513
517
514 def cleanup(self):
518 def cleanup(self):
515 def okayifmissing(f, path):
519 def okayifmissing(f, path):
516 try:
520 try:
517 f(path)
521 f(path)
518 except OSError, err:
522 except OSError, err:
519 if err.errno != errno.ENOENT:
523 if err.errno != errno.ENOENT:
520 raise
524 raise
521
525
522 okayifmissing(os.unlink, self.path)
526 okayifmissing(os.unlink, self.path)
523 if self.realpath != self.path:
527 if self.realpath != self.path:
524 okayifmissing(os.unlink, self.realpath)
528 okayifmissing(os.unlink, self.realpath)
525 okayifmissing(os.rmdir, os.path.dirname(self.realpath))
529 okayifmissing(os.rmdir, os.path.dirname(self.realpath))
@@ -1,335 +1,339 b''
1 # windows.py - Windows utility function implementations for Mercurial
1 # windows.py - Windows utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-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 osutil, encoding
9 import osutil, encoding
10 import errno, msvcrt, os, re, stat, sys, _winreg
10 import errno, msvcrt, os, re, stat, sys, _winreg
11
11
12 import win32
12 import win32
13 executablepath = win32.executablepath
13 executablepath = win32.executablepath
14 getuser = win32.getuser
14 getuser = win32.getuser
15 hidewindow = win32.hidewindow
15 hidewindow = win32.hidewindow
16 makedir = win32.makedir
16 makedir = win32.makedir
17 nlinks = win32.nlinks
17 nlinks = win32.nlinks
18 oslink = win32.oslink
18 oslink = win32.oslink
19 samedevice = win32.samedevice
19 samedevice = win32.samedevice
20 samefile = win32.samefile
20 samefile = win32.samefile
21 setsignalhandler = win32.setsignalhandler
21 setsignalhandler = win32.setsignalhandler
22 spawndetached = win32.spawndetached
22 spawndetached = win32.spawndetached
23 split = os.path.split
23 split = os.path.split
24 termwidth = win32.termwidth
24 termwidth = win32.termwidth
25 testpid = win32.testpid
25 testpid = win32.testpid
26 unlink = win32.unlink
26 unlink = win32.unlink
27
27
28 umask = 0022
28 umask = 0022
29
29
30 # wrap osutil.posixfile to provide friendlier exceptions
30 # wrap osutil.posixfile to provide friendlier exceptions
31 def posixfile(name, mode='r', buffering=-1):
31 def posixfile(name, mode='r', buffering=-1):
32 try:
32 try:
33 return osutil.posixfile(name, mode, buffering)
33 return osutil.posixfile(name, mode, buffering)
34 except WindowsError, err:
34 except WindowsError, err:
35 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
35 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
36 posixfile.__doc__ = osutil.posixfile.__doc__
36 posixfile.__doc__ = osutil.posixfile.__doc__
37
37
38 class winstdout(object):
38 class winstdout(object):
39 '''stdout on windows misbehaves if sent through a pipe'''
39 '''stdout on windows misbehaves if sent through a pipe'''
40
40
41 def __init__(self, fp):
41 def __init__(self, fp):
42 self.fp = fp
42 self.fp = fp
43
43
44 def __getattr__(self, key):
44 def __getattr__(self, key):
45 return getattr(self.fp, key)
45 return getattr(self.fp, key)
46
46
47 def close(self):
47 def close(self):
48 try:
48 try:
49 self.fp.close()
49 self.fp.close()
50 except IOError:
50 except IOError:
51 pass
51 pass
52
52
53 def write(self, s):
53 def write(self, s):
54 try:
54 try:
55 # This is workaround for "Not enough space" error on
55 # This is workaround for "Not enough space" error on
56 # writing large size of data to console.
56 # writing large size of data to console.
57 limit = 16000
57 limit = 16000
58 l = len(s)
58 l = len(s)
59 start = 0
59 start = 0
60 self.softspace = 0
60 self.softspace = 0
61 while start < l:
61 while start < l:
62 end = start + limit
62 end = start + limit
63 self.fp.write(s[start:end])
63 self.fp.write(s[start:end])
64 start = end
64 start = end
65 except IOError, inst:
65 except IOError, inst:
66 if inst.errno != 0:
66 if inst.errno != 0:
67 raise
67 raise
68 self.close()
68 self.close()
69 raise IOError(errno.EPIPE, 'Broken pipe')
69 raise IOError(errno.EPIPE, 'Broken pipe')
70
70
71 def flush(self):
71 def flush(self):
72 try:
72 try:
73 return self.fp.flush()
73 return self.fp.flush()
74 except IOError, inst:
74 except IOError, inst:
75 if inst.errno != errno.EINVAL:
75 if inst.errno != errno.EINVAL:
76 raise
76 raise
77 self.close()
77 self.close()
78 raise IOError(errno.EPIPE, 'Broken pipe')
78 raise IOError(errno.EPIPE, 'Broken pipe')
79
79
80 sys.__stdout__ = sys.stdout = winstdout(sys.stdout)
80 sys.__stdout__ = sys.stdout = winstdout(sys.stdout)
81
81
82 def _is_win_9x():
82 def _is_win_9x():
83 '''return true if run on windows 95, 98 or me.'''
83 '''return true if run on windows 95, 98 or me.'''
84 try:
84 try:
85 return sys.getwindowsversion()[3] == 1
85 return sys.getwindowsversion()[3] == 1
86 except AttributeError:
86 except AttributeError:
87 return 'command' in os.environ.get('comspec', '')
87 return 'command' in os.environ.get('comspec', '')
88
88
89 def openhardlinks():
89 def openhardlinks():
90 return not _is_win_9x()
90 return not _is_win_9x()
91
91
92 def parsepatchoutput(output_line):
92 def parsepatchoutput(output_line):
93 """parses the output produced by patch and returns the filename"""
93 """parses the output produced by patch and returns the filename"""
94 pf = output_line[14:]
94 pf = output_line[14:]
95 if pf[0] == '`':
95 if pf[0] == '`':
96 pf = pf[1:-1] # Remove the quotes
96 pf = pf[1:-1] # Remove the quotes
97 return pf
97 return pf
98
98
99 def sshargs(sshcmd, host, user, port):
99 def sshargs(sshcmd, host, user, port):
100 '''Build argument list for ssh or Plink'''
100 '''Build argument list for ssh or Plink'''
101 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
101 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
102 args = user and ("%s@%s" % (user, host)) or host
102 args = user and ("%s@%s" % (user, host)) or host
103 return port and ("%s %s %s" % (args, pflag, port)) or args
103 return port and ("%s %s %s" % (args, pflag, port)) or args
104
104
105 def setflags(f, l, x):
105 def setflags(f, l, x):
106 pass
106 pass
107
107
108 def copymode(src, dst, mode=None):
108 def copymode(src, dst, mode=None):
109 pass
109 pass
110
110
111 def checkexec(path):
111 def checkexec(path):
112 return False
112 return False
113
113
114 def checklink(path):
114 def checklink(path):
115 return False
115 return False
116
116
117 def setbinary(fd):
117 def setbinary(fd):
118 # When run without console, pipes may expose invalid
118 # When run without console, pipes may expose invalid
119 # fileno(), usually set to -1.
119 # fileno(), usually set to -1.
120 fno = getattr(fd, 'fileno', None)
120 fno = getattr(fd, 'fileno', None)
121 if fno is not None and fno() >= 0:
121 if fno is not None and fno() >= 0:
122 msvcrt.setmode(fno(), os.O_BINARY)
122 msvcrt.setmode(fno(), os.O_BINARY)
123
123
124 def pconvert(path):
124 def pconvert(path):
125 return path.replace(os.sep, '/')
125 return path.replace(os.sep, '/')
126
126
127 def localpath(path):
127 def localpath(path):
128 return path.replace('/', '\\')
128 return path.replace('/', '\\')
129
129
130 def normpath(path):
130 def normpath(path):
131 return pconvert(os.path.normpath(path))
131 return pconvert(os.path.normpath(path))
132
132
133 def normcase(path):
133 def normcase(path):
134 return encoding.upper(path)
134 return encoding.upper(path)
135
135
136 def realpath(path):
136 def realpath(path):
137 '''
137 '''
138 Returns the true, canonical file system path equivalent to the given
138 Returns the true, canonical file system path equivalent to the given
139 path.
139 path.
140 '''
140 '''
141 # TODO: There may be a more clever way to do this that also handles other,
141 # TODO: There may be a more clever way to do this that also handles other,
142 # less common file systems.
142 # less common file systems.
143 return os.path.normpath(normcase(os.path.realpath(path)))
143 return os.path.normpath(normcase(os.path.realpath(path)))
144
144
145 def samestat(s1, s2):
145 def samestat(s1, s2):
146 return False
146 return False
147
147
148 # A sequence of backslashes is special iff it precedes a double quote:
148 # A sequence of backslashes is special iff it precedes a double quote:
149 # - if there's an even number of backslashes, the double quote is not
149 # - if there's an even number of backslashes, the double quote is not
150 # quoted (i.e. it ends the quoted region)
150 # quoted (i.e. it ends the quoted region)
151 # - if there's an odd number of backslashes, the double quote is quoted
151 # - if there's an odd number of backslashes, the double quote is quoted
152 # - in both cases, every pair of backslashes is unquoted into a single
152 # - in both cases, every pair of backslashes is unquoted into a single
153 # backslash
153 # backslash
154 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
154 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
155 # So, to quote a string, we must surround it in double quotes, double
155 # So, to quote a string, we must surround it in double quotes, double
156 # the number of backslashes that precede double quotes and add another
156 # the number of backslashes that precede double quotes and add another
157 # backslash before every double quote (being careful with the double
157 # backslash before every double quote (being careful with the double
158 # quote we've appended to the end)
158 # quote we've appended to the end)
159 _quotere = None
159 _quotere = None
160 def shellquote(s):
160 def shellquote(s):
161 global _quotere
161 global _quotere
162 if _quotere is None:
162 if _quotere is None:
163 _quotere = re.compile(r'(\\*)("|\\$)')
163 _quotere = re.compile(r'(\\*)("|\\$)')
164 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
164 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
165
165
166 def quotecommand(cmd):
166 def quotecommand(cmd):
167 """Build a command string suitable for os.popen* calls."""
167 """Build a command string suitable for os.popen* calls."""
168 if sys.version_info < (2, 7, 1):
168 if sys.version_info < (2, 7, 1):
169 # Python versions since 2.7.1 do this extra quoting themselves
169 # Python versions since 2.7.1 do this extra quoting themselves
170 return '"' + cmd + '"'
170 return '"' + cmd + '"'
171 return cmd
171 return cmd
172
172
173 def popen(command, mode='r'):
173 def popen(command, mode='r'):
174 # Work around "popen spawned process may not write to stdout
174 # Work around "popen spawned process may not write to stdout
175 # under windows"
175 # under windows"
176 # http://bugs.python.org/issue1366
176 # http://bugs.python.org/issue1366
177 command += " 2> %s" % os.devnull
177 command += " 2> %s" % os.devnull
178 return os.popen(quotecommand(command), mode)
178 return os.popen(quotecommand(command), mode)
179
179
180 def explainexit(code):
180 def explainexit(code):
181 return _("exited with status %d") % code, code
181 return _("exited with status %d") % code, code
182
182
183 # if you change this stub into a real check, please try to implement the
183 # if you change this stub into a real check, please try to implement the
184 # username and groupname functions above, too.
184 # username and groupname functions above, too.
185 def isowner(st):
185 def isowner(st):
186 return True
186 return True
187
187
188 def findexe(command):
188 def findexe(command):
189 '''Find executable for command searching like cmd.exe does.
189 '''Find executable for command searching like cmd.exe does.
190 If command is a basename then PATH is searched for command.
190 If command is a basename then PATH is searched for command.
191 PATH isn't searched if command is an absolute or relative path.
191 PATH isn't searched if command is an absolute or relative path.
192 An extension from PATHEXT is found and added if not present.
192 An extension from PATHEXT is found and added if not present.
193 If command isn't found None is returned.'''
193 If command isn't found None is returned.'''
194 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
194 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
195 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
195 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
196 if os.path.splitext(command)[1].lower() in pathexts:
196 if os.path.splitext(command)[1].lower() in pathexts:
197 pathexts = ['']
197 pathexts = ['']
198
198
199 def findexisting(pathcommand):
199 def findexisting(pathcommand):
200 'Will append extension (if needed) and return existing file'
200 'Will append extension (if needed) and return existing file'
201 for ext in pathexts:
201 for ext in pathexts:
202 executable = pathcommand + ext
202 executable = pathcommand + ext
203 if os.path.exists(executable):
203 if os.path.exists(executable):
204 return executable
204 return executable
205 return None
205 return None
206
206
207 if os.sep in command:
207 if os.sep in command:
208 return findexisting(command)
208 return findexisting(command)
209
209
210 for path in os.environ.get('PATH', '').split(os.pathsep):
210 for path in os.environ.get('PATH', '').split(os.pathsep):
211 executable = findexisting(os.path.join(path, command))
211 executable = findexisting(os.path.join(path, command))
212 if executable is not None:
212 if executable is not None:
213 return executable
213 return executable
214 return findexisting(os.path.expanduser(os.path.expandvars(command)))
214 return findexisting(os.path.expanduser(os.path.expandvars(command)))
215
215
216 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
216 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
217
217
218 def statfiles(files):
218 def statfiles(files):
219 '''Stat each file in files. Yield each stat, or None if a file
219 '''Stat each file in files. Yield each stat, or None if a file
220 does not exist or has a type we don't care about.
220 does not exist or has a type we don't care about.
221
221
222 Cluster and cache stat per directory to minimize number of OS stat calls.'''
222 Cluster and cache stat per directory to minimize number of OS stat calls.'''
223 dircache = {} # dirname -> filename -> status | None if file does not exist
223 dircache = {} # dirname -> filename -> status | None if file does not exist
224 getkind = stat.S_IFMT
224 getkind = stat.S_IFMT
225 for nf in files:
225 for nf in files:
226 nf = normcase(nf)
226 nf = normcase(nf)
227 dir, base = os.path.split(nf)
227 dir, base = os.path.split(nf)
228 if not dir:
228 if not dir:
229 dir = '.'
229 dir = '.'
230 cache = dircache.get(dir, None)
230 cache = dircache.get(dir, None)
231 if cache is None:
231 if cache is None:
232 try:
232 try:
233 dmap = dict([(normcase(n), s)
233 dmap = dict([(normcase(n), s)
234 for n, k, s in osutil.listdir(dir, True)
234 for n, k, s in osutil.listdir(dir, True)
235 if getkind(s.st_mode) in _wantedkinds])
235 if getkind(s.st_mode) in _wantedkinds])
236 except OSError, err:
236 except OSError, err:
237 # handle directory not found in Python version prior to 2.5
237 # handle directory not found in Python version prior to 2.5
238 # Python <= 2.4 returns native Windows code 3 in errno
238 # Python <= 2.4 returns native Windows code 3 in errno
239 # Python >= 2.5 returns ENOENT and adds winerror field
239 # Python >= 2.5 returns ENOENT and adds winerror field
240 # EINVAL is raised if dir is not a directory.
240 # EINVAL is raised if dir is not a directory.
241 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
241 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
242 errno.ENOTDIR):
242 errno.ENOTDIR):
243 raise
243 raise
244 dmap = {}
244 dmap = {}
245 cache = dircache.setdefault(dir, dmap)
245 cache = dircache.setdefault(dir, dmap)
246 yield cache.get(base, None)
246 yield cache.get(base, None)
247
247
248 def username(uid=None):
248 def username(uid=None):
249 """Return the name of the user with the given uid.
249 """Return the name of the user with the given uid.
250
250
251 If uid is None, return the name of the current user."""
251 If uid is None, return the name of the current user."""
252 return None
252 return None
253
253
254 def groupname(gid=None):
254 def groupname(gid=None):
255 """Return the name of the group with the given gid.
255 """Return the name of the group with the given gid.
256
256
257 If gid is None, return the name of the current group."""
257 If gid is None, return the name of the current group."""
258 return None
258 return None
259
259
260 def _removedirs(name):
260 def _removedirs(name):
261 """special version of os.removedirs that does not remove symlinked
261 """special version of os.removedirs that does not remove symlinked
262 directories or junction points if they actually contain files"""
262 directories or junction points if they actually contain files"""
263 if osutil.listdir(name):
263 if osutil.listdir(name):
264 return
264 return
265 os.rmdir(name)
265 os.rmdir(name)
266 head, tail = os.path.split(name)
266 head, tail = os.path.split(name)
267 if not tail:
267 if not tail:
268 head, tail = os.path.split(head)
268 head, tail = os.path.split(head)
269 while head and tail:
269 while head and tail:
270 try:
270 try:
271 if osutil.listdir(head):
271 if osutil.listdir(head):
272 return
272 return
273 os.rmdir(head)
273 os.rmdir(head)
274 except (ValueError, OSError):
274 except (ValueError, OSError):
275 break
275 break
276 head, tail = os.path.split(head)
276 head, tail = os.path.split(head)
277
277
278 def unlinkpath(f):
278 def unlinkpath(f, ignoremissing=False):
279 """unlink and remove the directory if it is empty"""
279 """unlink and remove the directory if it is empty"""
280 unlink(f)
280 try:
281 unlink(f)
282 except OSError, e:
283 if not (ignoremissing and e.errno == errno.ENOENT):
284 raise
281 # try removing directories that might now be empty
285 # try removing directories that might now be empty
282 try:
286 try:
283 _removedirs(os.path.dirname(f))
287 _removedirs(os.path.dirname(f))
284 except OSError:
288 except OSError:
285 pass
289 pass
286
290
287 def rename(src, dst):
291 def rename(src, dst):
288 '''atomically rename file src to dst, replacing dst if it exists'''
292 '''atomically rename file src to dst, replacing dst if it exists'''
289 try:
293 try:
290 os.rename(src, dst)
294 os.rename(src, dst)
291 except OSError, e:
295 except OSError, e:
292 if e.errno != errno.EEXIST:
296 if e.errno != errno.EEXIST:
293 raise
297 raise
294 unlink(dst)
298 unlink(dst)
295 os.rename(src, dst)
299 os.rename(src, dst)
296
300
297 def gethgcmd():
301 def gethgcmd():
298 return [sys.executable] + sys.argv[:1]
302 return [sys.executable] + sys.argv[:1]
299
303
300 def groupmembers(name):
304 def groupmembers(name):
301 # Don't support groups on Windows for now
305 # Don't support groups on Windows for now
302 raise KeyError
306 raise KeyError
303
307
304 def isexec(f):
308 def isexec(f):
305 return False
309 return False
306
310
307 class cachestat(object):
311 class cachestat(object):
308 def __init__(self, path):
312 def __init__(self, path):
309 pass
313 pass
310
314
311 def cacheable(self):
315 def cacheable(self):
312 return False
316 return False
313
317
314 def lookupreg(key, valname=None, scope=None):
318 def lookupreg(key, valname=None, scope=None):
315 ''' Look up a key/value name in the Windows registry.
319 ''' Look up a key/value name in the Windows registry.
316
320
317 valname: value name. If unspecified, the default value for the key
321 valname: value name. If unspecified, the default value for the key
318 is used.
322 is used.
319 scope: optionally specify scope for registry lookup, this can be
323 scope: optionally specify scope for registry lookup, this can be
320 a sequence of scopes to look up in order. Default (CURRENT_USER,
324 a sequence of scopes to look up in order. Default (CURRENT_USER,
321 LOCAL_MACHINE).
325 LOCAL_MACHINE).
322 '''
326 '''
323 if scope is None:
327 if scope is None:
324 scope = (_winreg.HKEY_CURRENT_USER, _winreg.HKEY_LOCAL_MACHINE)
328 scope = (_winreg.HKEY_CURRENT_USER, _winreg.HKEY_LOCAL_MACHINE)
325 elif not isinstance(scope, (list, tuple)):
329 elif not isinstance(scope, (list, tuple)):
326 scope = (scope,)
330 scope = (scope,)
327 for s in scope:
331 for s in scope:
328 try:
332 try:
329 val = _winreg.QueryValueEx(_winreg.OpenKey(s, key), valname)[0]
333 val = _winreg.QueryValueEx(_winreg.OpenKey(s, key), valname)[0]
330 # never let a Unicode string escape into the wild
334 # never let a Unicode string escape into the wild
331 return encoding.tolocal(val.encode('UTF-8'))
335 return encoding.tolocal(val.encode('UTF-8'))
332 except EnvironmentError:
336 except EnvironmentError:
333 pass
337 pass
334
338
335 expandglobs = True
339 expandglobs = True
General Comments 0
You need to be logged in to leave comments. Login now