##// END OF EJS Templates
misc: replace .parents()[0] with p1()
Matt Mackall -
r13878:a8d13ee0 default
parent child Browse files
Show More

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

@@ -1,22 +1,22 b''
1 '''
1 '''
2 Examples of useful python hooks for Mercurial.
2 Examples of useful python hooks for Mercurial.
3 '''
3 '''
4 from mercurial import patch, util
4 from mercurial import patch, util
5
5
6 def diffstat(ui, repo, **kwargs):
6 def diffstat(ui, repo, **kwargs):
7 '''Example usage:
7 '''Example usage:
8
8
9 [hooks]
9 [hooks]
10 commit.diffstat = python:/path/to/this/file.py:diffstat
10 commit.diffstat = python:/path/to/this/file.py:diffstat
11 changegroup.diffstat = python:/path/to/this/file.py:diffstat
11 changegroup.diffstat = python:/path/to/this/file.py:diffstat
12 '''
12 '''
13 if kwargs.get('parent2'):
13 if kwargs.get('parent2'):
14 return
14 return
15 node = kwargs['node']
15 node = kwargs['node']
16 first = repo[node].parents()[0].node()
16 first = repo[node].p1().node()
17 if 'url' in kwargs:
17 if 'url' in kwargs:
18 last = repo['tip'].node()
18 last = repo['tip'].node()
19 else:
19 else:
20 last = node
20 last = node
21 diff = patch.diff(repo, first, last)
21 diff = patch.diff(repo, first, last)
22 ui.write(patch.diffstat(util.iterlines(diff)))
22 ui.write(patch.diffstat(util.iterlines(diff)))
@@ -1,325 +1,325 b''
1 # extdiff.py - external diff program support for mercurial
1 # extdiff.py - external diff program support for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 '''command to allow external programs to compare revisions
8 '''command to allow external programs to compare revisions
9
9
10 The extdiff Mercurial extension allows you to use external programs
10 The extdiff Mercurial extension allows you to use external programs
11 to compare revisions, or revision with working directory. The external
11 to compare revisions, or revision with working directory. The external
12 diff programs are called with a configurable set of options and two
12 diff programs are called with a configurable set of options and two
13 non-option arguments: paths to directories containing snapshots of
13 non-option arguments: paths to directories containing snapshots of
14 files to compare.
14 files to compare.
15
15
16 The extdiff extension also allows to configure new diff commands, so
16 The extdiff extension also allows to configure new diff commands, so
17 you do not need to type :hg:`extdiff -p kdiff3` always. ::
17 you do not need to type :hg:`extdiff -p kdiff3` always. ::
18
18
19 [extdiff]
19 [extdiff]
20 # add new command that runs GNU diff(1) in 'context diff' mode
20 # add new command that runs GNU diff(1) in 'context diff' mode
21 cdiff = gdiff -Nprc5
21 cdiff = gdiff -Nprc5
22 ## or the old way:
22 ## or the old way:
23 #cmd.cdiff = gdiff
23 #cmd.cdiff = gdiff
24 #opts.cdiff = -Nprc5
24 #opts.cdiff = -Nprc5
25
25
26 # add new command called vdiff, runs kdiff3
26 # add new command called vdiff, runs kdiff3
27 vdiff = kdiff3
27 vdiff = kdiff3
28
28
29 # add new command called meld, runs meld (no need to name twice)
29 # add new command called meld, runs meld (no need to name twice)
30 meld =
30 meld =
31
31
32 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
32 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
33 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
33 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
34 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
34 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
35 # your .vimrc
35 # your .vimrc
36 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
36 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
37
37
38 Tool arguments can include variables that are expanded at runtime::
38 Tool arguments can include variables that are expanded at runtime::
39
39
40 $parent1, $plabel1 - filename, descriptive label of first parent
40 $parent1, $plabel1 - filename, descriptive label of first parent
41 $child, $clabel - filename, descriptive label of child revision
41 $child, $clabel - filename, descriptive label of child revision
42 $parent2, $plabel2 - filename, descriptive label of second parent
42 $parent2, $plabel2 - filename, descriptive label of second parent
43 $parent is an alias for $parent1.
43 $parent is an alias for $parent1.
44
44
45 The extdiff extension will look in your [diff-tools] and [merge-tools]
45 The extdiff extension will look in your [diff-tools] and [merge-tools]
46 sections for diff tool arguments, when none are specified in [extdiff].
46 sections for diff tool arguments, when none are specified in [extdiff].
47
47
48 ::
48 ::
49
49
50 [extdiff]
50 [extdiff]
51 kdiff3 =
51 kdiff3 =
52
52
53 [diff-tools]
53 [diff-tools]
54 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
54 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
55
55
56 You can use -I/-X and list of file or directory names like normal
56 You can use -I/-X and list of file or directory names like normal
57 :hg:`diff` command. The extdiff extension makes snapshots of only
57 :hg:`diff` command. The extdiff extension makes snapshots of only
58 needed files, so running the external diff program will actually be
58 needed files, so running the external diff program will actually be
59 pretty fast (at least faster than having to compare the entire tree).
59 pretty fast (at least faster than having to compare the entire tree).
60 '''
60 '''
61
61
62 from mercurial.i18n import _
62 from mercurial.i18n import _
63 from mercurial.node import short, nullid
63 from mercurial.node import short, nullid
64 from mercurial import cmdutil, util, commands, encoding
64 from mercurial import cmdutil, util, commands, encoding
65 import os, shlex, shutil, tempfile, re
65 import os, shlex, shutil, tempfile, re
66
66
67 def snapshot(ui, repo, files, node, tmproot):
67 def snapshot(ui, repo, files, node, tmproot):
68 '''snapshot files as of some revision
68 '''snapshot files as of some revision
69 if not using snapshot, -I/-X does not work and recursive diff
69 if not using snapshot, -I/-X does not work and recursive diff
70 in tools like kdiff3 and meld displays too many files.'''
70 in tools like kdiff3 and meld displays too many files.'''
71 dirname = os.path.basename(repo.root)
71 dirname = os.path.basename(repo.root)
72 if dirname == "":
72 if dirname == "":
73 dirname = "root"
73 dirname = "root"
74 if node is not None:
74 if node is not None:
75 dirname = '%s.%s' % (dirname, short(node))
75 dirname = '%s.%s' % (dirname, short(node))
76 base = os.path.join(tmproot, dirname)
76 base = os.path.join(tmproot, dirname)
77 os.mkdir(base)
77 os.mkdir(base)
78 if node is not None:
78 if node is not None:
79 ui.note(_('making snapshot of %d files from rev %s\n') %
79 ui.note(_('making snapshot of %d files from rev %s\n') %
80 (len(files), short(node)))
80 (len(files), short(node)))
81 else:
81 else:
82 ui.note(_('making snapshot of %d files from working directory\n') %
82 ui.note(_('making snapshot of %d files from working directory\n') %
83 (len(files)))
83 (len(files)))
84 wopener = util.opener(base)
84 wopener = util.opener(base)
85 fns_and_mtime = []
85 fns_and_mtime = []
86 ctx = repo[node]
86 ctx = repo[node]
87 for fn in files:
87 for fn in files:
88 wfn = util.pconvert(fn)
88 wfn = util.pconvert(fn)
89 if not wfn in ctx:
89 if not wfn in ctx:
90 # File doesn't exist; could be a bogus modify
90 # File doesn't exist; could be a bogus modify
91 continue
91 continue
92 ui.note(' %s\n' % wfn)
92 ui.note(' %s\n' % wfn)
93 dest = os.path.join(base, wfn)
93 dest = os.path.join(base, wfn)
94 fctx = ctx[wfn]
94 fctx = ctx[wfn]
95 data = repo.wwritedata(wfn, fctx.data())
95 data = repo.wwritedata(wfn, fctx.data())
96 if 'l' in fctx.flags():
96 if 'l' in fctx.flags():
97 wopener.symlink(data, wfn)
97 wopener.symlink(data, wfn)
98 else:
98 else:
99 wopener(wfn, 'w').write(data)
99 wopener(wfn, 'w').write(data)
100 if 'x' in fctx.flags():
100 if 'x' in fctx.flags():
101 util.set_flags(dest, False, True)
101 util.set_flags(dest, False, True)
102 if node is None:
102 if node is None:
103 fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
103 fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
104 return dirname, fns_and_mtime
104 return dirname, fns_and_mtime
105
105
106 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
106 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
107 '''Do the actuall diff:
107 '''Do the actuall diff:
108
108
109 - copy to a temp structure if diffing 2 internal revisions
109 - copy to a temp structure if diffing 2 internal revisions
110 - copy to a temp structure if diffing working revision with
110 - copy to a temp structure if diffing working revision with
111 another one and more than 1 file is changed
111 another one and more than 1 file is changed
112 - just invoke the diff for a single file in the working dir
112 - just invoke the diff for a single file in the working dir
113 '''
113 '''
114
114
115 revs = opts.get('rev')
115 revs = opts.get('rev')
116 change = opts.get('change')
116 change = opts.get('change')
117 args = ' '.join(diffopts)
117 args = ' '.join(diffopts)
118 do3way = '$parent2' in args
118 do3way = '$parent2' in args
119
119
120 if revs and change:
120 if revs and change:
121 msg = _('cannot specify --rev and --change at the same time')
121 msg = _('cannot specify --rev and --change at the same time')
122 raise util.Abort(msg)
122 raise util.Abort(msg)
123 elif change:
123 elif change:
124 node2 = cmdutil.revsingle(repo, change, None).node()
124 node2 = cmdutil.revsingle(repo, change, None).node()
125 node1a, node1b = repo.changelog.parents(node2)
125 node1a, node1b = repo.changelog.parents(node2)
126 else:
126 else:
127 node1a, node2 = cmdutil.revpair(repo, revs)
127 node1a, node2 = cmdutil.revpair(repo, revs)
128 if not revs:
128 if not revs:
129 node1b = repo.dirstate.parents()[1]
129 node1b = repo.dirstate.p2()
130 else:
130 else:
131 node1b = nullid
131 node1b = nullid
132
132
133 # Disable 3-way merge if there is only one parent
133 # Disable 3-way merge if there is only one parent
134 if do3way:
134 if do3way:
135 if node1b == nullid:
135 if node1b == nullid:
136 do3way = False
136 do3way = False
137
137
138 matcher = cmdutil.match(repo, pats, opts)
138 matcher = cmdutil.match(repo, pats, opts)
139 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
139 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
140 if do3way:
140 if do3way:
141 mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
141 mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
142 else:
142 else:
143 mod_b, add_b, rem_b = set(), set(), set()
143 mod_b, add_b, rem_b = set(), set(), set()
144 modadd = mod_a | add_a | mod_b | add_b
144 modadd = mod_a | add_a | mod_b | add_b
145 common = modadd | rem_a | rem_b
145 common = modadd | rem_a | rem_b
146 if not common:
146 if not common:
147 return 0
147 return 0
148
148
149 tmproot = tempfile.mkdtemp(prefix='extdiff.')
149 tmproot = tempfile.mkdtemp(prefix='extdiff.')
150 try:
150 try:
151 # Always make a copy of node1a (and node1b, if applicable)
151 # Always make a copy of node1a (and node1b, if applicable)
152 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
152 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
153 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
153 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
154 rev1a = '@%d' % repo[node1a].rev()
154 rev1a = '@%d' % repo[node1a].rev()
155 if do3way:
155 if do3way:
156 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
156 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
157 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
157 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
158 rev1b = '@%d' % repo[node1b].rev()
158 rev1b = '@%d' % repo[node1b].rev()
159 else:
159 else:
160 dir1b = None
160 dir1b = None
161 rev1b = ''
161 rev1b = ''
162
162
163 fns_and_mtime = []
163 fns_and_mtime = []
164
164
165 # If node2 in not the wc or there is >1 change, copy it
165 # If node2 in not the wc or there is >1 change, copy it
166 dir2root = ''
166 dir2root = ''
167 rev2 = ''
167 rev2 = ''
168 if node2:
168 if node2:
169 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
169 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
170 rev2 = '@%d' % repo[node2].rev()
170 rev2 = '@%d' % repo[node2].rev()
171 elif len(common) > 1:
171 elif len(common) > 1:
172 #we only actually need to get the files to copy back to
172 #we only actually need to get the files to copy back to
173 #the working dir in this case (because the other cases
173 #the working dir in this case (because the other cases
174 #are: diffing 2 revisions or single file -- in which case
174 #are: diffing 2 revisions or single file -- in which case
175 #the file is already directly passed to the diff tool).
175 #the file is already directly passed to the diff tool).
176 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
176 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
177 else:
177 else:
178 # This lets the diff tool open the changed file directly
178 # This lets the diff tool open the changed file directly
179 dir2 = ''
179 dir2 = ''
180 dir2root = repo.root
180 dir2root = repo.root
181
181
182 label1a = rev1a
182 label1a = rev1a
183 label1b = rev1b
183 label1b = rev1b
184 label2 = rev2
184 label2 = rev2
185
185
186 # If only one change, diff the files instead of the directories
186 # If only one change, diff the files instead of the directories
187 # Handle bogus modifies correctly by checking if the files exist
187 # Handle bogus modifies correctly by checking if the files exist
188 if len(common) == 1:
188 if len(common) == 1:
189 common_file = util.localpath(common.pop())
189 common_file = util.localpath(common.pop())
190 dir1a = os.path.join(tmproot, dir1a, common_file)
190 dir1a = os.path.join(tmproot, dir1a, common_file)
191 label1a = common_file + rev1a
191 label1a = common_file + rev1a
192 if not os.path.isfile(dir1a):
192 if not os.path.isfile(dir1a):
193 dir1a = os.devnull
193 dir1a = os.devnull
194 if do3way:
194 if do3way:
195 dir1b = os.path.join(tmproot, dir1b, common_file)
195 dir1b = os.path.join(tmproot, dir1b, common_file)
196 label1b = common_file + rev1b
196 label1b = common_file + rev1b
197 if not os.path.isfile(dir1b):
197 if not os.path.isfile(dir1b):
198 dir1b = os.devnull
198 dir1b = os.devnull
199 dir2 = os.path.join(dir2root, dir2, common_file)
199 dir2 = os.path.join(dir2root, dir2, common_file)
200 label2 = common_file + rev2
200 label2 = common_file + rev2
201
201
202 # Function to quote file/dir names in the argument string.
202 # Function to quote file/dir names in the argument string.
203 # When not operating in 3-way mode, an empty string is
203 # When not operating in 3-way mode, an empty string is
204 # returned for parent2
204 # returned for parent2
205 replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b,
205 replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b,
206 plabel1=label1a, plabel2=label1b,
206 plabel1=label1a, plabel2=label1b,
207 clabel=label2, child=dir2)
207 clabel=label2, child=dir2)
208 def quote(match):
208 def quote(match):
209 key = match.group()[1:]
209 key = match.group()[1:]
210 if not do3way and key == 'parent2':
210 if not do3way and key == 'parent2':
211 return ''
211 return ''
212 return util.shellquote(replace[key])
212 return util.shellquote(replace[key])
213
213
214 # Match parent2 first, so 'parent1?' will match both parent1 and parent
214 # Match parent2 first, so 'parent1?' will match both parent1 and parent
215 regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel)'
215 regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel)'
216 if not do3way and not re.search(regex, args):
216 if not do3way and not re.search(regex, args):
217 args += ' $parent1 $child'
217 args += ' $parent1 $child'
218 args = re.sub(regex, quote, args)
218 args = re.sub(regex, quote, args)
219 cmdline = util.shellquote(diffcmd) + ' ' + args
219 cmdline = util.shellquote(diffcmd) + ' ' + args
220
220
221 ui.debug('running %r in %s\n' % (cmdline, tmproot))
221 ui.debug('running %r in %s\n' % (cmdline, tmproot))
222 util.system(cmdline, cwd=tmproot)
222 util.system(cmdline, cwd=tmproot)
223
223
224 for copy_fn, working_fn, mtime in fns_and_mtime:
224 for copy_fn, working_fn, mtime in fns_and_mtime:
225 if os.path.getmtime(copy_fn) != mtime:
225 if os.path.getmtime(copy_fn) != mtime:
226 ui.debug('file changed while diffing. '
226 ui.debug('file changed while diffing. '
227 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
227 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
228 util.copyfile(copy_fn, working_fn)
228 util.copyfile(copy_fn, working_fn)
229
229
230 return 1
230 return 1
231 finally:
231 finally:
232 ui.note(_('cleaning up temp directory\n'))
232 ui.note(_('cleaning up temp directory\n'))
233 shutil.rmtree(tmproot)
233 shutil.rmtree(tmproot)
234
234
235 def extdiff(ui, repo, *pats, **opts):
235 def extdiff(ui, repo, *pats, **opts):
236 '''use external program to diff repository (or selected files)
236 '''use external program to diff repository (or selected files)
237
237
238 Show differences between revisions for the specified files, using
238 Show differences between revisions for the specified files, using
239 an external program. The default program used is diff, with
239 an external program. The default program used is diff, with
240 default options "-Npru".
240 default options "-Npru".
241
241
242 To select a different program, use the -p/--program option. The
242 To select a different program, use the -p/--program option. The
243 program will be passed the names of two directories to compare. To
243 program will be passed the names of two directories to compare. To
244 pass additional options to the program, use -o/--option. These
244 pass additional options to the program, use -o/--option. These
245 will be passed before the names of the directories to compare.
245 will be passed before the names of the directories to compare.
246
246
247 When two revision arguments are given, then changes are shown
247 When two revision arguments are given, then changes are shown
248 between those revisions. If only one revision is specified then
248 between those revisions. If only one revision is specified then
249 that revision is compared to the working directory, and, when no
249 that revision is compared to the working directory, and, when no
250 revisions are specified, the working directory files are compared
250 revisions are specified, the working directory files are compared
251 to its parent.'''
251 to its parent.'''
252 program = opts.get('program')
252 program = opts.get('program')
253 option = opts.get('option')
253 option = opts.get('option')
254 if not program:
254 if not program:
255 program = 'diff'
255 program = 'diff'
256 option = option or ['-Npru']
256 option = option or ['-Npru']
257 return dodiff(ui, repo, program, option, pats, opts)
257 return dodiff(ui, repo, program, option, pats, opts)
258
258
259 cmdtable = {
259 cmdtable = {
260 "extdiff":
260 "extdiff":
261 (extdiff,
261 (extdiff,
262 [('p', 'program', '',
262 [('p', 'program', '',
263 _('comparison program to run'), _('CMD')),
263 _('comparison program to run'), _('CMD')),
264 ('o', 'option', [],
264 ('o', 'option', [],
265 _('pass option to comparison program'), _('OPT')),
265 _('pass option to comparison program'), _('OPT')),
266 ('r', 'rev', [],
266 ('r', 'rev', [],
267 _('revision'), _('REV')),
267 _('revision'), _('REV')),
268 ('c', 'change', '',
268 ('c', 'change', '',
269 _('change made by revision'), _('REV')),
269 _('change made by revision'), _('REV')),
270 ] + commands.walkopts,
270 ] + commands.walkopts,
271 _('hg extdiff [OPT]... [FILE]...')),
271 _('hg extdiff [OPT]... [FILE]...')),
272 }
272 }
273
273
274 def uisetup(ui):
274 def uisetup(ui):
275 for cmd, path in ui.configitems('extdiff'):
275 for cmd, path in ui.configitems('extdiff'):
276 if cmd.startswith('cmd.'):
276 if cmd.startswith('cmd.'):
277 cmd = cmd[4:]
277 cmd = cmd[4:]
278 if not path:
278 if not path:
279 path = cmd
279 path = cmd
280 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
280 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
281 diffopts = diffopts and [diffopts] or []
281 diffopts = diffopts and [diffopts] or []
282 elif cmd.startswith('opts.'):
282 elif cmd.startswith('opts.'):
283 continue
283 continue
284 else:
284 else:
285 # command = path opts
285 # command = path opts
286 if path:
286 if path:
287 diffopts = shlex.split(path)
287 diffopts = shlex.split(path)
288 path = diffopts.pop(0)
288 path = diffopts.pop(0)
289 else:
289 else:
290 path, diffopts = cmd, []
290 path, diffopts = cmd, []
291 # look for diff arguments in [diff-tools] then [merge-tools]
291 # look for diff arguments in [diff-tools] then [merge-tools]
292 if diffopts == []:
292 if diffopts == []:
293 args = ui.config('diff-tools', cmd+'.diffargs') or \
293 args = ui.config('diff-tools', cmd+'.diffargs') or \
294 ui.config('merge-tools', cmd+'.diffargs')
294 ui.config('merge-tools', cmd+'.diffargs')
295 if args:
295 if args:
296 diffopts = shlex.split(args)
296 diffopts = shlex.split(args)
297 def save(cmd, path, diffopts):
297 def save(cmd, path, diffopts):
298 '''use closure to save diff command to use'''
298 '''use closure to save diff command to use'''
299 def mydiff(ui, repo, *pats, **opts):
299 def mydiff(ui, repo, *pats, **opts):
300 return dodiff(ui, repo, path, diffopts + opts['option'],
300 return dodiff(ui, repo, path, diffopts + opts['option'],
301 pats, opts)
301 pats, opts)
302 doc = _('''\
302 doc = _('''\
303 use %(path)s to diff repository (or selected files)
303 use %(path)s to diff repository (or selected files)
304
304
305 Show differences between revisions for the specified files, using
305 Show differences between revisions for the specified files, using
306 the %(path)s program.
306 the %(path)s program.
307
307
308 When two revision arguments are given, then changes are shown
308 When two revision arguments are given, then changes are shown
309 between those revisions. If only one revision is specified then
309 between those revisions. If only one revision is specified then
310 that revision is compared to the working directory, and, when no
310 that revision is compared to the working directory, and, when no
311 revisions are specified, the working directory files are compared
311 revisions are specified, the working directory files are compared
312 to its parent.\
312 to its parent.\
313 ''') % dict(path=util.uirepr(path))
313 ''') % dict(path=util.uirepr(path))
314
314
315 # We must translate the docstring right away since it is
315 # We must translate the docstring right away since it is
316 # used as a format string. The string will unfortunately
316 # used as a format string. The string will unfortunately
317 # be translated again in commands.helpcmd and this will
317 # be translated again in commands.helpcmd and this will
318 # fail when the docstring contains non-ASCII characters.
318 # fail when the docstring contains non-ASCII characters.
319 # Decoding the string to a Unicode string here (using the
319 # Decoding the string to a Unicode string here (using the
320 # right encoding) prevents that.
320 # right encoding) prevents that.
321 mydiff.__doc__ = doc.decode(encoding.encoding)
321 mydiff.__doc__ = doc.decode(encoding.encoding)
322 return mydiff
322 return mydiff
323 cmdtable[cmd] = (save(cmd, path, diffopts),
323 cmdtable[cmd] = (save(cmd, path, diffopts),
324 cmdtable['extdiff'][1][1:],
324 cmdtable['extdiff'][1][1:],
325 _('hg %s [OPTION]... [FILE]...') % cmd)
325 _('hg %s [OPTION]... [FILE]...') % cmd)
@@ -1,251 +1,251 b''
1 # Copyright (C) 2007-8 Brendan Cully <brendan@kublai.com>
1 # Copyright (C) 2007-8 Brendan Cully <brendan@kublai.com>
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 """hooks for integrating with the CIA.vc notification service
6 """hooks for integrating with the CIA.vc notification service
7
7
8 This is meant to be run as a changegroup or incoming hook. To
8 This is meant to be run as a changegroup or incoming hook. To
9 configure it, set the following options in your hgrc::
9 configure it, set the following options in your hgrc::
10
10
11 [cia]
11 [cia]
12 # your registered CIA user name
12 # your registered CIA user name
13 user = foo
13 user = foo
14 # the name of the project in CIA
14 # the name of the project in CIA
15 project = foo
15 project = foo
16 # the module (subproject) (optional)
16 # the module (subproject) (optional)
17 #module = foo
17 #module = foo
18 # Append a diffstat to the log message (optional)
18 # Append a diffstat to the log message (optional)
19 #diffstat = False
19 #diffstat = False
20 # Template to use for log messages (optional)
20 # Template to use for log messages (optional)
21 #template = {desc}\\n{baseurl}/rev/{node}-- {diffstat}
21 #template = {desc}\\n{baseurl}/rev/{node}-- {diffstat}
22 # Style to use (optional)
22 # Style to use (optional)
23 #style = foo
23 #style = foo
24 # The URL of the CIA notification service (optional)
24 # The URL of the CIA notification service (optional)
25 # You can use mailto: URLs to send by email, eg
25 # You can use mailto: URLs to send by email, eg
26 # mailto:cia@cia.vc
26 # mailto:cia@cia.vc
27 # Make sure to set email.from if you do this.
27 # Make sure to set email.from if you do this.
28 #url = http://cia.vc/
28 #url = http://cia.vc/
29 # print message instead of sending it (optional)
29 # print message instead of sending it (optional)
30 #test = False
30 #test = False
31
31
32 [hooks]
32 [hooks]
33 # one of these:
33 # one of these:
34 changegroup.cia = python:hgcia.hook
34 changegroup.cia = python:hgcia.hook
35 #incoming.cia = python:hgcia.hook
35 #incoming.cia = python:hgcia.hook
36
36
37 [web]
37 [web]
38 # If you want hyperlinks (optional)
38 # If you want hyperlinks (optional)
39 baseurl = http://server/path/to/repo
39 baseurl = http://server/path/to/repo
40 """
40 """
41
41
42 from mercurial.i18n import _
42 from mercurial.i18n import _
43 from mercurial.node import bin, short
43 from mercurial.node import bin, short
44 from mercurial import cmdutil, patch, templater, util, mail
44 from mercurial import cmdutil, patch, templater, util, mail
45 import email.Parser
45 import email.Parser
46
46
47 import xmlrpclib
47 import xmlrpclib
48 from xml.sax import saxutils
48 from xml.sax import saxutils
49
49
50 socket_timeout = 30 # seconds
50 socket_timeout = 30 # seconds
51 try:
51 try:
52 # set a timeout for the socket so you don't have to wait so looooong
52 # set a timeout for the socket so you don't have to wait so looooong
53 # when cia.vc is having problems. requires python >= 2.3:
53 # when cia.vc is having problems. requires python >= 2.3:
54 import socket
54 import socket
55 socket.setdefaulttimeout(socket_timeout)
55 socket.setdefaulttimeout(socket_timeout)
56 except:
56 except:
57 pass
57 pass
58
58
59 HGCIA_VERSION = '0.1'
59 HGCIA_VERSION = '0.1'
60 HGCIA_URL = 'http://hg.kublai.com/mercurial/hgcia'
60 HGCIA_URL = 'http://hg.kublai.com/mercurial/hgcia'
61
61
62
62
63 class ciamsg(object):
63 class ciamsg(object):
64 """ A CIA message """
64 """ A CIA message """
65 def __init__(self, cia, ctx):
65 def __init__(self, cia, ctx):
66 self.cia = cia
66 self.cia = cia
67 self.ctx = ctx
67 self.ctx = ctx
68 self.url = self.cia.url
68 self.url = self.cia.url
69
69
70 def fileelem(self, path, uri, action):
70 def fileelem(self, path, uri, action):
71 if uri:
71 if uri:
72 uri = ' uri=%s' % saxutils.quoteattr(uri)
72 uri = ' uri=%s' % saxutils.quoteattr(uri)
73 return '<file%s action=%s>%s</file>' % (
73 return '<file%s action=%s>%s</file>' % (
74 uri, saxutils.quoteattr(action), saxutils.escape(path))
74 uri, saxutils.quoteattr(action), saxutils.escape(path))
75
75
76 def fileelems(self):
76 def fileelems(self):
77 n = self.ctx.node()
77 n = self.ctx.node()
78 f = self.cia.repo.status(self.ctx.parents()[0].node(), n)
78 f = self.cia.repo.status(self.ctx.p1().node(), n)
79 url = self.url or ''
79 url = self.url or ''
80 elems = []
80 elems = []
81 for path in f[0]:
81 for path in f[0]:
82 uri = '%s/diff/%s/%s' % (url, short(n), path)
82 uri = '%s/diff/%s/%s' % (url, short(n), path)
83 elems.append(self.fileelem(path, url and uri, 'modify'))
83 elems.append(self.fileelem(path, url and uri, 'modify'))
84 for path in f[1]:
84 for path in f[1]:
85 # TODO: copy/rename ?
85 # TODO: copy/rename ?
86 uri = '%s/file/%s/%s' % (url, short(n), path)
86 uri = '%s/file/%s/%s' % (url, short(n), path)
87 elems.append(self.fileelem(path, url and uri, 'add'))
87 elems.append(self.fileelem(path, url and uri, 'add'))
88 for path in f[2]:
88 for path in f[2]:
89 elems.append(self.fileelem(path, '', 'remove'))
89 elems.append(self.fileelem(path, '', 'remove'))
90
90
91 return '\n'.join(elems)
91 return '\n'.join(elems)
92
92
93 def sourceelem(self, project, module=None, branch=None):
93 def sourceelem(self, project, module=None, branch=None):
94 msg = ['<source>', '<project>%s</project>' % saxutils.escape(project)]
94 msg = ['<source>', '<project>%s</project>' % saxutils.escape(project)]
95 if module:
95 if module:
96 msg.append('<module>%s</module>' % saxutils.escape(module))
96 msg.append('<module>%s</module>' % saxutils.escape(module))
97 if branch:
97 if branch:
98 msg.append('<branch>%s</branch>' % saxutils.escape(branch))
98 msg.append('<branch>%s</branch>' % saxutils.escape(branch))
99 msg.append('</source>')
99 msg.append('</source>')
100
100
101 return '\n'.join(msg)
101 return '\n'.join(msg)
102
102
103 def diffstat(self):
103 def diffstat(self):
104 class patchbuf(object):
104 class patchbuf(object):
105 def __init__(self):
105 def __init__(self):
106 self.lines = []
106 self.lines = []
107 # diffstat is stupid
107 # diffstat is stupid
108 self.name = 'cia'
108 self.name = 'cia'
109 def write(self, data):
109 def write(self, data):
110 self.lines.append(data)
110 self.lines.append(data)
111 def close(self):
111 def close(self):
112 pass
112 pass
113
113
114 n = self.ctx.node()
114 n = self.ctx.node()
115 pbuf = patchbuf()
115 pbuf = patchbuf()
116 cmdutil.export(self.cia.repo, [n], fp=pbuf)
116 cmdutil.export(self.cia.repo, [n], fp=pbuf)
117 return patch.diffstat(pbuf.lines) or ''
117 return patch.diffstat(pbuf.lines) or ''
118
118
119 def logmsg(self):
119 def logmsg(self):
120 diffstat = self.cia.diffstat and self.diffstat() or ''
120 diffstat = self.cia.diffstat and self.diffstat() or ''
121 self.cia.ui.pushbuffer()
121 self.cia.ui.pushbuffer()
122 self.cia.templater.show(self.ctx, changes=self.ctx.changeset(),
122 self.cia.templater.show(self.ctx, changes=self.ctx.changeset(),
123 url=self.cia.url, diffstat=diffstat)
123 url=self.cia.url, diffstat=diffstat)
124 return self.cia.ui.popbuffer()
124 return self.cia.ui.popbuffer()
125
125
126 def xml(self):
126 def xml(self):
127 n = short(self.ctx.node())
127 n = short(self.ctx.node())
128 src = self.sourceelem(self.cia.project, module=self.cia.module,
128 src = self.sourceelem(self.cia.project, module=self.cia.module,
129 branch=self.ctx.branch())
129 branch=self.ctx.branch())
130 # unix timestamp
130 # unix timestamp
131 dt = self.ctx.date()
131 dt = self.ctx.date()
132 timestamp = dt[0]
132 timestamp = dt[0]
133
133
134 author = saxutils.escape(self.ctx.user())
134 author = saxutils.escape(self.ctx.user())
135 rev = '%d:%s' % (self.ctx.rev(), n)
135 rev = '%d:%s' % (self.ctx.rev(), n)
136 log = saxutils.escape(self.logmsg())
136 log = saxutils.escape(self.logmsg())
137
137
138 url = self.url and '<url>%s/rev/%s</url>' % (saxutils.escape(self.url),
138 url = self.url and '<url>%s/rev/%s</url>' % (saxutils.escape(self.url),
139 n) or ''
139 n) or ''
140
140
141 msg = """
141 msg = """
142 <message>
142 <message>
143 <generator>
143 <generator>
144 <name>Mercurial (hgcia)</name>
144 <name>Mercurial (hgcia)</name>
145 <version>%s</version>
145 <version>%s</version>
146 <url>%s</url>
146 <url>%s</url>
147 <user>%s</user>
147 <user>%s</user>
148 </generator>
148 </generator>
149 %s
149 %s
150 <body>
150 <body>
151 <commit>
151 <commit>
152 <author>%s</author>
152 <author>%s</author>
153 <version>%s</version>
153 <version>%s</version>
154 <log>%s</log>
154 <log>%s</log>
155 %s
155 %s
156 <files>%s</files>
156 <files>%s</files>
157 </commit>
157 </commit>
158 </body>
158 </body>
159 <timestamp>%d</timestamp>
159 <timestamp>%d</timestamp>
160 </message>
160 </message>
161 """ % \
161 """ % \
162 (HGCIA_VERSION, saxutils.escape(HGCIA_URL),
162 (HGCIA_VERSION, saxutils.escape(HGCIA_URL),
163 saxutils.escape(self.cia.user), src, author, rev, log, url,
163 saxutils.escape(self.cia.user), src, author, rev, log, url,
164 self.fileelems(), timestamp)
164 self.fileelems(), timestamp)
165
165
166 return msg
166 return msg
167
167
168
168
169 class hgcia(object):
169 class hgcia(object):
170 """ CIA notification class """
170 """ CIA notification class """
171
171
172 deftemplate = '{desc}'
172 deftemplate = '{desc}'
173 dstemplate = '{desc}\n-- \n{diffstat}'
173 dstemplate = '{desc}\n-- \n{diffstat}'
174
174
175 def __init__(self, ui, repo):
175 def __init__(self, ui, repo):
176 self.ui = ui
176 self.ui = ui
177 self.repo = repo
177 self.repo = repo
178
178
179 self.ciaurl = self.ui.config('cia', 'url', 'http://cia.vc')
179 self.ciaurl = self.ui.config('cia', 'url', 'http://cia.vc')
180 self.user = self.ui.config('cia', 'user')
180 self.user = self.ui.config('cia', 'user')
181 self.project = self.ui.config('cia', 'project')
181 self.project = self.ui.config('cia', 'project')
182 self.module = self.ui.config('cia', 'module')
182 self.module = self.ui.config('cia', 'module')
183 self.diffstat = self.ui.configbool('cia', 'diffstat')
183 self.diffstat = self.ui.configbool('cia', 'diffstat')
184 self.emailfrom = self.ui.config('email', 'from')
184 self.emailfrom = self.ui.config('email', 'from')
185 self.dryrun = self.ui.configbool('cia', 'test')
185 self.dryrun = self.ui.configbool('cia', 'test')
186 self.url = self.ui.config('web', 'baseurl')
186 self.url = self.ui.config('web', 'baseurl')
187
187
188 style = self.ui.config('cia', 'style')
188 style = self.ui.config('cia', 'style')
189 template = self.ui.config('cia', 'template')
189 template = self.ui.config('cia', 'template')
190 if not template:
190 if not template:
191 template = self.diffstat and self.dstemplate or self.deftemplate
191 template = self.diffstat and self.dstemplate or self.deftemplate
192 template = templater.parsestring(template, quoted=False)
192 template = templater.parsestring(template, quoted=False)
193 t = cmdutil.changeset_templater(self.ui, self.repo, False, None,
193 t = cmdutil.changeset_templater(self.ui, self.repo, False, None,
194 style, False)
194 style, False)
195 t.use_template(template)
195 t.use_template(template)
196 self.templater = t
196 self.templater = t
197
197
198 def sendrpc(self, msg):
198 def sendrpc(self, msg):
199 srv = xmlrpclib.Server(self.ciaurl)
199 srv = xmlrpclib.Server(self.ciaurl)
200 res = srv.hub.deliver(msg)
200 res = srv.hub.deliver(msg)
201 if res is not True and res != 'queued.':
201 if res is not True and res != 'queued.':
202 raise util.Abort(_('%s returned an error: %s') %
202 raise util.Abort(_('%s returned an error: %s') %
203 (self.ciaurl, res))
203 (self.ciaurl, res))
204
204
205 def sendemail(self, address, data):
205 def sendemail(self, address, data):
206 p = email.Parser.Parser()
206 p = email.Parser.Parser()
207 msg = p.parsestr(data)
207 msg = p.parsestr(data)
208 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
208 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
209 msg['To'] = address
209 msg['To'] = address
210 msg['From'] = self.emailfrom
210 msg['From'] = self.emailfrom
211 msg['Subject'] = 'DeliverXML'
211 msg['Subject'] = 'DeliverXML'
212 msg['Content-type'] = 'text/xml'
212 msg['Content-type'] = 'text/xml'
213 msgtext = msg.as_string()
213 msgtext = msg.as_string()
214
214
215 self.ui.status(_('hgcia: sending update to %s\n') % address)
215 self.ui.status(_('hgcia: sending update to %s\n') % address)
216 mail.sendmail(self.ui, util.email(self.emailfrom),
216 mail.sendmail(self.ui, util.email(self.emailfrom),
217 [address], msgtext)
217 [address], msgtext)
218
218
219
219
220 def hook(ui, repo, hooktype, node=None, url=None, **kwargs):
220 def hook(ui, repo, hooktype, node=None, url=None, **kwargs):
221 """ send CIA notification """
221 """ send CIA notification """
222 def sendmsg(cia, ctx):
222 def sendmsg(cia, ctx):
223 msg = ciamsg(cia, ctx).xml()
223 msg = ciamsg(cia, ctx).xml()
224 if cia.dryrun:
224 if cia.dryrun:
225 ui.write(msg)
225 ui.write(msg)
226 elif cia.ciaurl.startswith('mailto:'):
226 elif cia.ciaurl.startswith('mailto:'):
227 if not cia.emailfrom:
227 if not cia.emailfrom:
228 raise util.Abort(_('email.from must be defined when '
228 raise util.Abort(_('email.from must be defined when '
229 'sending by email'))
229 'sending by email'))
230 cia.sendemail(cia.ciaurl[7:], msg)
230 cia.sendemail(cia.ciaurl[7:], msg)
231 else:
231 else:
232 cia.sendrpc(msg)
232 cia.sendrpc(msg)
233
233
234 n = bin(node)
234 n = bin(node)
235 cia = hgcia(ui, repo)
235 cia = hgcia(ui, repo)
236 if not cia.user:
236 if not cia.user:
237 ui.debug('cia: no user specified')
237 ui.debug('cia: no user specified')
238 return
238 return
239 if not cia.project:
239 if not cia.project:
240 ui.debug('cia: no project specified')
240 ui.debug('cia: no project specified')
241 return
241 return
242 if hooktype == 'changegroup':
242 if hooktype == 'changegroup':
243 start = repo.changelog.rev(n)
243 start = repo.changelog.rev(n)
244 end = len(repo.changelog)
244 end = len(repo.changelog)
245 for rev in xrange(start, end):
245 for rev in xrange(start, end):
246 n = repo.changelog.node(rev)
246 n = repo.changelog.node(rev)
247 ctx = repo.changectx(n)
247 ctx = repo.changectx(n)
248 sendmsg(cia, ctx)
248 sendmsg(cia, ctx)
249 else:
249 else:
250 ctx = repo.changectx(n)
250 ctx = repo.changectx(n)
251 sendmsg(cia, ctx)
251 sendmsg(cia, ctx)
@@ -1,3273 +1,3273 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 You will by default be managing a patch queue named "patches". You can
41 You will by default be managing a patch queue named "patches". You can
42 create other, independent patch queues with the :hg:`qqueue` command.
42 create other, independent patch queues with the :hg:`qqueue` command.
43 '''
43 '''
44
44
45 from mercurial.i18n import _
45 from mercurial.i18n import _
46 from mercurial.node import bin, hex, short, nullid, nullrev
46 from mercurial.node import bin, hex, short, nullid, nullrev
47 from mercurial.lock import release
47 from mercurial.lock import release
48 from mercurial import commands, cmdutil, hg, patch, util
48 from mercurial import commands, cmdutil, hg, patch, util
49 from mercurial import repair, extensions, url, error
49 from mercurial import repair, extensions, url, error
50 import os, sys, re, errno, shutil
50 import os, sys, re, errno, shutil
51
51
52 commands.norepo += " qclone"
52 commands.norepo += " qclone"
53
53
54 # Patch names looks like unix-file names.
54 # Patch names looks like unix-file names.
55 # They must be joinable with queue directory and result in the patch path.
55 # They must be joinable with queue directory and result in the patch path.
56 normname = util.normpath
56 normname = util.normpath
57
57
58 class statusentry(object):
58 class statusentry(object):
59 def __init__(self, node, name):
59 def __init__(self, node, name):
60 self.node, self.name = node, name
60 self.node, self.name = node, name
61 def __repr__(self):
61 def __repr__(self):
62 return hex(self.node) + ':' + self.name
62 return hex(self.node) + ':' + self.name
63
63
64 class patchheader(object):
64 class patchheader(object):
65 def __init__(self, pf, plainmode=False):
65 def __init__(self, pf, plainmode=False):
66 def eatdiff(lines):
66 def eatdiff(lines):
67 while lines:
67 while lines:
68 l = lines[-1]
68 l = lines[-1]
69 if (l.startswith("diff -") or
69 if (l.startswith("diff -") or
70 l.startswith("Index:") or
70 l.startswith("Index:") or
71 l.startswith("===========")):
71 l.startswith("===========")):
72 del lines[-1]
72 del lines[-1]
73 else:
73 else:
74 break
74 break
75 def eatempty(lines):
75 def eatempty(lines):
76 while lines:
76 while lines:
77 if not lines[-1].strip():
77 if not lines[-1].strip():
78 del lines[-1]
78 del lines[-1]
79 else:
79 else:
80 break
80 break
81
81
82 message = []
82 message = []
83 comments = []
83 comments = []
84 user = None
84 user = None
85 date = None
85 date = None
86 parent = None
86 parent = None
87 format = None
87 format = None
88 subject = None
88 subject = None
89 branch = None
89 branch = None
90 nodeid = None
90 nodeid = None
91 diffstart = 0
91 diffstart = 0
92
92
93 for line in file(pf):
93 for line in file(pf):
94 line = line.rstrip()
94 line = line.rstrip()
95 if (line.startswith('diff --git')
95 if (line.startswith('diff --git')
96 or (diffstart and line.startswith('+++ '))):
96 or (diffstart and line.startswith('+++ '))):
97 diffstart = 2
97 diffstart = 2
98 break
98 break
99 diffstart = 0 # reset
99 diffstart = 0 # reset
100 if line.startswith("--- "):
100 if line.startswith("--- "):
101 diffstart = 1
101 diffstart = 1
102 continue
102 continue
103 elif format == "hgpatch":
103 elif format == "hgpatch":
104 # parse values when importing the result of an hg export
104 # parse values when importing the result of an hg export
105 if line.startswith("# User "):
105 if line.startswith("# User "):
106 user = line[7:]
106 user = line[7:]
107 elif line.startswith("# Date "):
107 elif line.startswith("# Date "):
108 date = line[7:]
108 date = line[7:]
109 elif line.startswith("# Parent "):
109 elif line.startswith("# Parent "):
110 parent = line[9:]
110 parent = line[9:]
111 elif line.startswith("# Branch "):
111 elif line.startswith("# Branch "):
112 branch = line[9:]
112 branch = line[9:]
113 elif line.startswith("# Node ID "):
113 elif line.startswith("# Node ID "):
114 nodeid = line[10:]
114 nodeid = line[10:]
115 elif not line.startswith("# ") and line:
115 elif not line.startswith("# ") and line:
116 message.append(line)
116 message.append(line)
117 format = None
117 format = None
118 elif line == '# HG changeset patch':
118 elif line == '# HG changeset patch':
119 message = []
119 message = []
120 format = "hgpatch"
120 format = "hgpatch"
121 elif (format != "tagdone" and (line.startswith("Subject: ") or
121 elif (format != "tagdone" and (line.startswith("Subject: ") or
122 line.startswith("subject: "))):
122 line.startswith("subject: "))):
123 subject = line[9:]
123 subject = line[9:]
124 format = "tag"
124 format = "tag"
125 elif (format != "tagdone" and (line.startswith("From: ") or
125 elif (format != "tagdone" and (line.startswith("From: ") or
126 line.startswith("from: "))):
126 line.startswith("from: "))):
127 user = line[6:]
127 user = line[6:]
128 format = "tag"
128 format = "tag"
129 elif (format != "tagdone" and (line.startswith("Date: ") or
129 elif (format != "tagdone" and (line.startswith("Date: ") or
130 line.startswith("date: "))):
130 line.startswith("date: "))):
131 date = line[6:]
131 date = line[6:]
132 format = "tag"
132 format = "tag"
133 elif format == "tag" and line == "":
133 elif format == "tag" and line == "":
134 # when looking for tags (subject: from: etc) they
134 # when looking for tags (subject: from: etc) they
135 # end once you find a blank line in the source
135 # end once you find a blank line in the source
136 format = "tagdone"
136 format = "tagdone"
137 elif message or line:
137 elif message or line:
138 message.append(line)
138 message.append(line)
139 comments.append(line)
139 comments.append(line)
140
140
141 eatdiff(message)
141 eatdiff(message)
142 eatdiff(comments)
142 eatdiff(comments)
143 # Remember the exact starting line of the patch diffs before consuming
143 # Remember the exact starting line of the patch diffs before consuming
144 # empty lines, for external use by TortoiseHg and others
144 # empty lines, for external use by TortoiseHg and others
145 self.diffstartline = len(comments)
145 self.diffstartline = len(comments)
146 eatempty(message)
146 eatempty(message)
147 eatempty(comments)
147 eatempty(comments)
148
148
149 # make sure message isn't empty
149 # make sure message isn't empty
150 if format and format.startswith("tag") and subject:
150 if format and format.startswith("tag") and subject:
151 message.insert(0, "")
151 message.insert(0, "")
152 message.insert(0, subject)
152 message.insert(0, subject)
153
153
154 self.message = message
154 self.message = message
155 self.comments = comments
155 self.comments = comments
156 self.user = user
156 self.user = user
157 self.date = date
157 self.date = date
158 self.parent = parent
158 self.parent = parent
159 # nodeid and branch are for external use by TortoiseHg and others
159 # nodeid and branch are for external use by TortoiseHg and others
160 self.nodeid = nodeid
160 self.nodeid = nodeid
161 self.branch = branch
161 self.branch = branch
162 self.haspatch = diffstart > 1
162 self.haspatch = diffstart > 1
163 self.plainmode = plainmode
163 self.plainmode = plainmode
164
164
165 def setuser(self, user):
165 def setuser(self, user):
166 if not self.updateheader(['From: ', '# User '], user):
166 if not self.updateheader(['From: ', '# User '], user):
167 try:
167 try:
168 patchheaderat = self.comments.index('# HG changeset patch')
168 patchheaderat = self.comments.index('# HG changeset patch')
169 self.comments.insert(patchheaderat + 1, '# User ' + user)
169 self.comments.insert(patchheaderat + 1, '# User ' + user)
170 except ValueError:
170 except ValueError:
171 if self.plainmode or self._hasheader(['Date: ']):
171 if self.plainmode or self._hasheader(['Date: ']):
172 self.comments = ['From: ' + user] + self.comments
172 self.comments = ['From: ' + user] + self.comments
173 else:
173 else:
174 tmp = ['# HG changeset patch', '# User ' + user, '']
174 tmp = ['# HG changeset patch', '# User ' + user, '']
175 self.comments = tmp + self.comments
175 self.comments = tmp + self.comments
176 self.user = user
176 self.user = user
177
177
178 def setdate(self, date):
178 def setdate(self, date):
179 if not self.updateheader(['Date: ', '# Date '], date):
179 if not self.updateheader(['Date: ', '# Date '], date):
180 try:
180 try:
181 patchheaderat = self.comments.index('# HG changeset patch')
181 patchheaderat = self.comments.index('# HG changeset patch')
182 self.comments.insert(patchheaderat + 1, '# Date ' + date)
182 self.comments.insert(patchheaderat + 1, '# Date ' + date)
183 except ValueError:
183 except ValueError:
184 if self.plainmode or self._hasheader(['From: ']):
184 if self.plainmode or self._hasheader(['From: ']):
185 self.comments = ['Date: ' + date] + self.comments
185 self.comments = ['Date: ' + date] + self.comments
186 else:
186 else:
187 tmp = ['# HG changeset patch', '# Date ' + date, '']
187 tmp = ['# HG changeset patch', '# Date ' + date, '']
188 self.comments = tmp + self.comments
188 self.comments = tmp + self.comments
189 self.date = date
189 self.date = date
190
190
191 def setparent(self, parent):
191 def setparent(self, parent):
192 if not self.updateheader(['# Parent '], parent):
192 if not self.updateheader(['# Parent '], parent):
193 try:
193 try:
194 patchheaderat = self.comments.index('# HG changeset patch')
194 patchheaderat = self.comments.index('# HG changeset patch')
195 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
195 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
196 except ValueError:
196 except ValueError:
197 pass
197 pass
198 self.parent = parent
198 self.parent = parent
199
199
200 def setmessage(self, message):
200 def setmessage(self, message):
201 if self.comments:
201 if self.comments:
202 self._delmsg()
202 self._delmsg()
203 self.message = [message]
203 self.message = [message]
204 self.comments += self.message
204 self.comments += self.message
205
205
206 def updateheader(self, prefixes, new):
206 def updateheader(self, prefixes, new):
207 '''Update all references to a field in the patch header.
207 '''Update all references to a field in the patch header.
208 Return whether the field is present.'''
208 Return whether the field is present.'''
209 res = False
209 res = False
210 for prefix in prefixes:
210 for prefix in prefixes:
211 for i in xrange(len(self.comments)):
211 for i in xrange(len(self.comments)):
212 if self.comments[i].startswith(prefix):
212 if self.comments[i].startswith(prefix):
213 self.comments[i] = prefix + new
213 self.comments[i] = prefix + new
214 res = True
214 res = True
215 break
215 break
216 return res
216 return res
217
217
218 def _hasheader(self, prefixes):
218 def _hasheader(self, prefixes):
219 '''Check if a header starts with any of the given prefixes.'''
219 '''Check if a header starts with any of the given prefixes.'''
220 for prefix in prefixes:
220 for prefix in prefixes:
221 for comment in self.comments:
221 for comment in self.comments:
222 if comment.startswith(prefix):
222 if comment.startswith(prefix):
223 return True
223 return True
224 return False
224 return False
225
225
226 def __str__(self):
226 def __str__(self):
227 if not self.comments:
227 if not self.comments:
228 return ''
228 return ''
229 return '\n'.join(self.comments) + '\n\n'
229 return '\n'.join(self.comments) + '\n\n'
230
230
231 def _delmsg(self):
231 def _delmsg(self):
232 '''Remove existing message, keeping the rest of the comments fields.
232 '''Remove existing message, keeping the rest of the comments fields.
233 If comments contains 'subject: ', message will prepend
233 If comments contains 'subject: ', message will prepend
234 the field and a blank line.'''
234 the field and a blank line.'''
235 if self.message:
235 if self.message:
236 subj = 'subject: ' + self.message[0].lower()
236 subj = 'subject: ' + self.message[0].lower()
237 for i in xrange(len(self.comments)):
237 for i in xrange(len(self.comments)):
238 if subj == self.comments[i].lower():
238 if subj == self.comments[i].lower():
239 del self.comments[i]
239 del self.comments[i]
240 self.message = self.message[2:]
240 self.message = self.message[2:]
241 break
241 break
242 ci = 0
242 ci = 0
243 for mi in self.message:
243 for mi in self.message:
244 while mi != self.comments[ci]:
244 while mi != self.comments[ci]:
245 ci += 1
245 ci += 1
246 del self.comments[ci]
246 del self.comments[ci]
247
247
248 class queue(object):
248 class queue(object):
249 def __init__(self, ui, path, patchdir=None):
249 def __init__(self, ui, path, patchdir=None):
250 self.basepath = path
250 self.basepath = path
251 try:
251 try:
252 fh = open(os.path.join(path, 'patches.queue'))
252 fh = open(os.path.join(path, 'patches.queue'))
253 cur = fh.read().rstrip()
253 cur = fh.read().rstrip()
254 fh.close()
254 fh.close()
255 if not cur:
255 if not cur:
256 curpath = os.path.join(path, 'patches')
256 curpath = os.path.join(path, 'patches')
257 else:
257 else:
258 curpath = os.path.join(path, 'patches-' + cur)
258 curpath = os.path.join(path, 'patches-' + cur)
259 except IOError:
259 except IOError:
260 curpath = os.path.join(path, 'patches')
260 curpath = os.path.join(path, 'patches')
261 self.path = patchdir or curpath
261 self.path = patchdir or curpath
262 self.opener = util.opener(self.path)
262 self.opener = util.opener(self.path)
263 self.ui = ui
263 self.ui = ui
264 self.applied_dirty = 0
264 self.applied_dirty = 0
265 self.series_dirty = 0
265 self.series_dirty = 0
266 self.added = []
266 self.added = []
267 self.series_path = "series"
267 self.series_path = "series"
268 self.status_path = "status"
268 self.status_path = "status"
269 self.guards_path = "guards"
269 self.guards_path = "guards"
270 self.active_guards = None
270 self.active_guards = None
271 self.guards_dirty = False
271 self.guards_dirty = False
272 # Handle mq.git as a bool with extended values
272 # Handle mq.git as a bool with extended values
273 try:
273 try:
274 gitmode = ui.configbool('mq', 'git', None)
274 gitmode = ui.configbool('mq', 'git', None)
275 if gitmode is None:
275 if gitmode is None:
276 raise error.ConfigError()
276 raise error.ConfigError()
277 self.gitmode = gitmode and 'yes' or 'no'
277 self.gitmode = gitmode and 'yes' or 'no'
278 except error.ConfigError:
278 except error.ConfigError:
279 self.gitmode = ui.config('mq', 'git', 'auto').lower()
279 self.gitmode = ui.config('mq', 'git', 'auto').lower()
280 self.plainmode = ui.configbool('mq', 'plain', False)
280 self.plainmode = ui.configbool('mq', 'plain', False)
281
281
282 @util.propertycache
282 @util.propertycache
283 def applied(self):
283 def applied(self):
284 if os.path.exists(self.join(self.status_path)):
284 if os.path.exists(self.join(self.status_path)):
285 def parselines(lines):
285 def parselines(lines):
286 for l in lines:
286 for l in lines:
287 entry = l.split(':', 1)
287 entry = l.split(':', 1)
288 if len(entry) > 1:
288 if len(entry) > 1:
289 n, name = entry
289 n, name = entry
290 yield statusentry(bin(n), name)
290 yield statusentry(bin(n), name)
291 elif l.strip():
291 elif l.strip():
292 self.ui.warn(_('malformated mq status line: %s\n') % entry)
292 self.ui.warn(_('malformated mq status line: %s\n') % entry)
293 # else we ignore empty lines
293 # else we ignore empty lines
294 lines = self.opener(self.status_path).read().splitlines()
294 lines = self.opener(self.status_path).read().splitlines()
295 return list(parselines(lines))
295 return list(parselines(lines))
296 return []
296 return []
297
297
298 @util.propertycache
298 @util.propertycache
299 def full_series(self):
299 def full_series(self):
300 if os.path.exists(self.join(self.series_path)):
300 if os.path.exists(self.join(self.series_path)):
301 return self.opener(self.series_path).read().splitlines()
301 return self.opener(self.series_path).read().splitlines()
302 return []
302 return []
303
303
304 @util.propertycache
304 @util.propertycache
305 def series(self):
305 def series(self):
306 self.parse_series()
306 self.parse_series()
307 return self.series
307 return self.series
308
308
309 @util.propertycache
309 @util.propertycache
310 def series_guards(self):
310 def series_guards(self):
311 self.parse_series()
311 self.parse_series()
312 return self.series_guards
312 return self.series_guards
313
313
314 def invalidate(self):
314 def invalidate(self):
315 for a in 'applied full_series series series_guards'.split():
315 for a in 'applied full_series series series_guards'.split():
316 if a in self.__dict__:
316 if a in self.__dict__:
317 delattr(self, a)
317 delattr(self, a)
318 self.applied_dirty = 0
318 self.applied_dirty = 0
319 self.series_dirty = 0
319 self.series_dirty = 0
320 self.guards_dirty = False
320 self.guards_dirty = False
321 self.active_guards = None
321 self.active_guards = None
322
322
323 def diffopts(self, opts={}, patchfn=None):
323 def diffopts(self, opts={}, patchfn=None):
324 diffopts = patch.diffopts(self.ui, opts)
324 diffopts = patch.diffopts(self.ui, opts)
325 if self.gitmode == 'auto':
325 if self.gitmode == 'auto':
326 diffopts.upgrade = True
326 diffopts.upgrade = True
327 elif self.gitmode == 'keep':
327 elif self.gitmode == 'keep':
328 pass
328 pass
329 elif self.gitmode in ('yes', 'no'):
329 elif self.gitmode in ('yes', 'no'):
330 diffopts.git = self.gitmode == 'yes'
330 diffopts.git = self.gitmode == 'yes'
331 else:
331 else:
332 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
332 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
333 ' got %s') % self.gitmode)
333 ' got %s') % self.gitmode)
334 if patchfn:
334 if patchfn:
335 diffopts = self.patchopts(diffopts, patchfn)
335 diffopts = self.patchopts(diffopts, patchfn)
336 return diffopts
336 return diffopts
337
337
338 def patchopts(self, diffopts, *patches):
338 def patchopts(self, diffopts, *patches):
339 """Return a copy of input diff options with git set to true if
339 """Return a copy of input diff options with git set to true if
340 referenced patch is a git patch and should be preserved as such.
340 referenced patch is a git patch and should be preserved as such.
341 """
341 """
342 diffopts = diffopts.copy()
342 diffopts = diffopts.copy()
343 if not diffopts.git and self.gitmode == 'keep':
343 if not diffopts.git and self.gitmode == 'keep':
344 for patchfn in patches:
344 for patchfn in patches:
345 patchf = self.opener(patchfn, 'r')
345 patchf = self.opener(patchfn, 'r')
346 # if the patch was a git patch, refresh it as a git patch
346 # if the patch was a git patch, refresh it as a git patch
347 for line in patchf:
347 for line in patchf:
348 if line.startswith('diff --git'):
348 if line.startswith('diff --git'):
349 diffopts.git = True
349 diffopts.git = True
350 break
350 break
351 patchf.close()
351 patchf.close()
352 return diffopts
352 return diffopts
353
353
354 def join(self, *p):
354 def join(self, *p):
355 return os.path.join(self.path, *p)
355 return os.path.join(self.path, *p)
356
356
357 def find_series(self, patch):
357 def find_series(self, patch):
358 def matchpatch(l):
358 def matchpatch(l):
359 l = l.split('#', 1)[0]
359 l = l.split('#', 1)[0]
360 return l.strip() == patch
360 return l.strip() == patch
361 for index, l in enumerate(self.full_series):
361 for index, l in enumerate(self.full_series):
362 if matchpatch(l):
362 if matchpatch(l):
363 return index
363 return index
364 return None
364 return None
365
365
366 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
366 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
367
367
368 def parse_series(self):
368 def parse_series(self):
369 self.series = []
369 self.series = []
370 self.series_guards = []
370 self.series_guards = []
371 for l in self.full_series:
371 for l in self.full_series:
372 h = l.find('#')
372 h = l.find('#')
373 if h == -1:
373 if h == -1:
374 patch = l
374 patch = l
375 comment = ''
375 comment = ''
376 elif h == 0:
376 elif h == 0:
377 continue
377 continue
378 else:
378 else:
379 patch = l[:h]
379 patch = l[:h]
380 comment = l[h:]
380 comment = l[h:]
381 patch = patch.strip()
381 patch = patch.strip()
382 if patch:
382 if patch:
383 if patch in self.series:
383 if patch in self.series:
384 raise util.Abort(_('%s appears more than once in %s') %
384 raise util.Abort(_('%s appears more than once in %s') %
385 (patch, self.join(self.series_path)))
385 (patch, self.join(self.series_path)))
386 self.series.append(patch)
386 self.series.append(patch)
387 self.series_guards.append(self.guard_re.findall(comment))
387 self.series_guards.append(self.guard_re.findall(comment))
388
388
389 def check_guard(self, guard):
389 def check_guard(self, guard):
390 if not guard:
390 if not guard:
391 return _('guard cannot be an empty string')
391 return _('guard cannot be an empty string')
392 bad_chars = '# \t\r\n\f'
392 bad_chars = '# \t\r\n\f'
393 first = guard[0]
393 first = guard[0]
394 if first in '-+':
394 if first in '-+':
395 return (_('guard %r starts with invalid character: %r') %
395 return (_('guard %r starts with invalid character: %r') %
396 (guard, first))
396 (guard, first))
397 for c in bad_chars:
397 for c in bad_chars:
398 if c in guard:
398 if c in guard:
399 return _('invalid character in guard %r: %r') % (guard, c)
399 return _('invalid character in guard %r: %r') % (guard, c)
400
400
401 def set_active(self, guards):
401 def set_active(self, guards):
402 for guard in guards:
402 for guard in guards:
403 bad = self.check_guard(guard)
403 bad = self.check_guard(guard)
404 if bad:
404 if bad:
405 raise util.Abort(bad)
405 raise util.Abort(bad)
406 guards = sorted(set(guards))
406 guards = sorted(set(guards))
407 self.ui.debug('active guards: %s\n' % ' '.join(guards))
407 self.ui.debug('active guards: %s\n' % ' '.join(guards))
408 self.active_guards = guards
408 self.active_guards = guards
409 self.guards_dirty = True
409 self.guards_dirty = True
410
410
411 def active(self):
411 def active(self):
412 if self.active_guards is None:
412 if self.active_guards is None:
413 self.active_guards = []
413 self.active_guards = []
414 try:
414 try:
415 guards = self.opener(self.guards_path).read().split()
415 guards = self.opener(self.guards_path).read().split()
416 except IOError, err:
416 except IOError, err:
417 if err.errno != errno.ENOENT:
417 if err.errno != errno.ENOENT:
418 raise
418 raise
419 guards = []
419 guards = []
420 for i, guard in enumerate(guards):
420 for i, guard in enumerate(guards):
421 bad = self.check_guard(guard)
421 bad = self.check_guard(guard)
422 if bad:
422 if bad:
423 self.ui.warn('%s:%d: %s\n' %
423 self.ui.warn('%s:%d: %s\n' %
424 (self.join(self.guards_path), i + 1, bad))
424 (self.join(self.guards_path), i + 1, bad))
425 else:
425 else:
426 self.active_guards.append(guard)
426 self.active_guards.append(guard)
427 return self.active_guards
427 return self.active_guards
428
428
429 def set_guards(self, idx, guards):
429 def set_guards(self, idx, guards):
430 for g in guards:
430 for g in guards:
431 if len(g) < 2:
431 if len(g) < 2:
432 raise util.Abort(_('guard %r too short') % g)
432 raise util.Abort(_('guard %r too short') % g)
433 if g[0] not in '-+':
433 if g[0] not in '-+':
434 raise util.Abort(_('guard %r starts with invalid char') % g)
434 raise util.Abort(_('guard %r starts with invalid char') % g)
435 bad = self.check_guard(g[1:])
435 bad = self.check_guard(g[1:])
436 if bad:
436 if bad:
437 raise util.Abort(bad)
437 raise util.Abort(bad)
438 drop = self.guard_re.sub('', self.full_series[idx])
438 drop = self.guard_re.sub('', self.full_series[idx])
439 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
439 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
440 self.parse_series()
440 self.parse_series()
441 self.series_dirty = True
441 self.series_dirty = True
442
442
443 def pushable(self, idx):
443 def pushable(self, idx):
444 if isinstance(idx, str):
444 if isinstance(idx, str):
445 idx = self.series.index(idx)
445 idx = self.series.index(idx)
446 patchguards = self.series_guards[idx]
446 patchguards = self.series_guards[idx]
447 if not patchguards:
447 if not patchguards:
448 return True, None
448 return True, None
449 guards = self.active()
449 guards = self.active()
450 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
450 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
451 if exactneg:
451 if exactneg:
452 return False, exactneg[0]
452 return False, exactneg[0]
453 pos = [g for g in patchguards if g[0] == '+']
453 pos = [g for g in patchguards if g[0] == '+']
454 exactpos = [g for g in pos if g[1:] in guards]
454 exactpos = [g for g in pos if g[1:] in guards]
455 if pos:
455 if pos:
456 if exactpos:
456 if exactpos:
457 return True, exactpos[0]
457 return True, exactpos[0]
458 return False, pos
458 return False, pos
459 return True, ''
459 return True, ''
460
460
461 def explain_pushable(self, idx, all_patches=False):
461 def explain_pushable(self, idx, all_patches=False):
462 write = all_patches and self.ui.write or self.ui.warn
462 write = all_patches and self.ui.write or self.ui.warn
463 if all_patches or self.ui.verbose:
463 if all_patches or self.ui.verbose:
464 if isinstance(idx, str):
464 if isinstance(idx, str):
465 idx = self.series.index(idx)
465 idx = self.series.index(idx)
466 pushable, why = self.pushable(idx)
466 pushable, why = self.pushable(idx)
467 if all_patches and pushable:
467 if all_patches and pushable:
468 if why is None:
468 if why is None:
469 write(_('allowing %s - no guards in effect\n') %
469 write(_('allowing %s - no guards in effect\n') %
470 self.series[idx])
470 self.series[idx])
471 else:
471 else:
472 if not why:
472 if not why:
473 write(_('allowing %s - no matching negative guards\n') %
473 write(_('allowing %s - no matching negative guards\n') %
474 self.series[idx])
474 self.series[idx])
475 else:
475 else:
476 write(_('allowing %s - guarded by %r\n') %
476 write(_('allowing %s - guarded by %r\n') %
477 (self.series[idx], why))
477 (self.series[idx], why))
478 if not pushable:
478 if not pushable:
479 if why:
479 if why:
480 write(_('skipping %s - guarded by %r\n') %
480 write(_('skipping %s - guarded by %r\n') %
481 (self.series[idx], why))
481 (self.series[idx], why))
482 else:
482 else:
483 write(_('skipping %s - no matching guards\n') %
483 write(_('skipping %s - no matching guards\n') %
484 self.series[idx])
484 self.series[idx])
485
485
486 def save_dirty(self):
486 def save_dirty(self):
487 def write_list(items, path):
487 def write_list(items, path):
488 fp = self.opener(path, 'w')
488 fp = self.opener(path, 'w')
489 for i in items:
489 for i in items:
490 fp.write("%s\n" % i)
490 fp.write("%s\n" % i)
491 fp.close()
491 fp.close()
492 if self.applied_dirty:
492 if self.applied_dirty:
493 write_list(map(str, self.applied), self.status_path)
493 write_list(map(str, self.applied), self.status_path)
494 if self.series_dirty:
494 if self.series_dirty:
495 write_list(self.full_series, self.series_path)
495 write_list(self.full_series, self.series_path)
496 if self.guards_dirty:
496 if self.guards_dirty:
497 write_list(self.active_guards, self.guards_path)
497 write_list(self.active_guards, self.guards_path)
498 if self.added:
498 if self.added:
499 qrepo = self.qrepo()
499 qrepo = self.qrepo()
500 if qrepo:
500 if qrepo:
501 qrepo[None].add(f for f in self.added if f not in qrepo[None])
501 qrepo[None].add(f for f in self.added if f not in qrepo[None])
502 self.added = []
502 self.added = []
503
503
504 def removeundo(self, repo):
504 def removeundo(self, repo):
505 undo = repo.sjoin('undo')
505 undo = repo.sjoin('undo')
506 if not os.path.exists(undo):
506 if not os.path.exists(undo):
507 return
507 return
508 try:
508 try:
509 os.unlink(undo)
509 os.unlink(undo)
510 except OSError, inst:
510 except OSError, inst:
511 self.ui.warn(_('error removing undo: %s\n') % str(inst))
511 self.ui.warn(_('error removing undo: %s\n') % str(inst))
512
512
513 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
513 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
514 fp=None, changes=None, opts={}):
514 fp=None, changes=None, opts={}):
515 stat = opts.get('stat')
515 stat = opts.get('stat')
516 m = cmdutil.match(repo, files, opts)
516 m = cmdutil.match(repo, files, opts)
517 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
517 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
518 changes, stat, fp)
518 changes, stat, fp)
519
519
520 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
520 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
521 # first try just applying the patch
521 # first try just applying the patch
522 (err, n) = self.apply(repo, [patch], update_status=False,
522 (err, n) = self.apply(repo, [patch], update_status=False,
523 strict=True, merge=rev)
523 strict=True, merge=rev)
524
524
525 if err == 0:
525 if err == 0:
526 return (err, n)
526 return (err, n)
527
527
528 if n is None:
528 if n is None:
529 raise util.Abort(_("apply failed for patch %s") % patch)
529 raise util.Abort(_("apply failed for patch %s") % patch)
530
530
531 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
531 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
532
532
533 # apply failed, strip away that rev and merge.
533 # apply failed, strip away that rev and merge.
534 hg.clean(repo, head)
534 hg.clean(repo, head)
535 self.strip(repo, [n], update=False, backup='strip')
535 self.strip(repo, [n], update=False, backup='strip')
536
536
537 ctx = repo[rev]
537 ctx = repo[rev]
538 ret = hg.merge(repo, rev)
538 ret = hg.merge(repo, rev)
539 if ret:
539 if ret:
540 raise util.Abort(_("update returned %d") % ret)
540 raise util.Abort(_("update returned %d") % ret)
541 n = repo.commit(ctx.description(), ctx.user(), force=True)
541 n = repo.commit(ctx.description(), ctx.user(), force=True)
542 if n is None:
542 if n is None:
543 raise util.Abort(_("repo commit failed"))
543 raise util.Abort(_("repo commit failed"))
544 try:
544 try:
545 ph = patchheader(mergeq.join(patch), self.plainmode)
545 ph = patchheader(mergeq.join(patch), self.plainmode)
546 except:
546 except:
547 raise util.Abort(_("unable to read %s") % patch)
547 raise util.Abort(_("unable to read %s") % patch)
548
548
549 diffopts = self.patchopts(diffopts, patch)
549 diffopts = self.patchopts(diffopts, patch)
550 patchf = self.opener(patch, "w")
550 patchf = self.opener(patch, "w")
551 comments = str(ph)
551 comments = str(ph)
552 if comments:
552 if comments:
553 patchf.write(comments)
553 patchf.write(comments)
554 self.printdiff(repo, diffopts, head, n, fp=patchf)
554 self.printdiff(repo, diffopts, head, n, fp=patchf)
555 patchf.close()
555 patchf.close()
556 self.removeundo(repo)
556 self.removeundo(repo)
557 return (0, n)
557 return (0, n)
558
558
559 def qparents(self, repo, rev=None):
559 def qparents(self, repo, rev=None):
560 if rev is None:
560 if rev is None:
561 (p1, p2) = repo.dirstate.parents()
561 (p1, p2) = repo.dirstate.parents()
562 if p2 == nullid:
562 if p2 == nullid:
563 return p1
563 return p1
564 if not self.applied:
564 if not self.applied:
565 return None
565 return None
566 return self.applied[-1].node
566 return self.applied[-1].node
567 p1, p2 = repo.changelog.parents(rev)
567 p1, p2 = repo.changelog.parents(rev)
568 if p2 != nullid and p2 in [x.node for x in self.applied]:
568 if p2 != nullid and p2 in [x.node for x in self.applied]:
569 return p2
569 return p2
570 return p1
570 return p1
571
571
572 def mergepatch(self, repo, mergeq, series, diffopts):
572 def mergepatch(self, repo, mergeq, series, diffopts):
573 if not self.applied:
573 if not self.applied:
574 # each of the patches merged in will have two parents. This
574 # each of the patches merged in will have two parents. This
575 # can confuse the qrefresh, qdiff, and strip code because it
575 # can confuse the qrefresh, qdiff, and strip code because it
576 # needs to know which parent is actually in the patch queue.
576 # needs to know which parent is actually in the patch queue.
577 # so, we insert a merge marker with only one parent. This way
577 # so, we insert a merge marker with only one parent. This way
578 # the first patch in the queue is never a merge patch
578 # the first patch in the queue is never a merge patch
579 #
579 #
580 pname = ".hg.patches.merge.marker"
580 pname = ".hg.patches.merge.marker"
581 n = repo.commit('[mq]: merge marker', force=True)
581 n = repo.commit('[mq]: merge marker', force=True)
582 self.removeundo(repo)
582 self.removeundo(repo)
583 self.applied.append(statusentry(n, pname))
583 self.applied.append(statusentry(n, pname))
584 self.applied_dirty = 1
584 self.applied_dirty = 1
585
585
586 head = self.qparents(repo)
586 head = self.qparents(repo)
587
587
588 for patch in series:
588 for patch in series:
589 patch = mergeq.lookup(patch, strict=True)
589 patch = mergeq.lookup(patch, strict=True)
590 if not patch:
590 if not patch:
591 self.ui.warn(_("patch %s does not exist\n") % patch)
591 self.ui.warn(_("patch %s does not exist\n") % patch)
592 return (1, None)
592 return (1, None)
593 pushable, reason = self.pushable(patch)
593 pushable, reason = self.pushable(patch)
594 if not pushable:
594 if not pushable:
595 self.explain_pushable(patch, all_patches=True)
595 self.explain_pushable(patch, all_patches=True)
596 continue
596 continue
597 info = mergeq.isapplied(patch)
597 info = mergeq.isapplied(patch)
598 if not info:
598 if not info:
599 self.ui.warn(_("patch %s is not applied\n") % patch)
599 self.ui.warn(_("patch %s is not applied\n") % patch)
600 return (1, None)
600 return (1, None)
601 rev = info[1]
601 rev = info[1]
602 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
602 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
603 if head:
603 if head:
604 self.applied.append(statusentry(head, patch))
604 self.applied.append(statusentry(head, patch))
605 self.applied_dirty = 1
605 self.applied_dirty = 1
606 if err:
606 if err:
607 return (err, head)
607 return (err, head)
608 self.save_dirty()
608 self.save_dirty()
609 return (0, head)
609 return (0, head)
610
610
611 def patch(self, repo, patchfile):
611 def patch(self, repo, patchfile):
612 '''Apply patchfile to the working directory.
612 '''Apply patchfile to the working directory.
613 patchfile: name of patch file'''
613 patchfile: name of patch file'''
614 files = {}
614 files = {}
615 try:
615 try:
616 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
616 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
617 files=files, eolmode=None)
617 files=files, eolmode=None)
618 except Exception, inst:
618 except Exception, inst:
619 self.ui.note(str(inst) + '\n')
619 self.ui.note(str(inst) + '\n')
620 if not self.ui.verbose:
620 if not self.ui.verbose:
621 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
621 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
622 return (False, files, False)
622 return (False, files, False)
623
623
624 return (True, files, fuzz)
624 return (True, files, fuzz)
625
625
626 def apply(self, repo, series, list=False, update_status=True,
626 def apply(self, repo, series, list=False, update_status=True,
627 strict=False, patchdir=None, merge=None, all_files=None):
627 strict=False, patchdir=None, merge=None, all_files=None):
628 wlock = lock = tr = None
628 wlock = lock = tr = None
629 try:
629 try:
630 wlock = repo.wlock()
630 wlock = repo.wlock()
631 lock = repo.lock()
631 lock = repo.lock()
632 tr = repo.transaction("qpush")
632 tr = repo.transaction("qpush")
633 try:
633 try:
634 ret = self._apply(repo, series, list, update_status,
634 ret = self._apply(repo, series, list, update_status,
635 strict, patchdir, merge, all_files=all_files)
635 strict, patchdir, merge, all_files=all_files)
636 tr.close()
636 tr.close()
637 self.save_dirty()
637 self.save_dirty()
638 return ret
638 return ret
639 except:
639 except:
640 try:
640 try:
641 tr.abort()
641 tr.abort()
642 finally:
642 finally:
643 repo.invalidate()
643 repo.invalidate()
644 repo.dirstate.invalidate()
644 repo.dirstate.invalidate()
645 raise
645 raise
646 finally:
646 finally:
647 release(tr, lock, wlock)
647 release(tr, lock, wlock)
648 self.removeundo(repo)
648 self.removeundo(repo)
649
649
650 def _apply(self, repo, series, list=False, update_status=True,
650 def _apply(self, repo, series, list=False, update_status=True,
651 strict=False, patchdir=None, merge=None, all_files=None):
651 strict=False, patchdir=None, merge=None, all_files=None):
652 '''returns (error, hash)
652 '''returns (error, hash)
653 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
653 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
654 # TODO unify with commands.py
654 # TODO unify with commands.py
655 if not patchdir:
655 if not patchdir:
656 patchdir = self.path
656 patchdir = self.path
657 err = 0
657 err = 0
658 n = None
658 n = None
659 for patchname in series:
659 for patchname in series:
660 pushable, reason = self.pushable(patchname)
660 pushable, reason = self.pushable(patchname)
661 if not pushable:
661 if not pushable:
662 self.explain_pushable(patchname, all_patches=True)
662 self.explain_pushable(patchname, all_patches=True)
663 continue
663 continue
664 self.ui.status(_("applying %s\n") % patchname)
664 self.ui.status(_("applying %s\n") % patchname)
665 pf = os.path.join(patchdir, patchname)
665 pf = os.path.join(patchdir, patchname)
666
666
667 try:
667 try:
668 ph = patchheader(self.join(patchname), self.plainmode)
668 ph = patchheader(self.join(patchname), self.plainmode)
669 except:
669 except:
670 self.ui.warn(_("unable to read %s\n") % patchname)
670 self.ui.warn(_("unable to read %s\n") % patchname)
671 err = 1
671 err = 1
672 break
672 break
673
673
674 message = ph.message
674 message = ph.message
675 if not message:
675 if not message:
676 # The commit message should not be translated
676 # The commit message should not be translated
677 message = "imported patch %s\n" % patchname
677 message = "imported patch %s\n" % patchname
678 else:
678 else:
679 if list:
679 if list:
680 # The commit message should not be translated
680 # The commit message should not be translated
681 message.append("\nimported patch %s" % patchname)
681 message.append("\nimported patch %s" % patchname)
682 message = '\n'.join(message)
682 message = '\n'.join(message)
683
683
684 if ph.haspatch:
684 if ph.haspatch:
685 (patcherr, files, fuzz) = self.patch(repo, pf)
685 (patcherr, files, fuzz) = self.patch(repo, pf)
686 if all_files is not None:
686 if all_files is not None:
687 all_files.update(files)
687 all_files.update(files)
688 patcherr = not patcherr
688 patcherr = not patcherr
689 else:
689 else:
690 self.ui.warn(_("patch %s is empty\n") % patchname)
690 self.ui.warn(_("patch %s is empty\n") % patchname)
691 patcherr, files, fuzz = 0, [], 0
691 patcherr, files, fuzz = 0, [], 0
692
692
693 if merge and files:
693 if merge and files:
694 # Mark as removed/merged and update dirstate parent info
694 # Mark as removed/merged and update dirstate parent info
695 removed = []
695 removed = []
696 merged = []
696 merged = []
697 for f in files:
697 for f in files:
698 if os.path.lexists(repo.wjoin(f)):
698 if os.path.lexists(repo.wjoin(f)):
699 merged.append(f)
699 merged.append(f)
700 else:
700 else:
701 removed.append(f)
701 removed.append(f)
702 for f in removed:
702 for f in removed:
703 repo.dirstate.remove(f)
703 repo.dirstate.remove(f)
704 for f in merged:
704 for f in merged:
705 repo.dirstate.merge(f)
705 repo.dirstate.merge(f)
706 p1, p2 = repo.dirstate.parents()
706 p1, p2 = repo.dirstate.parents()
707 repo.dirstate.setparents(p1, merge)
707 repo.dirstate.setparents(p1, merge)
708
708
709 files = cmdutil.updatedir(self.ui, repo, files)
709 files = cmdutil.updatedir(self.ui, repo, files)
710 match = cmdutil.matchfiles(repo, files or [])
710 match = cmdutil.matchfiles(repo, files or [])
711 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
711 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
712
712
713 if n is None:
713 if n is None:
714 raise util.Abort(_("repository commit failed"))
714 raise util.Abort(_("repository commit failed"))
715
715
716 if update_status:
716 if update_status:
717 self.applied.append(statusentry(n, patchname))
717 self.applied.append(statusentry(n, patchname))
718
718
719 if patcherr:
719 if patcherr:
720 self.ui.warn(_("patch failed, rejects left in working dir\n"))
720 self.ui.warn(_("patch failed, rejects left in working dir\n"))
721 err = 2
721 err = 2
722 break
722 break
723
723
724 if fuzz and strict:
724 if fuzz and strict:
725 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
725 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
726 err = 3
726 err = 3
727 break
727 break
728 return (err, n)
728 return (err, n)
729
729
730 def _cleanup(self, patches, numrevs, keep=False):
730 def _cleanup(self, patches, numrevs, keep=False):
731 if not keep:
731 if not keep:
732 r = self.qrepo()
732 r = self.qrepo()
733 if r:
733 if r:
734 r[None].remove(patches, True)
734 r[None].remove(patches, True)
735 else:
735 else:
736 for p in patches:
736 for p in patches:
737 os.unlink(self.join(p))
737 os.unlink(self.join(p))
738
738
739 if numrevs:
739 if numrevs:
740 del self.applied[:numrevs]
740 del self.applied[:numrevs]
741 self.applied_dirty = 1
741 self.applied_dirty = 1
742
742
743 for i in sorted([self.find_series(p) for p in patches], reverse=True):
743 for i in sorted([self.find_series(p) for p in patches], reverse=True):
744 del self.full_series[i]
744 del self.full_series[i]
745 self.parse_series()
745 self.parse_series()
746 self.series_dirty = 1
746 self.series_dirty = 1
747
747
748 def _revpatches(self, repo, revs):
748 def _revpatches(self, repo, revs):
749 firstrev = repo[self.applied[0].node].rev()
749 firstrev = repo[self.applied[0].node].rev()
750 patches = []
750 patches = []
751 for i, rev in enumerate(revs):
751 for i, rev in enumerate(revs):
752
752
753 if rev < firstrev:
753 if rev < firstrev:
754 raise util.Abort(_('revision %d is not managed') % rev)
754 raise util.Abort(_('revision %d is not managed') % rev)
755
755
756 ctx = repo[rev]
756 ctx = repo[rev]
757 base = self.applied[i].node
757 base = self.applied[i].node
758 if ctx.node() != base:
758 if ctx.node() != base:
759 msg = _('cannot delete revision %d above applied patches')
759 msg = _('cannot delete revision %d above applied patches')
760 raise util.Abort(msg % rev)
760 raise util.Abort(msg % rev)
761
761
762 patch = self.applied[i].name
762 patch = self.applied[i].name
763 for fmt in ('[mq]: %s', 'imported patch %s'):
763 for fmt in ('[mq]: %s', 'imported patch %s'):
764 if ctx.description() == fmt % patch:
764 if ctx.description() == fmt % patch:
765 msg = _('patch %s finalized without changeset message\n')
765 msg = _('patch %s finalized without changeset message\n')
766 repo.ui.status(msg % patch)
766 repo.ui.status(msg % patch)
767 break
767 break
768
768
769 patches.append(patch)
769 patches.append(patch)
770 return patches
770 return patches
771
771
772 def finish(self, repo, revs):
772 def finish(self, repo, revs):
773 patches = self._revpatches(repo, sorted(revs))
773 patches = self._revpatches(repo, sorted(revs))
774 self._cleanup(patches, len(patches))
774 self._cleanup(patches, len(patches))
775
775
776 def delete(self, repo, patches, opts):
776 def delete(self, repo, patches, opts):
777 if not patches and not opts.get('rev'):
777 if not patches and not opts.get('rev'):
778 raise util.Abort(_('qdelete requires at least one revision or '
778 raise util.Abort(_('qdelete requires at least one revision or '
779 'patch name'))
779 'patch name'))
780
780
781 realpatches = []
781 realpatches = []
782 for patch in patches:
782 for patch in patches:
783 patch = self.lookup(patch, strict=True)
783 patch = self.lookup(patch, strict=True)
784 info = self.isapplied(patch)
784 info = self.isapplied(patch)
785 if info:
785 if info:
786 raise util.Abort(_("cannot delete applied patch %s") % patch)
786 raise util.Abort(_("cannot delete applied patch %s") % patch)
787 if patch not in self.series:
787 if patch not in self.series:
788 raise util.Abort(_("patch %s not in series file") % patch)
788 raise util.Abort(_("patch %s not in series file") % patch)
789 if patch not in realpatches:
789 if patch not in realpatches:
790 realpatches.append(patch)
790 realpatches.append(patch)
791
791
792 numrevs = 0
792 numrevs = 0
793 if opts.get('rev'):
793 if opts.get('rev'):
794 if not self.applied:
794 if not self.applied:
795 raise util.Abort(_('no patches applied'))
795 raise util.Abort(_('no patches applied'))
796 revs = cmdutil.revrange(repo, opts.get('rev'))
796 revs = cmdutil.revrange(repo, opts.get('rev'))
797 if len(revs) > 1 and revs[0] > revs[1]:
797 if len(revs) > 1 and revs[0] > revs[1]:
798 revs.reverse()
798 revs.reverse()
799 revpatches = self._revpatches(repo, revs)
799 revpatches = self._revpatches(repo, revs)
800 realpatches += revpatches
800 realpatches += revpatches
801 numrevs = len(revpatches)
801 numrevs = len(revpatches)
802
802
803 self._cleanup(realpatches, numrevs, opts.get('keep'))
803 self._cleanup(realpatches, numrevs, opts.get('keep'))
804
804
805 def check_toppatch(self, repo):
805 def check_toppatch(self, repo):
806 if self.applied:
806 if self.applied:
807 top = self.applied[-1].node
807 top = self.applied[-1].node
808 patch = self.applied[-1].name
808 patch = self.applied[-1].name
809 pp = repo.dirstate.parents()
809 pp = repo.dirstate.parents()
810 if top not in pp:
810 if top not in pp:
811 raise util.Abort(_("working directory revision is not qtip"))
811 raise util.Abort(_("working directory revision is not qtip"))
812 return top, patch
812 return top, patch
813 return None, None
813 return None, None
814
814
815 def check_substate(self, repo):
815 def check_substate(self, repo):
816 '''return list of subrepos at a different revision than substate.
816 '''return list of subrepos at a different revision than substate.
817 Abort if any subrepos have uncommitted changes.'''
817 Abort if any subrepos have uncommitted changes.'''
818 inclsubs = []
818 inclsubs = []
819 wctx = repo[None]
819 wctx = repo[None]
820 for s in wctx.substate:
820 for s in wctx.substate:
821 if wctx.sub(s).dirty(True):
821 if wctx.sub(s).dirty(True):
822 raise util.Abort(
822 raise util.Abort(
823 _("uncommitted changes in subrepository %s") % s)
823 _("uncommitted changes in subrepository %s") % s)
824 elif wctx.sub(s).dirty():
824 elif wctx.sub(s).dirty():
825 inclsubs.append(s)
825 inclsubs.append(s)
826 return inclsubs
826 return inclsubs
827
827
828 def check_localchanges(self, repo, force=False, refresh=True):
828 def check_localchanges(self, repo, force=False, refresh=True):
829 m, a, r, d = repo.status()[:4]
829 m, a, r, d = repo.status()[:4]
830 if (m or a or r or d) and not force:
830 if (m or a or r or d) and not force:
831 if refresh:
831 if refresh:
832 raise util.Abort(_("local changes found, refresh first"))
832 raise util.Abort(_("local changes found, refresh first"))
833 else:
833 else:
834 raise util.Abort(_("local changes found"))
834 raise util.Abort(_("local changes found"))
835 return m, a, r, d
835 return m, a, r, d
836
836
837 _reserved = ('series', 'status', 'guards')
837 _reserved = ('series', 'status', 'guards')
838 def check_reserved_name(self, name):
838 def check_reserved_name(self, name):
839 if (name in self._reserved or name.startswith('.hg')
839 if (name in self._reserved or name.startswith('.hg')
840 or name.startswith('.mq') or '#' in name or ':' in name):
840 or name.startswith('.mq') or '#' in name or ':' in name):
841 raise util.Abort(_('"%s" cannot be used as the name of a patch')
841 raise util.Abort(_('"%s" cannot be used as the name of a patch')
842 % name)
842 % name)
843
843
844 def new(self, repo, patchfn, *pats, **opts):
844 def new(self, repo, patchfn, *pats, **opts):
845 """options:
845 """options:
846 msg: a string or a no-argument function returning a string
846 msg: a string or a no-argument function returning a string
847 """
847 """
848 msg = opts.get('msg')
848 msg = opts.get('msg')
849 user = opts.get('user')
849 user = opts.get('user')
850 date = opts.get('date')
850 date = opts.get('date')
851 if date:
851 if date:
852 date = util.parsedate(date)
852 date = util.parsedate(date)
853 diffopts = self.diffopts({'git': opts.get('git')})
853 diffopts = self.diffopts({'git': opts.get('git')})
854 self.check_reserved_name(patchfn)
854 self.check_reserved_name(patchfn)
855 if os.path.exists(self.join(patchfn)):
855 if os.path.exists(self.join(patchfn)):
856 if os.path.isdir(self.join(patchfn)):
856 if os.path.isdir(self.join(patchfn)):
857 raise util.Abort(_('"%s" already exists as a directory')
857 raise util.Abort(_('"%s" already exists as a directory')
858 % patchfn)
858 % patchfn)
859 else:
859 else:
860 raise util.Abort(_('patch "%s" already exists') % patchfn)
860 raise util.Abort(_('patch "%s" already exists') % patchfn)
861
861
862 inclsubs = self.check_substate(repo)
862 inclsubs = self.check_substate(repo)
863 if inclsubs:
863 if inclsubs:
864 inclsubs.append('.hgsubstate')
864 inclsubs.append('.hgsubstate')
865 if opts.get('include') or opts.get('exclude') or pats:
865 if opts.get('include') or opts.get('exclude') or pats:
866 if inclsubs:
866 if inclsubs:
867 pats = list(pats or []) + inclsubs
867 pats = list(pats or []) + inclsubs
868 match = cmdutil.match(repo, pats, opts)
868 match = cmdutil.match(repo, pats, opts)
869 # detect missing files in pats
869 # detect missing files in pats
870 def badfn(f, msg):
870 def badfn(f, msg):
871 if f != '.hgsubstate': # .hgsubstate is auto-created
871 if f != '.hgsubstate': # .hgsubstate is auto-created
872 raise util.Abort('%s: %s' % (f, msg))
872 raise util.Abort('%s: %s' % (f, msg))
873 match.bad = badfn
873 match.bad = badfn
874 m, a, r, d = repo.status(match=match)[:4]
874 m, a, r, d = repo.status(match=match)[:4]
875 else:
875 else:
876 m, a, r, d = self.check_localchanges(repo, force=True)
876 m, a, r, d = self.check_localchanges(repo, force=True)
877 match = cmdutil.matchfiles(repo, m + a + r + inclsubs)
877 match = cmdutil.matchfiles(repo, m + a + r + inclsubs)
878 if len(repo[None].parents()) > 1:
878 if len(repo[None].parents()) > 1:
879 raise util.Abort(_('cannot manage merge changesets'))
879 raise util.Abort(_('cannot manage merge changesets'))
880 commitfiles = m + a + r
880 commitfiles = m + a + r
881 self.check_toppatch(repo)
881 self.check_toppatch(repo)
882 insert = self.full_series_end()
882 insert = self.full_series_end()
883 wlock = repo.wlock()
883 wlock = repo.wlock()
884 try:
884 try:
885 try:
885 try:
886 # if patch file write fails, abort early
886 # if patch file write fails, abort early
887 p = self.opener(patchfn, "w")
887 p = self.opener(patchfn, "w")
888 except IOError, e:
888 except IOError, e:
889 raise util.Abort(_('cannot write patch "%s": %s')
889 raise util.Abort(_('cannot write patch "%s": %s')
890 % (patchfn, e.strerror))
890 % (patchfn, e.strerror))
891 try:
891 try:
892 if self.plainmode:
892 if self.plainmode:
893 if user:
893 if user:
894 p.write("From: " + user + "\n")
894 p.write("From: " + user + "\n")
895 if not date:
895 if not date:
896 p.write("\n")
896 p.write("\n")
897 if date:
897 if date:
898 p.write("Date: %d %d\n\n" % date)
898 p.write("Date: %d %d\n\n" % date)
899 else:
899 else:
900 p.write("# HG changeset patch\n")
900 p.write("# HG changeset patch\n")
901 p.write("# Parent "
901 p.write("# Parent "
902 + hex(repo[None].parents()[0].node()) + "\n")
902 + hex(repo[None].p1().node()) + "\n")
903 if user:
903 if user:
904 p.write("# User " + user + "\n")
904 p.write("# User " + user + "\n")
905 if date:
905 if date:
906 p.write("# Date %s %s\n\n" % date)
906 p.write("# Date %s %s\n\n" % date)
907 if hasattr(msg, '__call__'):
907 if hasattr(msg, '__call__'):
908 msg = msg()
908 msg = msg()
909 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
909 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
910 n = repo.commit(commitmsg, user, date, match=match, force=True)
910 n = repo.commit(commitmsg, user, date, match=match, force=True)
911 if n is None:
911 if n is None:
912 raise util.Abort(_("repo commit failed"))
912 raise util.Abort(_("repo commit failed"))
913 try:
913 try:
914 self.full_series[insert:insert] = [patchfn]
914 self.full_series[insert:insert] = [patchfn]
915 self.applied.append(statusentry(n, patchfn))
915 self.applied.append(statusentry(n, patchfn))
916 self.parse_series()
916 self.parse_series()
917 self.series_dirty = 1
917 self.series_dirty = 1
918 self.applied_dirty = 1
918 self.applied_dirty = 1
919 if msg:
919 if msg:
920 msg = msg + "\n\n"
920 msg = msg + "\n\n"
921 p.write(msg)
921 p.write(msg)
922 if commitfiles:
922 if commitfiles:
923 parent = self.qparents(repo, n)
923 parent = self.qparents(repo, n)
924 chunks = patch.diff(repo, node1=parent, node2=n,
924 chunks = patch.diff(repo, node1=parent, node2=n,
925 match=match, opts=diffopts)
925 match=match, opts=diffopts)
926 for chunk in chunks:
926 for chunk in chunks:
927 p.write(chunk)
927 p.write(chunk)
928 p.close()
928 p.close()
929 wlock.release()
929 wlock.release()
930 wlock = None
930 wlock = None
931 r = self.qrepo()
931 r = self.qrepo()
932 if r:
932 if r:
933 r[None].add([patchfn])
933 r[None].add([patchfn])
934 except:
934 except:
935 repo.rollback()
935 repo.rollback()
936 raise
936 raise
937 except Exception:
937 except Exception:
938 patchpath = self.join(patchfn)
938 patchpath = self.join(patchfn)
939 try:
939 try:
940 os.unlink(patchpath)
940 os.unlink(patchpath)
941 except:
941 except:
942 self.ui.warn(_('error unlinking %s\n') % patchpath)
942 self.ui.warn(_('error unlinking %s\n') % patchpath)
943 raise
943 raise
944 self.removeundo(repo)
944 self.removeundo(repo)
945 finally:
945 finally:
946 release(wlock)
946 release(wlock)
947
947
948 def strip(self, repo, revs, update=True, backup="all", force=None):
948 def strip(self, repo, revs, update=True, backup="all", force=None):
949 wlock = lock = None
949 wlock = lock = None
950 try:
950 try:
951 wlock = repo.wlock()
951 wlock = repo.wlock()
952 lock = repo.lock()
952 lock = repo.lock()
953
953
954 if update:
954 if update:
955 self.check_localchanges(repo, force=force, refresh=False)
955 self.check_localchanges(repo, force=force, refresh=False)
956 urev = self.qparents(repo, revs[0])
956 urev = self.qparents(repo, revs[0])
957 hg.clean(repo, urev)
957 hg.clean(repo, urev)
958 repo.dirstate.write()
958 repo.dirstate.write()
959
959
960 self.removeundo(repo)
960 self.removeundo(repo)
961 for rev in revs:
961 for rev in revs:
962 repair.strip(self.ui, repo, rev, backup)
962 repair.strip(self.ui, repo, rev, backup)
963 # strip may have unbundled a set of backed up revisions after
963 # strip may have unbundled a set of backed up revisions after
964 # the actual strip
964 # the actual strip
965 self.removeundo(repo)
965 self.removeundo(repo)
966 finally:
966 finally:
967 release(lock, wlock)
967 release(lock, wlock)
968
968
969 def isapplied(self, patch):
969 def isapplied(self, patch):
970 """returns (index, rev, patch)"""
970 """returns (index, rev, patch)"""
971 for i, a in enumerate(self.applied):
971 for i, a in enumerate(self.applied):
972 if a.name == patch:
972 if a.name == patch:
973 return (i, a.node, a.name)
973 return (i, a.node, a.name)
974 return None
974 return None
975
975
976 # if the exact patch name does not exist, we try a few
976 # if the exact patch name does not exist, we try a few
977 # variations. If strict is passed, we try only #1
977 # variations. If strict is passed, we try only #1
978 #
978 #
979 # 1) a number to indicate an offset in the series file
979 # 1) a number to indicate an offset in the series file
980 # 2) a unique substring of the patch name was given
980 # 2) a unique substring of the patch name was given
981 # 3) patchname[-+]num to indicate an offset in the series file
981 # 3) patchname[-+]num to indicate an offset in the series file
982 def lookup(self, patch, strict=False):
982 def lookup(self, patch, strict=False):
983 patch = patch and str(patch)
983 patch = patch and str(patch)
984
984
985 def partial_name(s):
985 def partial_name(s):
986 if s in self.series:
986 if s in self.series:
987 return s
987 return s
988 matches = [x for x in self.series if s in x]
988 matches = [x for x in self.series if s in x]
989 if len(matches) > 1:
989 if len(matches) > 1:
990 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
990 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
991 for m in matches:
991 for m in matches:
992 self.ui.warn(' %s\n' % m)
992 self.ui.warn(' %s\n' % m)
993 return None
993 return None
994 if matches:
994 if matches:
995 return matches[0]
995 return matches[0]
996 if self.series and self.applied:
996 if self.series and self.applied:
997 if s == 'qtip':
997 if s == 'qtip':
998 return self.series[self.series_end(True)-1]
998 return self.series[self.series_end(True)-1]
999 if s == 'qbase':
999 if s == 'qbase':
1000 return self.series[0]
1000 return self.series[0]
1001 return None
1001 return None
1002
1002
1003 if patch is None:
1003 if patch is None:
1004 return None
1004 return None
1005 if patch in self.series:
1005 if patch in self.series:
1006 return patch
1006 return patch
1007
1007
1008 if not os.path.isfile(self.join(patch)):
1008 if not os.path.isfile(self.join(patch)):
1009 try:
1009 try:
1010 sno = int(patch)
1010 sno = int(patch)
1011 except (ValueError, OverflowError):
1011 except (ValueError, OverflowError):
1012 pass
1012 pass
1013 else:
1013 else:
1014 if -len(self.series) <= sno < len(self.series):
1014 if -len(self.series) <= sno < len(self.series):
1015 return self.series[sno]
1015 return self.series[sno]
1016
1016
1017 if not strict:
1017 if not strict:
1018 res = partial_name(patch)
1018 res = partial_name(patch)
1019 if res:
1019 if res:
1020 return res
1020 return res
1021 minus = patch.rfind('-')
1021 minus = patch.rfind('-')
1022 if minus >= 0:
1022 if minus >= 0:
1023 res = partial_name(patch[:minus])
1023 res = partial_name(patch[:minus])
1024 if res:
1024 if res:
1025 i = self.series.index(res)
1025 i = self.series.index(res)
1026 try:
1026 try:
1027 off = int(patch[minus + 1:] or 1)
1027 off = int(patch[minus + 1:] or 1)
1028 except (ValueError, OverflowError):
1028 except (ValueError, OverflowError):
1029 pass
1029 pass
1030 else:
1030 else:
1031 if i - off >= 0:
1031 if i - off >= 0:
1032 return self.series[i - off]
1032 return self.series[i - off]
1033 plus = patch.rfind('+')
1033 plus = patch.rfind('+')
1034 if plus >= 0:
1034 if plus >= 0:
1035 res = partial_name(patch[:plus])
1035 res = partial_name(patch[:plus])
1036 if res:
1036 if res:
1037 i = self.series.index(res)
1037 i = self.series.index(res)
1038 try:
1038 try:
1039 off = int(patch[plus + 1:] or 1)
1039 off = int(patch[plus + 1:] or 1)
1040 except (ValueError, OverflowError):
1040 except (ValueError, OverflowError):
1041 pass
1041 pass
1042 else:
1042 else:
1043 if i + off < len(self.series):
1043 if i + off < len(self.series):
1044 return self.series[i + off]
1044 return self.series[i + off]
1045 raise util.Abort(_("patch %s not in series") % patch)
1045 raise util.Abort(_("patch %s not in series") % patch)
1046
1046
1047 def push(self, repo, patch=None, force=False, list=False,
1047 def push(self, repo, patch=None, force=False, list=False,
1048 mergeq=None, all=False, move=False, exact=False):
1048 mergeq=None, all=False, move=False, exact=False):
1049 diffopts = self.diffopts()
1049 diffopts = self.diffopts()
1050 wlock = repo.wlock()
1050 wlock = repo.wlock()
1051 try:
1051 try:
1052 heads = []
1052 heads = []
1053 for b, ls in repo.branchmap().iteritems():
1053 for b, ls in repo.branchmap().iteritems():
1054 heads += ls
1054 heads += ls
1055 if not heads:
1055 if not heads:
1056 heads = [nullid]
1056 heads = [nullid]
1057 if repo.dirstate.parents()[0] not in heads and not exact:
1057 if repo.dirstate.p1() not in heads and not exact:
1058 self.ui.status(_("(working directory not at a head)\n"))
1058 self.ui.status(_("(working directory not at a head)\n"))
1059
1059
1060 if not self.series:
1060 if not self.series:
1061 self.ui.warn(_('no patches in series\n'))
1061 self.ui.warn(_('no patches in series\n'))
1062 return 0
1062 return 0
1063
1063
1064 patch = self.lookup(patch)
1064 patch = self.lookup(patch)
1065 # Suppose our series file is: A B C and the current 'top'
1065 # Suppose our series file is: A B C and the current 'top'
1066 # patch is B. qpush C should be performed (moving forward)
1066 # patch is B. qpush C should be performed (moving forward)
1067 # qpush B is a NOP (no change) qpush A is an error (can't
1067 # qpush B is a NOP (no change) qpush A is an error (can't
1068 # go backwards with qpush)
1068 # go backwards with qpush)
1069 if patch:
1069 if patch:
1070 info = self.isapplied(patch)
1070 info = self.isapplied(patch)
1071 if info and info[0] >= len(self.applied) - 1:
1071 if info and info[0] >= len(self.applied) - 1:
1072 self.ui.warn(
1072 self.ui.warn(
1073 _('qpush: %s is already at the top\n') % patch)
1073 _('qpush: %s is already at the top\n') % patch)
1074 return 0
1074 return 0
1075
1075
1076 pushable, reason = self.pushable(patch)
1076 pushable, reason = self.pushable(patch)
1077 if pushable:
1077 if pushable:
1078 if self.series.index(patch) < self.series_end():
1078 if self.series.index(patch) < self.series_end():
1079 raise util.Abort(
1079 raise util.Abort(
1080 _("cannot push to a previous patch: %s") % patch)
1080 _("cannot push to a previous patch: %s") % patch)
1081 else:
1081 else:
1082 if reason:
1082 if reason:
1083 reason = _('guarded by %r') % reason
1083 reason = _('guarded by %r') % reason
1084 else:
1084 else:
1085 reason = _('no matching guards')
1085 reason = _('no matching guards')
1086 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1086 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1087 return 1
1087 return 1
1088 elif all:
1088 elif all:
1089 patch = self.series[-1]
1089 patch = self.series[-1]
1090 if self.isapplied(patch):
1090 if self.isapplied(patch):
1091 self.ui.warn(_('all patches are currently applied\n'))
1091 self.ui.warn(_('all patches are currently applied\n'))
1092 return 0
1092 return 0
1093
1093
1094 # Following the above example, starting at 'top' of B:
1094 # Following the above example, starting at 'top' of B:
1095 # qpush should be performed (pushes C), but a subsequent
1095 # qpush should be performed (pushes C), but a subsequent
1096 # qpush without an argument is an error (nothing to
1096 # qpush without an argument is an error (nothing to
1097 # apply). This allows a loop of "...while hg qpush..." to
1097 # apply). This allows a loop of "...while hg qpush..." to
1098 # work as it detects an error when done
1098 # work as it detects an error when done
1099 start = self.series_end()
1099 start = self.series_end()
1100 if start == len(self.series):
1100 if start == len(self.series):
1101 self.ui.warn(_('patch series already fully applied\n'))
1101 self.ui.warn(_('patch series already fully applied\n'))
1102 return 1
1102 return 1
1103 if not force:
1103 if not force:
1104 self.check_localchanges(repo)
1104 self.check_localchanges(repo)
1105
1105
1106 if exact:
1106 if exact:
1107 if move:
1107 if move:
1108 raise util.Abort(_("cannot use --exact and --move together"))
1108 raise util.Abort(_("cannot use --exact and --move together"))
1109 if self.applied:
1109 if self.applied:
1110 raise util.Abort(_("cannot push --exact with applied patches"))
1110 raise util.Abort(_("cannot push --exact with applied patches"))
1111 root = self.series[start]
1111 root = self.series[start]
1112 target = patchheader(self.join(root), self.plainmode).parent
1112 target = patchheader(self.join(root), self.plainmode).parent
1113 if not target:
1113 if not target:
1114 raise util.Abort(_("%s does not have a parent recorded" % root))
1114 raise util.Abort(_("%s does not have a parent recorded" % root))
1115 if not repo[target] == repo['.']:
1115 if not repo[target] == repo['.']:
1116 hg.update(repo, target)
1116 hg.update(repo, target)
1117
1117
1118 if move:
1118 if move:
1119 if not patch:
1119 if not patch:
1120 raise util.Abort(_("please specify the patch to move"))
1120 raise util.Abort(_("please specify the patch to move"))
1121 for i, rpn in enumerate(self.full_series[start:]):
1121 for i, rpn in enumerate(self.full_series[start:]):
1122 # strip markers for patch guards
1122 # strip markers for patch guards
1123 if self.guard_re.split(rpn, 1)[0] == patch:
1123 if self.guard_re.split(rpn, 1)[0] == patch:
1124 break
1124 break
1125 index = start + i
1125 index = start + i
1126 assert index < len(self.full_series)
1126 assert index < len(self.full_series)
1127 fullpatch = self.full_series[index]
1127 fullpatch = self.full_series[index]
1128 del self.full_series[index]
1128 del self.full_series[index]
1129 self.full_series.insert(start, fullpatch)
1129 self.full_series.insert(start, fullpatch)
1130 self.parse_series()
1130 self.parse_series()
1131 self.series_dirty = 1
1131 self.series_dirty = 1
1132
1132
1133 self.applied_dirty = 1
1133 self.applied_dirty = 1
1134 if start > 0:
1134 if start > 0:
1135 self.check_toppatch(repo)
1135 self.check_toppatch(repo)
1136 if not patch:
1136 if not patch:
1137 patch = self.series[start]
1137 patch = self.series[start]
1138 end = start + 1
1138 end = start + 1
1139 else:
1139 else:
1140 end = self.series.index(patch, start) + 1
1140 end = self.series.index(patch, start) + 1
1141
1141
1142 s = self.series[start:end]
1142 s = self.series[start:end]
1143 all_files = set()
1143 all_files = set()
1144 try:
1144 try:
1145 if mergeq:
1145 if mergeq:
1146 ret = self.mergepatch(repo, mergeq, s, diffopts)
1146 ret = self.mergepatch(repo, mergeq, s, diffopts)
1147 else:
1147 else:
1148 ret = self.apply(repo, s, list, all_files=all_files)
1148 ret = self.apply(repo, s, list, all_files=all_files)
1149 except:
1149 except:
1150 self.ui.warn(_('cleaning up working directory...'))
1150 self.ui.warn(_('cleaning up working directory...'))
1151 node = repo.dirstate.parents()[0]
1151 node = repo.dirstate.p1()
1152 hg.revert(repo, node, None)
1152 hg.revert(repo, node, None)
1153 # only remove unknown files that we know we touched or
1153 # only remove unknown files that we know we touched or
1154 # created while patching
1154 # created while patching
1155 for f in all_files:
1155 for f in all_files:
1156 if f not in repo.dirstate:
1156 if f not in repo.dirstate:
1157 try:
1157 try:
1158 util.unlinkpath(repo.wjoin(f))
1158 util.unlinkpath(repo.wjoin(f))
1159 except OSError, inst:
1159 except OSError, inst:
1160 if inst.errno != errno.ENOENT:
1160 if inst.errno != errno.ENOENT:
1161 raise
1161 raise
1162 self.ui.warn(_('done\n'))
1162 self.ui.warn(_('done\n'))
1163 raise
1163 raise
1164
1164
1165 if not self.applied:
1165 if not self.applied:
1166 return ret[0]
1166 return ret[0]
1167 top = self.applied[-1].name
1167 top = self.applied[-1].name
1168 if ret[0] and ret[0] > 1:
1168 if ret[0] and ret[0] > 1:
1169 msg = _("errors during apply, please fix and refresh %s\n")
1169 msg = _("errors during apply, please fix and refresh %s\n")
1170 self.ui.write(msg % top)
1170 self.ui.write(msg % top)
1171 else:
1171 else:
1172 self.ui.write(_("now at: %s\n") % top)
1172 self.ui.write(_("now at: %s\n") % top)
1173 return ret[0]
1173 return ret[0]
1174
1174
1175 finally:
1175 finally:
1176 wlock.release()
1176 wlock.release()
1177
1177
1178 def pop(self, repo, patch=None, force=False, update=True, all=False):
1178 def pop(self, repo, patch=None, force=False, update=True, all=False):
1179 wlock = repo.wlock()
1179 wlock = repo.wlock()
1180 try:
1180 try:
1181 if patch:
1181 if patch:
1182 # index, rev, patch
1182 # index, rev, patch
1183 info = self.isapplied(patch)
1183 info = self.isapplied(patch)
1184 if not info:
1184 if not info:
1185 patch = self.lookup(patch)
1185 patch = self.lookup(patch)
1186 info = self.isapplied(patch)
1186 info = self.isapplied(patch)
1187 if not info:
1187 if not info:
1188 raise util.Abort(_("patch %s is not applied") % patch)
1188 raise util.Abort(_("patch %s is not applied") % patch)
1189
1189
1190 if not self.applied:
1190 if not self.applied:
1191 # Allow qpop -a to work repeatedly,
1191 # Allow qpop -a to work repeatedly,
1192 # but not qpop without an argument
1192 # but not qpop without an argument
1193 self.ui.warn(_("no patches applied\n"))
1193 self.ui.warn(_("no patches applied\n"))
1194 return not all
1194 return not all
1195
1195
1196 if all:
1196 if all:
1197 start = 0
1197 start = 0
1198 elif patch:
1198 elif patch:
1199 start = info[0] + 1
1199 start = info[0] + 1
1200 else:
1200 else:
1201 start = len(self.applied) - 1
1201 start = len(self.applied) - 1
1202
1202
1203 if start >= len(self.applied):
1203 if start >= len(self.applied):
1204 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1204 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1205 return
1205 return
1206
1206
1207 if not update:
1207 if not update:
1208 parents = repo.dirstate.parents()
1208 parents = repo.dirstate.parents()
1209 rr = [x.node for x in self.applied]
1209 rr = [x.node for x in self.applied]
1210 for p in parents:
1210 for p in parents:
1211 if p in rr:
1211 if p in rr:
1212 self.ui.warn(_("qpop: forcing dirstate update\n"))
1212 self.ui.warn(_("qpop: forcing dirstate update\n"))
1213 update = True
1213 update = True
1214 else:
1214 else:
1215 parents = [p.node() for p in repo[None].parents()]
1215 parents = [p.node() for p in repo[None].parents()]
1216 needupdate = False
1216 needupdate = False
1217 for entry in self.applied[start:]:
1217 for entry in self.applied[start:]:
1218 if entry.node in parents:
1218 if entry.node in parents:
1219 needupdate = True
1219 needupdate = True
1220 break
1220 break
1221 update = needupdate
1221 update = needupdate
1222
1222
1223 if not force and update:
1223 if not force and update:
1224 self.check_localchanges(repo)
1224 self.check_localchanges(repo)
1225
1225
1226 self.applied_dirty = 1
1226 self.applied_dirty = 1
1227 end = len(self.applied)
1227 end = len(self.applied)
1228 rev = self.applied[start].node
1228 rev = self.applied[start].node
1229 if update:
1229 if update:
1230 top = self.check_toppatch(repo)[0]
1230 top = self.check_toppatch(repo)[0]
1231
1231
1232 try:
1232 try:
1233 heads = repo.changelog.heads(rev)
1233 heads = repo.changelog.heads(rev)
1234 except error.LookupError:
1234 except error.LookupError:
1235 node = short(rev)
1235 node = short(rev)
1236 raise util.Abort(_('trying to pop unknown node %s') % node)
1236 raise util.Abort(_('trying to pop unknown node %s') % node)
1237
1237
1238 if heads != [self.applied[-1].node]:
1238 if heads != [self.applied[-1].node]:
1239 raise util.Abort(_("popping would remove a revision not "
1239 raise util.Abort(_("popping would remove a revision not "
1240 "managed by this patch queue"))
1240 "managed by this patch queue"))
1241
1241
1242 # we know there are no local changes, so we can make a simplified
1242 # we know there are no local changes, so we can make a simplified
1243 # form of hg.update.
1243 # form of hg.update.
1244 if update:
1244 if update:
1245 qp = self.qparents(repo, rev)
1245 qp = self.qparents(repo, rev)
1246 ctx = repo[qp]
1246 ctx = repo[qp]
1247 m, a, r, d = repo.status(qp, top)[:4]
1247 m, a, r, d = repo.status(qp, top)[:4]
1248 if d:
1248 if d:
1249 raise util.Abort(_("deletions found between repo revs"))
1249 raise util.Abort(_("deletions found between repo revs"))
1250 for f in a:
1250 for f in a:
1251 try:
1251 try:
1252 util.unlinkpath(repo.wjoin(f))
1252 util.unlinkpath(repo.wjoin(f))
1253 except OSError, e:
1253 except OSError, e:
1254 if e.errno != errno.ENOENT:
1254 if e.errno != errno.ENOENT:
1255 raise
1255 raise
1256 repo.dirstate.forget(f)
1256 repo.dirstate.forget(f)
1257 for f in m + r:
1257 for f in m + r:
1258 fctx = ctx[f]
1258 fctx = ctx[f]
1259 repo.wwrite(f, fctx.data(), fctx.flags())
1259 repo.wwrite(f, fctx.data(), fctx.flags())
1260 repo.dirstate.normal(f)
1260 repo.dirstate.normal(f)
1261 repo.dirstate.setparents(qp, nullid)
1261 repo.dirstate.setparents(qp, nullid)
1262 for patch in reversed(self.applied[start:end]):
1262 for patch in reversed(self.applied[start:end]):
1263 self.ui.status(_("popping %s\n") % patch.name)
1263 self.ui.status(_("popping %s\n") % patch.name)
1264 del self.applied[start:end]
1264 del self.applied[start:end]
1265 self.strip(repo, [rev], update=False, backup='strip')
1265 self.strip(repo, [rev], update=False, backup='strip')
1266 if self.applied:
1266 if self.applied:
1267 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1267 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1268 else:
1268 else:
1269 self.ui.write(_("patch queue now empty\n"))
1269 self.ui.write(_("patch queue now empty\n"))
1270 finally:
1270 finally:
1271 wlock.release()
1271 wlock.release()
1272
1272
1273 def diff(self, repo, pats, opts):
1273 def diff(self, repo, pats, opts):
1274 top, patch = self.check_toppatch(repo)
1274 top, patch = self.check_toppatch(repo)
1275 if not top:
1275 if not top:
1276 self.ui.write(_("no patches applied\n"))
1276 self.ui.write(_("no patches applied\n"))
1277 return
1277 return
1278 qp = self.qparents(repo, top)
1278 qp = self.qparents(repo, top)
1279 if opts.get('reverse'):
1279 if opts.get('reverse'):
1280 node1, node2 = None, qp
1280 node1, node2 = None, qp
1281 else:
1281 else:
1282 node1, node2 = qp, None
1282 node1, node2 = qp, None
1283 diffopts = self.diffopts(opts, patch)
1283 diffopts = self.diffopts(opts, patch)
1284 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1284 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1285
1285
1286 def refresh(self, repo, pats=None, **opts):
1286 def refresh(self, repo, pats=None, **opts):
1287 if not self.applied:
1287 if not self.applied:
1288 self.ui.write(_("no patches applied\n"))
1288 self.ui.write(_("no patches applied\n"))
1289 return 1
1289 return 1
1290 msg = opts.get('msg', '').rstrip()
1290 msg = opts.get('msg', '').rstrip()
1291 newuser = opts.get('user')
1291 newuser = opts.get('user')
1292 newdate = opts.get('date')
1292 newdate = opts.get('date')
1293 if newdate:
1293 if newdate:
1294 newdate = '%d %d' % util.parsedate(newdate)
1294 newdate = '%d %d' % util.parsedate(newdate)
1295 wlock = repo.wlock()
1295 wlock = repo.wlock()
1296
1296
1297 try:
1297 try:
1298 self.check_toppatch(repo)
1298 self.check_toppatch(repo)
1299 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1299 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1300 if repo.changelog.heads(top) != [top]:
1300 if repo.changelog.heads(top) != [top]:
1301 raise util.Abort(_("cannot refresh a revision with children"))
1301 raise util.Abort(_("cannot refresh a revision with children"))
1302
1302
1303 inclsubs = self.check_substate(repo)
1303 inclsubs = self.check_substate(repo)
1304
1304
1305 cparents = repo.changelog.parents(top)
1305 cparents = repo.changelog.parents(top)
1306 patchparent = self.qparents(repo, top)
1306 patchparent = self.qparents(repo, top)
1307 ph = patchheader(self.join(patchfn), self.plainmode)
1307 ph = patchheader(self.join(patchfn), self.plainmode)
1308 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1308 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1309 if msg:
1309 if msg:
1310 ph.setmessage(msg)
1310 ph.setmessage(msg)
1311 if newuser:
1311 if newuser:
1312 ph.setuser(newuser)
1312 ph.setuser(newuser)
1313 if newdate:
1313 if newdate:
1314 ph.setdate(newdate)
1314 ph.setdate(newdate)
1315 ph.setparent(hex(patchparent))
1315 ph.setparent(hex(patchparent))
1316
1316
1317 # only commit new patch when write is complete
1317 # only commit new patch when write is complete
1318 patchf = self.opener(patchfn, 'w', atomictemp=True)
1318 patchf = self.opener(patchfn, 'w', atomictemp=True)
1319
1319
1320 comments = str(ph)
1320 comments = str(ph)
1321 if comments:
1321 if comments:
1322 patchf.write(comments)
1322 patchf.write(comments)
1323
1323
1324 # update the dirstate in place, strip off the qtip commit
1324 # update the dirstate in place, strip off the qtip commit
1325 # and then commit.
1325 # and then commit.
1326 #
1326 #
1327 # this should really read:
1327 # this should really read:
1328 # mm, dd, aa = repo.status(top, patchparent)[:3]
1328 # mm, dd, aa = repo.status(top, patchparent)[:3]
1329 # but we do it backwards to take advantage of manifest/chlog
1329 # but we do it backwards to take advantage of manifest/chlog
1330 # caching against the next repo.status call
1330 # caching against the next repo.status call
1331 mm, aa, dd = repo.status(patchparent, top)[:3]
1331 mm, aa, dd = repo.status(patchparent, top)[:3]
1332 changes = repo.changelog.read(top)
1332 changes = repo.changelog.read(top)
1333 man = repo.manifest.read(changes[0])
1333 man = repo.manifest.read(changes[0])
1334 aaa = aa[:]
1334 aaa = aa[:]
1335 matchfn = cmdutil.match(repo, pats, opts)
1335 matchfn = cmdutil.match(repo, pats, opts)
1336 # in short mode, we only diff the files included in the
1336 # in short mode, we only diff the files included in the
1337 # patch already plus specified files
1337 # patch already plus specified files
1338 if opts.get('short'):
1338 if opts.get('short'):
1339 # if amending a patch, we start with existing
1339 # if amending a patch, we start with existing
1340 # files plus specified files - unfiltered
1340 # files plus specified files - unfiltered
1341 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1341 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1342 # filter with inc/exl options
1342 # filter with inc/exl options
1343 matchfn = cmdutil.match(repo, opts=opts)
1343 matchfn = cmdutil.match(repo, opts=opts)
1344 else:
1344 else:
1345 match = cmdutil.matchall(repo)
1345 match = cmdutil.matchall(repo)
1346 m, a, r, d = repo.status(match=match)[:4]
1346 m, a, r, d = repo.status(match=match)[:4]
1347 mm = set(mm)
1347 mm = set(mm)
1348 aa = set(aa)
1348 aa = set(aa)
1349 dd = set(dd)
1349 dd = set(dd)
1350
1350
1351 # we might end up with files that were added between
1351 # we might end up with files that were added between
1352 # qtip and the dirstate parent, but then changed in the
1352 # qtip and the dirstate parent, but then changed in the
1353 # local dirstate. in this case, we want them to only
1353 # local dirstate. in this case, we want them to only
1354 # show up in the added section
1354 # show up in the added section
1355 for x in m:
1355 for x in m:
1356 if x not in aa:
1356 if x not in aa:
1357 mm.add(x)
1357 mm.add(x)
1358 # we might end up with files added by the local dirstate that
1358 # we might end up with files added by the local dirstate that
1359 # were deleted by the patch. In this case, they should only
1359 # were deleted by the patch. In this case, they should only
1360 # show up in the changed section.
1360 # show up in the changed section.
1361 for x in a:
1361 for x in a:
1362 if x in dd:
1362 if x in dd:
1363 dd.remove(x)
1363 dd.remove(x)
1364 mm.add(x)
1364 mm.add(x)
1365 else:
1365 else:
1366 aa.add(x)
1366 aa.add(x)
1367 # make sure any files deleted in the local dirstate
1367 # make sure any files deleted in the local dirstate
1368 # are not in the add or change column of the patch
1368 # are not in the add or change column of the patch
1369 forget = []
1369 forget = []
1370 for x in d + r:
1370 for x in d + r:
1371 if x in aa:
1371 if x in aa:
1372 aa.remove(x)
1372 aa.remove(x)
1373 forget.append(x)
1373 forget.append(x)
1374 continue
1374 continue
1375 else:
1375 else:
1376 mm.discard(x)
1376 mm.discard(x)
1377 dd.add(x)
1377 dd.add(x)
1378
1378
1379 m = list(mm)
1379 m = list(mm)
1380 r = list(dd)
1380 r = list(dd)
1381 a = list(aa)
1381 a = list(aa)
1382 c = [filter(matchfn, l) for l in (m, a, r)]
1382 c = [filter(matchfn, l) for l in (m, a, r)]
1383 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1383 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1384 chunks = patch.diff(repo, patchparent, match=match,
1384 chunks = patch.diff(repo, patchparent, match=match,
1385 changes=c, opts=diffopts)
1385 changes=c, opts=diffopts)
1386 for chunk in chunks:
1386 for chunk in chunks:
1387 patchf.write(chunk)
1387 patchf.write(chunk)
1388
1388
1389 try:
1389 try:
1390 if diffopts.git or diffopts.upgrade:
1390 if diffopts.git or diffopts.upgrade:
1391 copies = {}
1391 copies = {}
1392 for dst in a:
1392 for dst in a:
1393 src = repo.dirstate.copied(dst)
1393 src = repo.dirstate.copied(dst)
1394 # during qfold, the source file for copies may
1394 # during qfold, the source file for copies may
1395 # be removed. Treat this as a simple add.
1395 # be removed. Treat this as a simple add.
1396 if src is not None and src in repo.dirstate:
1396 if src is not None and src in repo.dirstate:
1397 copies.setdefault(src, []).append(dst)
1397 copies.setdefault(src, []).append(dst)
1398 repo.dirstate.add(dst)
1398 repo.dirstate.add(dst)
1399 # remember the copies between patchparent and qtip
1399 # remember the copies between patchparent and qtip
1400 for dst in aaa:
1400 for dst in aaa:
1401 f = repo.file(dst)
1401 f = repo.file(dst)
1402 src = f.renamed(man[dst])
1402 src = f.renamed(man[dst])
1403 if src:
1403 if src:
1404 copies.setdefault(src[0], []).extend(
1404 copies.setdefault(src[0], []).extend(
1405 copies.get(dst, []))
1405 copies.get(dst, []))
1406 if dst in a:
1406 if dst in a:
1407 copies[src[0]].append(dst)
1407 copies[src[0]].append(dst)
1408 # we can't copy a file created by the patch itself
1408 # we can't copy a file created by the patch itself
1409 if dst in copies:
1409 if dst in copies:
1410 del copies[dst]
1410 del copies[dst]
1411 for src, dsts in copies.iteritems():
1411 for src, dsts in copies.iteritems():
1412 for dst in dsts:
1412 for dst in dsts:
1413 repo.dirstate.copy(src, dst)
1413 repo.dirstate.copy(src, dst)
1414 else:
1414 else:
1415 for dst in a:
1415 for dst in a:
1416 repo.dirstate.add(dst)
1416 repo.dirstate.add(dst)
1417 # Drop useless copy information
1417 # Drop useless copy information
1418 for f in list(repo.dirstate.copies()):
1418 for f in list(repo.dirstate.copies()):
1419 repo.dirstate.copy(None, f)
1419 repo.dirstate.copy(None, f)
1420 for f in r:
1420 for f in r:
1421 repo.dirstate.remove(f)
1421 repo.dirstate.remove(f)
1422 # if the patch excludes a modified file, mark that
1422 # if the patch excludes a modified file, mark that
1423 # file with mtime=0 so status can see it.
1423 # file with mtime=0 so status can see it.
1424 mm = []
1424 mm = []
1425 for i in xrange(len(m)-1, -1, -1):
1425 for i in xrange(len(m)-1, -1, -1):
1426 if not matchfn(m[i]):
1426 if not matchfn(m[i]):
1427 mm.append(m[i])
1427 mm.append(m[i])
1428 del m[i]
1428 del m[i]
1429 for f in m:
1429 for f in m:
1430 repo.dirstate.normal(f)
1430 repo.dirstate.normal(f)
1431 for f in mm:
1431 for f in mm:
1432 repo.dirstate.normallookup(f)
1432 repo.dirstate.normallookup(f)
1433 for f in forget:
1433 for f in forget:
1434 repo.dirstate.forget(f)
1434 repo.dirstate.forget(f)
1435
1435
1436 if not msg:
1436 if not msg:
1437 if not ph.message:
1437 if not ph.message:
1438 message = "[mq]: %s\n" % patchfn
1438 message = "[mq]: %s\n" % patchfn
1439 else:
1439 else:
1440 message = "\n".join(ph.message)
1440 message = "\n".join(ph.message)
1441 else:
1441 else:
1442 message = msg
1442 message = msg
1443
1443
1444 user = ph.user or changes[1]
1444 user = ph.user or changes[1]
1445
1445
1446 # assumes strip can roll itself back if interrupted
1446 # assumes strip can roll itself back if interrupted
1447 repo.dirstate.setparents(*cparents)
1447 repo.dirstate.setparents(*cparents)
1448 self.applied.pop()
1448 self.applied.pop()
1449 self.applied_dirty = 1
1449 self.applied_dirty = 1
1450 self.strip(repo, [top], update=False,
1450 self.strip(repo, [top], update=False,
1451 backup='strip')
1451 backup='strip')
1452 except:
1452 except:
1453 repo.dirstate.invalidate()
1453 repo.dirstate.invalidate()
1454 raise
1454 raise
1455
1455
1456 try:
1456 try:
1457 # might be nice to attempt to roll back strip after this
1457 # might be nice to attempt to roll back strip after this
1458 n = repo.commit(message, user, ph.date, match=match,
1458 n = repo.commit(message, user, ph.date, match=match,
1459 force=True)
1459 force=True)
1460 # only write patch after a successful commit
1460 # only write patch after a successful commit
1461 patchf.rename()
1461 patchf.rename()
1462 self.applied.append(statusentry(n, patchfn))
1462 self.applied.append(statusentry(n, patchfn))
1463 except:
1463 except:
1464 ctx = repo[cparents[0]]
1464 ctx = repo[cparents[0]]
1465 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1465 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1466 self.save_dirty()
1466 self.save_dirty()
1467 self.ui.warn(_('refresh interrupted while patch was popped! '
1467 self.ui.warn(_('refresh interrupted while patch was popped! '
1468 '(revert --all, qpush to recover)\n'))
1468 '(revert --all, qpush to recover)\n'))
1469 raise
1469 raise
1470 finally:
1470 finally:
1471 wlock.release()
1471 wlock.release()
1472 self.removeundo(repo)
1472 self.removeundo(repo)
1473
1473
1474 def init(self, repo, create=False):
1474 def init(self, repo, create=False):
1475 if not create and os.path.isdir(self.path):
1475 if not create and os.path.isdir(self.path):
1476 raise util.Abort(_("patch queue directory already exists"))
1476 raise util.Abort(_("patch queue directory already exists"))
1477 try:
1477 try:
1478 os.mkdir(self.path)
1478 os.mkdir(self.path)
1479 except OSError, inst:
1479 except OSError, inst:
1480 if inst.errno != errno.EEXIST or not create:
1480 if inst.errno != errno.EEXIST or not create:
1481 raise
1481 raise
1482 if create:
1482 if create:
1483 return self.qrepo(create=True)
1483 return self.qrepo(create=True)
1484
1484
1485 def unapplied(self, repo, patch=None):
1485 def unapplied(self, repo, patch=None):
1486 if patch and patch not in self.series:
1486 if patch and patch not in self.series:
1487 raise util.Abort(_("patch %s is not in series file") % patch)
1487 raise util.Abort(_("patch %s is not in series file") % patch)
1488 if not patch:
1488 if not patch:
1489 start = self.series_end()
1489 start = self.series_end()
1490 else:
1490 else:
1491 start = self.series.index(patch) + 1
1491 start = self.series.index(patch) + 1
1492 unapplied = []
1492 unapplied = []
1493 for i in xrange(start, len(self.series)):
1493 for i in xrange(start, len(self.series)):
1494 pushable, reason = self.pushable(i)
1494 pushable, reason = self.pushable(i)
1495 if pushable:
1495 if pushable:
1496 unapplied.append((i, self.series[i]))
1496 unapplied.append((i, self.series[i]))
1497 self.explain_pushable(i)
1497 self.explain_pushable(i)
1498 return unapplied
1498 return unapplied
1499
1499
1500 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1500 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1501 summary=False):
1501 summary=False):
1502 def displayname(pfx, patchname, state):
1502 def displayname(pfx, patchname, state):
1503 if pfx:
1503 if pfx:
1504 self.ui.write(pfx)
1504 self.ui.write(pfx)
1505 if summary:
1505 if summary:
1506 ph = patchheader(self.join(patchname), self.plainmode)
1506 ph = patchheader(self.join(patchname), self.plainmode)
1507 msg = ph.message and ph.message[0] or ''
1507 msg = ph.message and ph.message[0] or ''
1508 if self.ui.formatted():
1508 if self.ui.formatted():
1509 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1509 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1510 if width > 0:
1510 if width > 0:
1511 msg = util.ellipsis(msg, width)
1511 msg = util.ellipsis(msg, width)
1512 else:
1512 else:
1513 msg = ''
1513 msg = ''
1514 self.ui.write(patchname, label='qseries.' + state)
1514 self.ui.write(patchname, label='qseries.' + state)
1515 self.ui.write(': ')
1515 self.ui.write(': ')
1516 self.ui.write(msg, label='qseries.message.' + state)
1516 self.ui.write(msg, label='qseries.message.' + state)
1517 else:
1517 else:
1518 self.ui.write(patchname, label='qseries.' + state)
1518 self.ui.write(patchname, label='qseries.' + state)
1519 self.ui.write('\n')
1519 self.ui.write('\n')
1520
1520
1521 applied = set([p.name for p in self.applied])
1521 applied = set([p.name for p in self.applied])
1522 if length is None:
1522 if length is None:
1523 length = len(self.series) - start
1523 length = len(self.series) - start
1524 if not missing:
1524 if not missing:
1525 if self.ui.verbose:
1525 if self.ui.verbose:
1526 idxwidth = len(str(start + length - 1))
1526 idxwidth = len(str(start + length - 1))
1527 for i in xrange(start, start + length):
1527 for i in xrange(start, start + length):
1528 patch = self.series[i]
1528 patch = self.series[i]
1529 if patch in applied:
1529 if patch in applied:
1530 char, state = 'A', 'applied'
1530 char, state = 'A', 'applied'
1531 elif self.pushable(i)[0]:
1531 elif self.pushable(i)[0]:
1532 char, state = 'U', 'unapplied'
1532 char, state = 'U', 'unapplied'
1533 else:
1533 else:
1534 char, state = 'G', 'guarded'
1534 char, state = 'G', 'guarded'
1535 pfx = ''
1535 pfx = ''
1536 if self.ui.verbose:
1536 if self.ui.verbose:
1537 pfx = '%*d %s ' % (idxwidth, i, char)
1537 pfx = '%*d %s ' % (idxwidth, i, char)
1538 elif status and status != char:
1538 elif status and status != char:
1539 continue
1539 continue
1540 displayname(pfx, patch, state)
1540 displayname(pfx, patch, state)
1541 else:
1541 else:
1542 msng_list = []
1542 msng_list = []
1543 for root, dirs, files in os.walk(self.path):
1543 for root, dirs, files in os.walk(self.path):
1544 d = root[len(self.path) + 1:]
1544 d = root[len(self.path) + 1:]
1545 for f in files:
1545 for f in files:
1546 fl = os.path.join(d, f)
1546 fl = os.path.join(d, f)
1547 if (fl not in self.series and
1547 if (fl not in self.series and
1548 fl not in (self.status_path, self.series_path,
1548 fl not in (self.status_path, self.series_path,
1549 self.guards_path)
1549 self.guards_path)
1550 and not fl.startswith('.')):
1550 and not fl.startswith('.')):
1551 msng_list.append(fl)
1551 msng_list.append(fl)
1552 for x in sorted(msng_list):
1552 for x in sorted(msng_list):
1553 pfx = self.ui.verbose and ('D ') or ''
1553 pfx = self.ui.verbose and ('D ') or ''
1554 displayname(pfx, x, 'missing')
1554 displayname(pfx, x, 'missing')
1555
1555
1556 def issaveline(self, l):
1556 def issaveline(self, l):
1557 if l.name == '.hg.patches.save.line':
1557 if l.name == '.hg.patches.save.line':
1558 return True
1558 return True
1559
1559
1560 def qrepo(self, create=False):
1560 def qrepo(self, create=False):
1561 ui = self.ui.copy()
1561 ui = self.ui.copy()
1562 ui.setconfig('paths', 'default', '', overlay=False)
1562 ui.setconfig('paths', 'default', '', overlay=False)
1563 ui.setconfig('paths', 'default-push', '', overlay=False)
1563 ui.setconfig('paths', 'default-push', '', overlay=False)
1564 if create or os.path.isdir(self.join(".hg")):
1564 if create or os.path.isdir(self.join(".hg")):
1565 return hg.repository(ui, path=self.path, create=create)
1565 return hg.repository(ui, path=self.path, create=create)
1566
1566
1567 def restore(self, repo, rev, delete=None, qupdate=None):
1567 def restore(self, repo, rev, delete=None, qupdate=None):
1568 desc = repo[rev].description().strip()
1568 desc = repo[rev].description().strip()
1569 lines = desc.splitlines()
1569 lines = desc.splitlines()
1570 i = 0
1570 i = 0
1571 datastart = None
1571 datastart = None
1572 series = []
1572 series = []
1573 applied = []
1573 applied = []
1574 qpp = None
1574 qpp = None
1575 for i, line in enumerate(lines):
1575 for i, line in enumerate(lines):
1576 if line == 'Patch Data:':
1576 if line == 'Patch Data:':
1577 datastart = i + 1
1577 datastart = i + 1
1578 elif line.startswith('Dirstate:'):
1578 elif line.startswith('Dirstate:'):
1579 l = line.rstrip()
1579 l = line.rstrip()
1580 l = l[10:].split(' ')
1580 l = l[10:].split(' ')
1581 qpp = [bin(x) for x in l]
1581 qpp = [bin(x) for x in l]
1582 elif datastart is not None:
1582 elif datastart is not None:
1583 l = line.rstrip()
1583 l = line.rstrip()
1584 n, name = l.split(':', 1)
1584 n, name = l.split(':', 1)
1585 if n:
1585 if n:
1586 applied.append(statusentry(bin(n), name))
1586 applied.append(statusentry(bin(n), name))
1587 else:
1587 else:
1588 series.append(l)
1588 series.append(l)
1589 if datastart is None:
1589 if datastart is None:
1590 self.ui.warn(_("No saved patch data found\n"))
1590 self.ui.warn(_("No saved patch data found\n"))
1591 return 1
1591 return 1
1592 self.ui.warn(_("restoring status: %s\n") % lines[0])
1592 self.ui.warn(_("restoring status: %s\n") % lines[0])
1593 self.full_series = series
1593 self.full_series = series
1594 self.applied = applied
1594 self.applied = applied
1595 self.parse_series()
1595 self.parse_series()
1596 self.series_dirty = 1
1596 self.series_dirty = 1
1597 self.applied_dirty = 1
1597 self.applied_dirty = 1
1598 heads = repo.changelog.heads()
1598 heads = repo.changelog.heads()
1599 if delete:
1599 if delete:
1600 if rev not in heads:
1600 if rev not in heads:
1601 self.ui.warn(_("save entry has children, leaving it alone\n"))
1601 self.ui.warn(_("save entry has children, leaving it alone\n"))
1602 else:
1602 else:
1603 self.ui.warn(_("removing save entry %s\n") % short(rev))
1603 self.ui.warn(_("removing save entry %s\n") % short(rev))
1604 pp = repo.dirstate.parents()
1604 pp = repo.dirstate.parents()
1605 if rev in pp:
1605 if rev in pp:
1606 update = True
1606 update = True
1607 else:
1607 else:
1608 update = False
1608 update = False
1609 self.strip(repo, [rev], update=update, backup='strip')
1609 self.strip(repo, [rev], update=update, backup='strip')
1610 if qpp:
1610 if qpp:
1611 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1611 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1612 (short(qpp[0]), short(qpp[1])))
1612 (short(qpp[0]), short(qpp[1])))
1613 if qupdate:
1613 if qupdate:
1614 self.ui.status(_("updating queue directory\n"))
1614 self.ui.status(_("updating queue directory\n"))
1615 r = self.qrepo()
1615 r = self.qrepo()
1616 if not r:
1616 if not r:
1617 self.ui.warn(_("Unable to load queue repository\n"))
1617 self.ui.warn(_("Unable to load queue repository\n"))
1618 return 1
1618 return 1
1619 hg.clean(r, qpp[0])
1619 hg.clean(r, qpp[0])
1620
1620
1621 def save(self, repo, msg=None):
1621 def save(self, repo, msg=None):
1622 if not self.applied:
1622 if not self.applied:
1623 self.ui.warn(_("save: no patches applied, exiting\n"))
1623 self.ui.warn(_("save: no patches applied, exiting\n"))
1624 return 1
1624 return 1
1625 if self.issaveline(self.applied[-1]):
1625 if self.issaveline(self.applied[-1]):
1626 self.ui.warn(_("status is already saved\n"))
1626 self.ui.warn(_("status is already saved\n"))
1627 return 1
1627 return 1
1628
1628
1629 if not msg:
1629 if not msg:
1630 msg = _("hg patches saved state")
1630 msg = _("hg patches saved state")
1631 else:
1631 else:
1632 msg = "hg patches: " + msg.rstrip('\r\n')
1632 msg = "hg patches: " + msg.rstrip('\r\n')
1633 r = self.qrepo()
1633 r = self.qrepo()
1634 if r:
1634 if r:
1635 pp = r.dirstate.parents()
1635 pp = r.dirstate.parents()
1636 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1636 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1637 msg += "\n\nPatch Data:\n"
1637 msg += "\n\nPatch Data:\n"
1638 msg += ''.join('%s\n' % x for x in self.applied)
1638 msg += ''.join('%s\n' % x for x in self.applied)
1639 msg += ''.join(':%s\n' % x for x in self.full_series)
1639 msg += ''.join(':%s\n' % x for x in self.full_series)
1640 n = repo.commit(msg, force=True)
1640 n = repo.commit(msg, force=True)
1641 if not n:
1641 if not n:
1642 self.ui.warn(_("repo commit failed\n"))
1642 self.ui.warn(_("repo commit failed\n"))
1643 return 1
1643 return 1
1644 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1644 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1645 self.applied_dirty = 1
1645 self.applied_dirty = 1
1646 self.removeundo(repo)
1646 self.removeundo(repo)
1647
1647
1648 def full_series_end(self):
1648 def full_series_end(self):
1649 if self.applied:
1649 if self.applied:
1650 p = self.applied[-1].name
1650 p = self.applied[-1].name
1651 end = self.find_series(p)
1651 end = self.find_series(p)
1652 if end is None:
1652 if end is None:
1653 return len(self.full_series)
1653 return len(self.full_series)
1654 return end + 1
1654 return end + 1
1655 return 0
1655 return 0
1656
1656
1657 def series_end(self, all_patches=False):
1657 def series_end(self, all_patches=False):
1658 """If all_patches is False, return the index of the next pushable patch
1658 """If all_patches is False, return the index of the next pushable patch
1659 in the series, or the series length. If all_patches is True, return the
1659 in the series, or the series length. If all_patches is True, return the
1660 index of the first patch past the last applied one.
1660 index of the first patch past the last applied one.
1661 """
1661 """
1662 end = 0
1662 end = 0
1663 def next(start):
1663 def next(start):
1664 if all_patches or start >= len(self.series):
1664 if all_patches or start >= len(self.series):
1665 return start
1665 return start
1666 for i in xrange(start, len(self.series)):
1666 for i in xrange(start, len(self.series)):
1667 p, reason = self.pushable(i)
1667 p, reason = self.pushable(i)
1668 if p:
1668 if p:
1669 break
1669 break
1670 self.explain_pushable(i)
1670 self.explain_pushable(i)
1671 return i
1671 return i
1672 if self.applied:
1672 if self.applied:
1673 p = self.applied[-1].name
1673 p = self.applied[-1].name
1674 try:
1674 try:
1675 end = self.series.index(p)
1675 end = self.series.index(p)
1676 except ValueError:
1676 except ValueError:
1677 return 0
1677 return 0
1678 return next(end + 1)
1678 return next(end + 1)
1679 return next(end)
1679 return next(end)
1680
1680
1681 def appliedname(self, index):
1681 def appliedname(self, index):
1682 pname = self.applied[index].name
1682 pname = self.applied[index].name
1683 if not self.ui.verbose:
1683 if not self.ui.verbose:
1684 p = pname
1684 p = pname
1685 else:
1685 else:
1686 p = str(self.series.index(pname)) + " " + pname
1686 p = str(self.series.index(pname)) + " " + pname
1687 return p
1687 return p
1688
1688
1689 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1689 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1690 force=None, git=False):
1690 force=None, git=False):
1691 def checkseries(patchname):
1691 def checkseries(patchname):
1692 if patchname in self.series:
1692 if patchname in self.series:
1693 raise util.Abort(_('patch %s is already in the series file')
1693 raise util.Abort(_('patch %s is already in the series file')
1694 % patchname)
1694 % patchname)
1695 def checkfile(patchname):
1695 def checkfile(patchname):
1696 if not force and os.path.exists(self.join(patchname)):
1696 if not force and os.path.exists(self.join(patchname)):
1697 raise util.Abort(_('patch "%s" already exists')
1697 raise util.Abort(_('patch "%s" already exists')
1698 % patchname)
1698 % patchname)
1699
1699
1700 if rev:
1700 if rev:
1701 if files:
1701 if files:
1702 raise util.Abort(_('option "-r" not valid when importing '
1702 raise util.Abort(_('option "-r" not valid when importing '
1703 'files'))
1703 'files'))
1704 rev = cmdutil.revrange(repo, rev)
1704 rev = cmdutil.revrange(repo, rev)
1705 rev.sort(reverse=True)
1705 rev.sort(reverse=True)
1706 if (len(files) > 1 or len(rev) > 1) and patchname:
1706 if (len(files) > 1 or len(rev) > 1) and patchname:
1707 raise util.Abort(_('option "-n" not valid when importing multiple '
1707 raise util.Abort(_('option "-n" not valid when importing multiple '
1708 'patches'))
1708 'patches'))
1709 if rev:
1709 if rev:
1710 # If mq patches are applied, we can only import revisions
1710 # If mq patches are applied, we can only import revisions
1711 # that form a linear path to qbase.
1711 # that form a linear path to qbase.
1712 # Otherwise, they should form a linear path to a head.
1712 # Otherwise, they should form a linear path to a head.
1713 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1713 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1714 if len(heads) > 1:
1714 if len(heads) > 1:
1715 raise util.Abort(_('revision %d is the root of more than one '
1715 raise util.Abort(_('revision %d is the root of more than one '
1716 'branch') % rev[-1])
1716 'branch') % rev[-1])
1717 if self.applied:
1717 if self.applied:
1718 base = repo.changelog.node(rev[0])
1718 base = repo.changelog.node(rev[0])
1719 if base in [n.node for n in self.applied]:
1719 if base in [n.node for n in self.applied]:
1720 raise util.Abort(_('revision %d is already managed')
1720 raise util.Abort(_('revision %d is already managed')
1721 % rev[0])
1721 % rev[0])
1722 if heads != [self.applied[-1].node]:
1722 if heads != [self.applied[-1].node]:
1723 raise util.Abort(_('revision %d is not the parent of '
1723 raise util.Abort(_('revision %d is not the parent of '
1724 'the queue') % rev[0])
1724 'the queue') % rev[0])
1725 base = repo.changelog.rev(self.applied[0].node)
1725 base = repo.changelog.rev(self.applied[0].node)
1726 lastparent = repo.changelog.parentrevs(base)[0]
1726 lastparent = repo.changelog.parentrevs(base)[0]
1727 else:
1727 else:
1728 if heads != [repo.changelog.node(rev[0])]:
1728 if heads != [repo.changelog.node(rev[0])]:
1729 raise util.Abort(_('revision %d has unmanaged children')
1729 raise util.Abort(_('revision %d has unmanaged children')
1730 % rev[0])
1730 % rev[0])
1731 lastparent = None
1731 lastparent = None
1732
1732
1733 diffopts = self.diffopts({'git': git})
1733 diffopts = self.diffopts({'git': git})
1734 for r in rev:
1734 for r in rev:
1735 p1, p2 = repo.changelog.parentrevs(r)
1735 p1, p2 = repo.changelog.parentrevs(r)
1736 n = repo.changelog.node(r)
1736 n = repo.changelog.node(r)
1737 if p2 != nullrev:
1737 if p2 != nullrev:
1738 raise util.Abort(_('cannot import merge revision %d') % r)
1738 raise util.Abort(_('cannot import merge revision %d') % r)
1739 if lastparent and lastparent != r:
1739 if lastparent and lastparent != r:
1740 raise util.Abort(_('revision %d is not the parent of %d')
1740 raise util.Abort(_('revision %d is not the parent of %d')
1741 % (r, lastparent))
1741 % (r, lastparent))
1742 lastparent = p1
1742 lastparent = p1
1743
1743
1744 if not patchname:
1744 if not patchname:
1745 patchname = normname('%d.diff' % r)
1745 patchname = normname('%d.diff' % r)
1746 self.check_reserved_name(patchname)
1746 self.check_reserved_name(patchname)
1747 checkseries(patchname)
1747 checkseries(patchname)
1748 checkfile(patchname)
1748 checkfile(patchname)
1749 self.full_series.insert(0, patchname)
1749 self.full_series.insert(0, patchname)
1750
1750
1751 patchf = self.opener(patchname, "w")
1751 patchf = self.opener(patchname, "w")
1752 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1752 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1753 patchf.close()
1753 patchf.close()
1754
1754
1755 se = statusentry(n, patchname)
1755 se = statusentry(n, patchname)
1756 self.applied.insert(0, se)
1756 self.applied.insert(0, se)
1757
1757
1758 self.added.append(patchname)
1758 self.added.append(patchname)
1759 patchname = None
1759 patchname = None
1760 self.parse_series()
1760 self.parse_series()
1761 self.applied_dirty = 1
1761 self.applied_dirty = 1
1762 self.series_dirty = True
1762 self.series_dirty = True
1763
1763
1764 for i, filename in enumerate(files):
1764 for i, filename in enumerate(files):
1765 if existing:
1765 if existing:
1766 if filename == '-':
1766 if filename == '-':
1767 raise util.Abort(_('-e is incompatible with import from -'))
1767 raise util.Abort(_('-e is incompatible with import from -'))
1768 filename = normname(filename)
1768 filename = normname(filename)
1769 self.check_reserved_name(filename)
1769 self.check_reserved_name(filename)
1770 originpath = self.join(filename)
1770 originpath = self.join(filename)
1771 if not os.path.isfile(originpath):
1771 if not os.path.isfile(originpath):
1772 raise util.Abort(_("patch %s does not exist") % filename)
1772 raise util.Abort(_("patch %s does not exist") % filename)
1773
1773
1774 if patchname:
1774 if patchname:
1775 self.check_reserved_name(patchname)
1775 self.check_reserved_name(patchname)
1776 checkfile(patchname)
1776 checkfile(patchname)
1777
1777
1778 self.ui.write(_('renaming %s to %s\n')
1778 self.ui.write(_('renaming %s to %s\n')
1779 % (filename, patchname))
1779 % (filename, patchname))
1780 util.rename(originpath, self.join(patchname))
1780 util.rename(originpath, self.join(patchname))
1781 else:
1781 else:
1782 patchname = filename
1782 patchname = filename
1783
1783
1784 else:
1784 else:
1785 try:
1785 try:
1786 if filename == '-':
1786 if filename == '-':
1787 if not patchname:
1787 if not patchname:
1788 raise util.Abort(
1788 raise util.Abort(
1789 _('need --name to import a patch from -'))
1789 _('need --name to import a patch from -'))
1790 text = sys.stdin.read()
1790 text = sys.stdin.read()
1791 else:
1791 else:
1792 fp = url.open(self.ui, filename)
1792 fp = url.open(self.ui, filename)
1793 text = fp.read()
1793 text = fp.read()
1794 fp.close()
1794 fp.close()
1795 except (OSError, IOError):
1795 except (OSError, IOError):
1796 raise util.Abort(_("unable to read file %s") % filename)
1796 raise util.Abort(_("unable to read file %s") % filename)
1797 if not patchname:
1797 if not patchname:
1798 patchname = normname(os.path.basename(filename))
1798 patchname = normname(os.path.basename(filename))
1799 self.check_reserved_name(patchname)
1799 self.check_reserved_name(patchname)
1800 checkfile(patchname)
1800 checkfile(patchname)
1801 patchf = self.opener(patchname, "w")
1801 patchf = self.opener(patchname, "w")
1802 patchf.write(text)
1802 patchf.write(text)
1803 patchf.close()
1803 patchf.close()
1804 if not force:
1804 if not force:
1805 checkseries(patchname)
1805 checkseries(patchname)
1806 if patchname not in self.series:
1806 if patchname not in self.series:
1807 index = self.full_series_end() + i
1807 index = self.full_series_end() + i
1808 self.full_series[index:index] = [patchname]
1808 self.full_series[index:index] = [patchname]
1809 self.parse_series()
1809 self.parse_series()
1810 self.series_dirty = True
1810 self.series_dirty = True
1811 self.ui.warn(_("adding %s to series file\n") % patchname)
1811 self.ui.warn(_("adding %s to series file\n") % patchname)
1812 self.added.append(patchname)
1812 self.added.append(patchname)
1813 patchname = None
1813 patchname = None
1814
1814
1815 self.removeundo(repo)
1815 self.removeundo(repo)
1816
1816
1817 def delete(ui, repo, *patches, **opts):
1817 def delete(ui, repo, *patches, **opts):
1818 """remove patches from queue
1818 """remove patches from queue
1819
1819
1820 The patches must not be applied, and at least one patch is required. With
1820 The patches must not be applied, and at least one patch is required. With
1821 -k/--keep, the patch files are preserved in the patch directory.
1821 -k/--keep, the patch files are preserved in the patch directory.
1822
1822
1823 To stop managing a patch and move it into permanent history,
1823 To stop managing a patch and move it into permanent history,
1824 use the :hg:`qfinish` command."""
1824 use the :hg:`qfinish` command."""
1825 q = repo.mq
1825 q = repo.mq
1826 q.delete(repo, patches, opts)
1826 q.delete(repo, patches, opts)
1827 q.save_dirty()
1827 q.save_dirty()
1828 return 0
1828 return 0
1829
1829
1830 def applied(ui, repo, patch=None, **opts):
1830 def applied(ui, repo, patch=None, **opts):
1831 """print the patches already applied
1831 """print the patches already applied
1832
1832
1833 Returns 0 on success."""
1833 Returns 0 on success."""
1834
1834
1835 q = repo.mq
1835 q = repo.mq
1836
1836
1837 if patch:
1837 if patch:
1838 if patch not in q.series:
1838 if patch not in q.series:
1839 raise util.Abort(_("patch %s is not in series file") % patch)
1839 raise util.Abort(_("patch %s is not in series file") % patch)
1840 end = q.series.index(patch) + 1
1840 end = q.series.index(patch) + 1
1841 else:
1841 else:
1842 end = q.series_end(True)
1842 end = q.series_end(True)
1843
1843
1844 if opts.get('last') and not end:
1844 if opts.get('last') and not end:
1845 ui.write(_("no patches applied\n"))
1845 ui.write(_("no patches applied\n"))
1846 return 1
1846 return 1
1847 elif opts.get('last') and end == 1:
1847 elif opts.get('last') and end == 1:
1848 ui.write(_("only one patch applied\n"))
1848 ui.write(_("only one patch applied\n"))
1849 return 1
1849 return 1
1850 elif opts.get('last'):
1850 elif opts.get('last'):
1851 start = end - 2
1851 start = end - 2
1852 end = 1
1852 end = 1
1853 else:
1853 else:
1854 start = 0
1854 start = 0
1855
1855
1856 q.qseries(repo, length=end, start=start, status='A',
1856 q.qseries(repo, length=end, start=start, status='A',
1857 summary=opts.get('summary'))
1857 summary=opts.get('summary'))
1858
1858
1859
1859
1860 def unapplied(ui, repo, patch=None, **opts):
1860 def unapplied(ui, repo, patch=None, **opts):
1861 """print the patches not yet applied
1861 """print the patches not yet applied
1862
1862
1863 Returns 0 on success."""
1863 Returns 0 on success."""
1864
1864
1865 q = repo.mq
1865 q = repo.mq
1866 if patch:
1866 if patch:
1867 if patch not in q.series:
1867 if patch not in q.series:
1868 raise util.Abort(_("patch %s is not in series file") % patch)
1868 raise util.Abort(_("patch %s is not in series file") % patch)
1869 start = q.series.index(patch) + 1
1869 start = q.series.index(patch) + 1
1870 else:
1870 else:
1871 start = q.series_end(True)
1871 start = q.series_end(True)
1872
1872
1873 if start == len(q.series) and opts.get('first'):
1873 if start == len(q.series) and opts.get('first'):
1874 ui.write(_("all patches applied\n"))
1874 ui.write(_("all patches applied\n"))
1875 return 1
1875 return 1
1876
1876
1877 length = opts.get('first') and 1 or None
1877 length = opts.get('first') and 1 or None
1878 q.qseries(repo, start=start, length=length, status='U',
1878 q.qseries(repo, start=start, length=length, status='U',
1879 summary=opts.get('summary'))
1879 summary=opts.get('summary'))
1880
1880
1881 def qimport(ui, repo, *filename, **opts):
1881 def qimport(ui, repo, *filename, **opts):
1882 """import a patch
1882 """import a patch
1883
1883
1884 The patch is inserted into the series after the last applied
1884 The patch is inserted into the series after the last applied
1885 patch. If no patches have been applied, qimport prepends the patch
1885 patch. If no patches have been applied, qimport prepends the patch
1886 to the series.
1886 to the series.
1887
1887
1888 The patch will have the same name as its source file unless you
1888 The patch will have the same name as its source file unless you
1889 give it a new one with -n/--name.
1889 give it a new one with -n/--name.
1890
1890
1891 You can register an existing patch inside the patch directory with
1891 You can register an existing patch inside the patch directory with
1892 the -e/--existing flag.
1892 the -e/--existing flag.
1893
1893
1894 With -f/--force, an existing patch of the same name will be
1894 With -f/--force, an existing patch of the same name will be
1895 overwritten.
1895 overwritten.
1896
1896
1897 An existing changeset may be placed under mq control with -r/--rev
1897 An existing changeset may be placed under mq control with -r/--rev
1898 (e.g. qimport --rev tip -n patch will place tip under mq control).
1898 (e.g. qimport --rev tip -n patch will place tip under mq control).
1899 With -g/--git, patches imported with --rev will use the git diff
1899 With -g/--git, patches imported with --rev will use the git diff
1900 format. See the diffs help topic for information on why this is
1900 format. See the diffs help topic for information on why this is
1901 important for preserving rename/copy information and permission
1901 important for preserving rename/copy information and permission
1902 changes. Use :hg:`qfinish` to remove changesets from mq control.
1902 changes. Use :hg:`qfinish` to remove changesets from mq control.
1903
1903
1904 To import a patch from standard input, pass - as the patch file.
1904 To import a patch from standard input, pass - as the patch file.
1905 When importing from standard input, a patch name must be specified
1905 When importing from standard input, a patch name must be specified
1906 using the --name flag.
1906 using the --name flag.
1907
1907
1908 To import an existing patch while renaming it::
1908 To import an existing patch while renaming it::
1909
1909
1910 hg qimport -e existing-patch -n new-name
1910 hg qimport -e existing-patch -n new-name
1911
1911
1912 Returns 0 if import succeeded.
1912 Returns 0 if import succeeded.
1913 """
1913 """
1914 q = repo.mq
1914 q = repo.mq
1915 try:
1915 try:
1916 q.qimport(repo, filename, patchname=opts.get('name'),
1916 q.qimport(repo, filename, patchname=opts.get('name'),
1917 existing=opts.get('existing'), force=opts.get('force'),
1917 existing=opts.get('existing'), force=opts.get('force'),
1918 rev=opts.get('rev'), git=opts.get('git'))
1918 rev=opts.get('rev'), git=opts.get('git'))
1919 finally:
1919 finally:
1920 q.save_dirty()
1920 q.save_dirty()
1921
1921
1922 if opts.get('push') and not opts.get('rev'):
1922 if opts.get('push') and not opts.get('rev'):
1923 return q.push(repo, None)
1923 return q.push(repo, None)
1924 return 0
1924 return 0
1925
1925
1926 def qinit(ui, repo, create):
1926 def qinit(ui, repo, create):
1927 """initialize a new queue repository
1927 """initialize a new queue repository
1928
1928
1929 This command also creates a series file for ordering patches, and
1929 This command also creates a series file for ordering patches, and
1930 an mq-specific .hgignore file in the queue repository, to exclude
1930 an mq-specific .hgignore file in the queue repository, to exclude
1931 the status and guards files (these contain mostly transient state).
1931 the status and guards files (these contain mostly transient state).
1932
1932
1933 Returns 0 if initialization succeeded."""
1933 Returns 0 if initialization succeeded."""
1934 q = repo.mq
1934 q = repo.mq
1935 r = q.init(repo, create)
1935 r = q.init(repo, create)
1936 q.save_dirty()
1936 q.save_dirty()
1937 if r:
1937 if r:
1938 if not os.path.exists(r.wjoin('.hgignore')):
1938 if not os.path.exists(r.wjoin('.hgignore')):
1939 fp = r.wopener('.hgignore', 'w')
1939 fp = r.wopener('.hgignore', 'w')
1940 fp.write('^\\.hg\n')
1940 fp.write('^\\.hg\n')
1941 fp.write('^\\.mq\n')
1941 fp.write('^\\.mq\n')
1942 fp.write('syntax: glob\n')
1942 fp.write('syntax: glob\n')
1943 fp.write('status\n')
1943 fp.write('status\n')
1944 fp.write('guards\n')
1944 fp.write('guards\n')
1945 fp.close()
1945 fp.close()
1946 if not os.path.exists(r.wjoin('series')):
1946 if not os.path.exists(r.wjoin('series')):
1947 r.wopener('series', 'w').close()
1947 r.wopener('series', 'w').close()
1948 r[None].add(['.hgignore', 'series'])
1948 r[None].add(['.hgignore', 'series'])
1949 commands.add(ui, r)
1949 commands.add(ui, r)
1950 return 0
1950 return 0
1951
1951
1952 def init(ui, repo, **opts):
1952 def init(ui, repo, **opts):
1953 """init a new queue repository (DEPRECATED)
1953 """init a new queue repository (DEPRECATED)
1954
1954
1955 The queue repository is unversioned by default. If
1955 The queue repository is unversioned by default. If
1956 -c/--create-repo is specified, qinit will create a separate nested
1956 -c/--create-repo is specified, qinit will create a separate nested
1957 repository for patches (qinit -c may also be run later to convert
1957 repository for patches (qinit -c may also be run later to convert
1958 an unversioned patch repository into a versioned one). You can use
1958 an unversioned patch repository into a versioned one). You can use
1959 qcommit to commit changes to this queue repository.
1959 qcommit to commit changes to this queue repository.
1960
1960
1961 This command is deprecated. Without -c, it's implied by other relevant
1961 This command is deprecated. Without -c, it's implied by other relevant
1962 commands. With -c, use :hg:`init --mq` instead."""
1962 commands. With -c, use :hg:`init --mq` instead."""
1963 return qinit(ui, repo, create=opts.get('create_repo'))
1963 return qinit(ui, repo, create=opts.get('create_repo'))
1964
1964
1965 def clone(ui, source, dest=None, **opts):
1965 def clone(ui, source, dest=None, **opts):
1966 '''clone main and patch repository at same time
1966 '''clone main and patch repository at same time
1967
1967
1968 If source is local, destination will have no patches applied. If
1968 If source is local, destination will have no patches applied. If
1969 source is remote, this command can not check if patches are
1969 source is remote, this command can not check if patches are
1970 applied in source, so cannot guarantee that patches are not
1970 applied in source, so cannot guarantee that patches are not
1971 applied in destination. If you clone remote repository, be sure
1971 applied in destination. If you clone remote repository, be sure
1972 before that it has no patches applied.
1972 before that it has no patches applied.
1973
1973
1974 Source patch repository is looked for in <src>/.hg/patches by
1974 Source patch repository is looked for in <src>/.hg/patches by
1975 default. Use -p <url> to change.
1975 default. Use -p <url> to change.
1976
1976
1977 The patch directory must be a nested Mercurial repository, as
1977 The patch directory must be a nested Mercurial repository, as
1978 would be created by :hg:`init --mq`.
1978 would be created by :hg:`init --mq`.
1979
1979
1980 Return 0 on success.
1980 Return 0 on success.
1981 '''
1981 '''
1982 def patchdir(repo):
1982 def patchdir(repo):
1983 url = repo.url()
1983 url = repo.url()
1984 if url.endswith('/'):
1984 if url.endswith('/'):
1985 url = url[:-1]
1985 url = url[:-1]
1986 return url + '/.hg/patches'
1986 return url + '/.hg/patches'
1987 if dest is None:
1987 if dest is None:
1988 dest = hg.defaultdest(source)
1988 dest = hg.defaultdest(source)
1989 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
1989 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
1990 if opts.get('patches'):
1990 if opts.get('patches'):
1991 patchespath = ui.expandpath(opts.get('patches'))
1991 patchespath = ui.expandpath(opts.get('patches'))
1992 else:
1992 else:
1993 patchespath = patchdir(sr)
1993 patchespath = patchdir(sr)
1994 try:
1994 try:
1995 hg.repository(ui, patchespath)
1995 hg.repository(ui, patchespath)
1996 except error.RepoError:
1996 except error.RepoError:
1997 raise util.Abort(_('versioned patch repository not found'
1997 raise util.Abort(_('versioned patch repository not found'
1998 ' (see init --mq)'))
1998 ' (see init --mq)'))
1999 qbase, destrev = None, None
1999 qbase, destrev = None, None
2000 if sr.local():
2000 if sr.local():
2001 if sr.mq.applied:
2001 if sr.mq.applied:
2002 qbase = sr.mq.applied[0].node
2002 qbase = sr.mq.applied[0].node
2003 if not hg.islocal(dest):
2003 if not hg.islocal(dest):
2004 heads = set(sr.heads())
2004 heads = set(sr.heads())
2005 destrev = list(heads.difference(sr.heads(qbase)))
2005 destrev = list(heads.difference(sr.heads(qbase)))
2006 destrev.append(sr.changelog.parents(qbase)[0])
2006 destrev.append(sr.changelog.parents(qbase)[0])
2007 elif sr.capable('lookup'):
2007 elif sr.capable('lookup'):
2008 try:
2008 try:
2009 qbase = sr.lookup('qbase')
2009 qbase = sr.lookup('qbase')
2010 except error.RepoError:
2010 except error.RepoError:
2011 pass
2011 pass
2012 ui.note(_('cloning main repository\n'))
2012 ui.note(_('cloning main repository\n'))
2013 sr, dr = hg.clone(ui, sr.url(), dest,
2013 sr, dr = hg.clone(ui, sr.url(), dest,
2014 pull=opts.get('pull'),
2014 pull=opts.get('pull'),
2015 rev=destrev,
2015 rev=destrev,
2016 update=False,
2016 update=False,
2017 stream=opts.get('uncompressed'))
2017 stream=opts.get('uncompressed'))
2018 ui.note(_('cloning patch repository\n'))
2018 ui.note(_('cloning patch repository\n'))
2019 hg.clone(ui, opts.get('patches') or patchdir(sr), patchdir(dr),
2019 hg.clone(ui, opts.get('patches') or patchdir(sr), patchdir(dr),
2020 pull=opts.get('pull'), update=not opts.get('noupdate'),
2020 pull=opts.get('pull'), update=not opts.get('noupdate'),
2021 stream=opts.get('uncompressed'))
2021 stream=opts.get('uncompressed'))
2022 if dr.local():
2022 if dr.local():
2023 if qbase:
2023 if qbase:
2024 ui.note(_('stripping applied patches from destination '
2024 ui.note(_('stripping applied patches from destination '
2025 'repository\n'))
2025 'repository\n'))
2026 dr.mq.strip(dr, [qbase], update=False, backup=None)
2026 dr.mq.strip(dr, [qbase], update=False, backup=None)
2027 if not opts.get('noupdate'):
2027 if not opts.get('noupdate'):
2028 ui.note(_('updating destination repository\n'))
2028 ui.note(_('updating destination repository\n'))
2029 hg.update(dr, dr.changelog.tip())
2029 hg.update(dr, dr.changelog.tip())
2030
2030
2031 def commit(ui, repo, *pats, **opts):
2031 def commit(ui, repo, *pats, **opts):
2032 """commit changes in the queue repository (DEPRECATED)
2032 """commit changes in the queue repository (DEPRECATED)
2033
2033
2034 This command is deprecated; use :hg:`commit --mq` instead."""
2034 This command is deprecated; use :hg:`commit --mq` instead."""
2035 q = repo.mq
2035 q = repo.mq
2036 r = q.qrepo()
2036 r = q.qrepo()
2037 if not r:
2037 if not r:
2038 raise util.Abort('no queue repository')
2038 raise util.Abort('no queue repository')
2039 commands.commit(r.ui, r, *pats, **opts)
2039 commands.commit(r.ui, r, *pats, **opts)
2040
2040
2041 def series(ui, repo, **opts):
2041 def series(ui, repo, **opts):
2042 """print the entire series file
2042 """print the entire series file
2043
2043
2044 Returns 0 on success."""
2044 Returns 0 on success."""
2045 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2045 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2046 return 0
2046 return 0
2047
2047
2048 def top(ui, repo, **opts):
2048 def top(ui, repo, **opts):
2049 """print the name of the current patch
2049 """print the name of the current patch
2050
2050
2051 Returns 0 on success."""
2051 Returns 0 on success."""
2052 q = repo.mq
2052 q = repo.mq
2053 t = q.applied and q.series_end(True) or 0
2053 t = q.applied and q.series_end(True) or 0
2054 if t:
2054 if t:
2055 q.qseries(repo, start=t - 1, length=1, status='A',
2055 q.qseries(repo, start=t - 1, length=1, status='A',
2056 summary=opts.get('summary'))
2056 summary=opts.get('summary'))
2057 else:
2057 else:
2058 ui.write(_("no patches applied\n"))
2058 ui.write(_("no patches applied\n"))
2059 return 1
2059 return 1
2060
2060
2061 def next(ui, repo, **opts):
2061 def next(ui, repo, **opts):
2062 """print the name of the next patch
2062 """print the name of the next patch
2063
2063
2064 Returns 0 on success."""
2064 Returns 0 on success."""
2065 q = repo.mq
2065 q = repo.mq
2066 end = q.series_end()
2066 end = q.series_end()
2067 if end == len(q.series):
2067 if end == len(q.series):
2068 ui.write(_("all patches applied\n"))
2068 ui.write(_("all patches applied\n"))
2069 return 1
2069 return 1
2070 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2070 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2071
2071
2072 def prev(ui, repo, **opts):
2072 def prev(ui, repo, **opts):
2073 """print the name of the previous patch
2073 """print the name of the previous patch
2074
2074
2075 Returns 0 on success."""
2075 Returns 0 on success."""
2076 q = repo.mq
2076 q = repo.mq
2077 l = len(q.applied)
2077 l = len(q.applied)
2078 if l == 1:
2078 if l == 1:
2079 ui.write(_("only one patch applied\n"))
2079 ui.write(_("only one patch applied\n"))
2080 return 1
2080 return 1
2081 if not l:
2081 if not l:
2082 ui.write(_("no patches applied\n"))
2082 ui.write(_("no patches applied\n"))
2083 return 1
2083 return 1
2084 q.qseries(repo, start=l - 2, length=1, status='A',
2084 q.qseries(repo, start=l - 2, length=1, status='A',
2085 summary=opts.get('summary'))
2085 summary=opts.get('summary'))
2086
2086
2087 def setupheaderopts(ui, opts):
2087 def setupheaderopts(ui, opts):
2088 if not opts.get('user') and opts.get('currentuser'):
2088 if not opts.get('user') and opts.get('currentuser'):
2089 opts['user'] = ui.username()
2089 opts['user'] = ui.username()
2090 if not opts.get('date') and opts.get('currentdate'):
2090 if not opts.get('date') and opts.get('currentdate'):
2091 opts['date'] = "%d %d" % util.makedate()
2091 opts['date'] = "%d %d" % util.makedate()
2092
2092
2093 def new(ui, repo, patch, *args, **opts):
2093 def new(ui, repo, patch, *args, **opts):
2094 """create a new patch
2094 """create a new patch
2095
2095
2096 qnew creates a new patch on top of the currently-applied patch (if
2096 qnew creates a new patch on top of the currently-applied patch (if
2097 any). The patch will be initialized with any outstanding changes
2097 any). The patch will be initialized with any outstanding changes
2098 in the working directory. You may also use -I/--include,
2098 in the working directory. You may also use -I/--include,
2099 -X/--exclude, and/or a list of files after the patch name to add
2099 -X/--exclude, and/or a list of files after the patch name to add
2100 only changes to matching files to the new patch, leaving the rest
2100 only changes to matching files to the new patch, leaving the rest
2101 as uncommitted modifications.
2101 as uncommitted modifications.
2102
2102
2103 -u/--user and -d/--date can be used to set the (given) user and
2103 -u/--user and -d/--date can be used to set the (given) user and
2104 date, respectively. -U/--currentuser and -D/--currentdate set user
2104 date, respectively. -U/--currentuser and -D/--currentdate set user
2105 to current user and date to current date.
2105 to current user and date to current date.
2106
2106
2107 -e/--edit, -m/--message or -l/--logfile set the patch header as
2107 -e/--edit, -m/--message or -l/--logfile set the patch header as
2108 well as the commit message. If none is specified, the header is
2108 well as the commit message. If none is specified, the header is
2109 empty and the commit message is '[mq]: PATCH'.
2109 empty and the commit message is '[mq]: PATCH'.
2110
2110
2111 Use the -g/--git option to keep the patch in the git extended diff
2111 Use the -g/--git option to keep the patch in the git extended diff
2112 format. Read the diffs help topic for more information on why this
2112 format. Read the diffs help topic for more information on why this
2113 is important for preserving permission changes and copy/rename
2113 is important for preserving permission changes and copy/rename
2114 information.
2114 information.
2115
2115
2116 Returns 0 on successful creation of a new patch.
2116 Returns 0 on successful creation of a new patch.
2117 """
2117 """
2118 msg = cmdutil.logmessage(opts)
2118 msg = cmdutil.logmessage(opts)
2119 def getmsg():
2119 def getmsg():
2120 return ui.edit(msg, opts.get('user') or ui.username())
2120 return ui.edit(msg, opts.get('user') or ui.username())
2121 q = repo.mq
2121 q = repo.mq
2122 opts['msg'] = msg
2122 opts['msg'] = msg
2123 if opts.get('edit'):
2123 if opts.get('edit'):
2124 opts['msg'] = getmsg
2124 opts['msg'] = getmsg
2125 else:
2125 else:
2126 opts['msg'] = msg
2126 opts['msg'] = msg
2127 setupheaderopts(ui, opts)
2127 setupheaderopts(ui, opts)
2128 q.new(repo, patch, *args, **opts)
2128 q.new(repo, patch, *args, **opts)
2129 q.save_dirty()
2129 q.save_dirty()
2130 return 0
2130 return 0
2131
2131
2132 def refresh(ui, repo, *pats, **opts):
2132 def refresh(ui, repo, *pats, **opts):
2133 """update the current patch
2133 """update the current patch
2134
2134
2135 If any file patterns are provided, the refreshed patch will
2135 If any file patterns are provided, the refreshed patch will
2136 contain only the modifications that match those patterns; the
2136 contain only the modifications that match those patterns; the
2137 remaining modifications will remain in the working directory.
2137 remaining modifications will remain in the working directory.
2138
2138
2139 If -s/--short is specified, files currently included in the patch
2139 If -s/--short is specified, files currently included in the patch
2140 will be refreshed just like matched files and remain in the patch.
2140 will be refreshed just like matched files and remain in the patch.
2141
2141
2142 If -e/--edit is specified, Mercurial will start your configured editor for
2142 If -e/--edit is specified, Mercurial will start your configured editor for
2143 you to enter a message. In case qrefresh fails, you will find a backup of
2143 you to enter a message. In case qrefresh fails, you will find a backup of
2144 your message in ``.hg/last-message.txt``.
2144 your message in ``.hg/last-message.txt``.
2145
2145
2146 hg add/remove/copy/rename work as usual, though you might want to
2146 hg add/remove/copy/rename work as usual, though you might want to
2147 use git-style patches (-g/--git or [diff] git=1) to track copies
2147 use git-style patches (-g/--git or [diff] git=1) to track copies
2148 and renames. See the diffs help topic for more information on the
2148 and renames. See the diffs help topic for more information on the
2149 git diff format.
2149 git diff format.
2150
2150
2151 Returns 0 on success.
2151 Returns 0 on success.
2152 """
2152 """
2153 q = repo.mq
2153 q = repo.mq
2154 message = cmdutil.logmessage(opts)
2154 message = cmdutil.logmessage(opts)
2155 if opts.get('edit'):
2155 if opts.get('edit'):
2156 if not q.applied:
2156 if not q.applied:
2157 ui.write(_("no patches applied\n"))
2157 ui.write(_("no patches applied\n"))
2158 return 1
2158 return 1
2159 if message:
2159 if message:
2160 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2160 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2161 patch = q.applied[-1].name
2161 patch = q.applied[-1].name
2162 ph = patchheader(q.join(patch), q.plainmode)
2162 ph = patchheader(q.join(patch), q.plainmode)
2163 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2163 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2164 # We don't want to lose the patch message if qrefresh fails (issue2062)
2164 # We don't want to lose the patch message if qrefresh fails (issue2062)
2165 msgfile = repo.opener('last-message.txt', 'wb')
2165 msgfile = repo.opener('last-message.txt', 'wb')
2166 msgfile.write(message)
2166 msgfile.write(message)
2167 msgfile.close()
2167 msgfile.close()
2168 setupheaderopts(ui, opts)
2168 setupheaderopts(ui, opts)
2169 ret = q.refresh(repo, pats, msg=message, **opts)
2169 ret = q.refresh(repo, pats, msg=message, **opts)
2170 q.save_dirty()
2170 q.save_dirty()
2171 return ret
2171 return ret
2172
2172
2173 def diff(ui, repo, *pats, **opts):
2173 def diff(ui, repo, *pats, **opts):
2174 """diff of the current patch and subsequent modifications
2174 """diff of the current patch and subsequent modifications
2175
2175
2176 Shows a diff which includes the current patch as well as any
2176 Shows a diff which includes the current patch as well as any
2177 changes which have been made in the working directory since the
2177 changes which have been made in the working directory since the
2178 last refresh (thus showing what the current patch would become
2178 last refresh (thus showing what the current patch would become
2179 after a qrefresh).
2179 after a qrefresh).
2180
2180
2181 Use :hg:`diff` if you only want to see the changes made since the
2181 Use :hg:`diff` if you only want to see the changes made since the
2182 last qrefresh, or :hg:`export qtip` if you want to see changes
2182 last qrefresh, or :hg:`export qtip` if you want to see changes
2183 made by the current patch without including changes made since the
2183 made by the current patch without including changes made since the
2184 qrefresh.
2184 qrefresh.
2185
2185
2186 Returns 0 on success.
2186 Returns 0 on success.
2187 """
2187 """
2188 repo.mq.diff(repo, pats, opts)
2188 repo.mq.diff(repo, pats, opts)
2189 return 0
2189 return 0
2190
2190
2191 def fold(ui, repo, *files, **opts):
2191 def fold(ui, repo, *files, **opts):
2192 """fold the named patches into the current patch
2192 """fold the named patches into the current patch
2193
2193
2194 Patches must not yet be applied. Each patch will be successively
2194 Patches must not yet be applied. Each patch will be successively
2195 applied to the current patch in the order given. If all the
2195 applied to the current patch in the order given. If all the
2196 patches apply successfully, the current patch will be refreshed
2196 patches apply successfully, the current patch will be refreshed
2197 with the new cumulative patch, and the folded patches will be
2197 with the new cumulative patch, and the folded patches will be
2198 deleted. With -k/--keep, the folded patch files will not be
2198 deleted. With -k/--keep, the folded patch files will not be
2199 removed afterwards.
2199 removed afterwards.
2200
2200
2201 The header for each folded patch will be concatenated with the
2201 The header for each folded patch will be concatenated with the
2202 current patch header, separated by a line of ``* * *``.
2202 current patch header, separated by a line of ``* * *``.
2203
2203
2204 Returns 0 on success."""
2204 Returns 0 on success."""
2205
2205
2206 q = repo.mq
2206 q = repo.mq
2207
2207
2208 if not files:
2208 if not files:
2209 raise util.Abort(_('qfold requires at least one patch name'))
2209 raise util.Abort(_('qfold requires at least one patch name'))
2210 if not q.check_toppatch(repo)[0]:
2210 if not q.check_toppatch(repo)[0]:
2211 raise util.Abort(_('no patches applied'))
2211 raise util.Abort(_('no patches applied'))
2212 q.check_localchanges(repo)
2212 q.check_localchanges(repo)
2213
2213
2214 message = cmdutil.logmessage(opts)
2214 message = cmdutil.logmessage(opts)
2215 if opts.get('edit'):
2215 if opts.get('edit'):
2216 if message:
2216 if message:
2217 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2217 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2218
2218
2219 parent = q.lookup('qtip')
2219 parent = q.lookup('qtip')
2220 patches = []
2220 patches = []
2221 messages = []
2221 messages = []
2222 for f in files:
2222 for f in files:
2223 p = q.lookup(f)
2223 p = q.lookup(f)
2224 if p in patches or p == parent:
2224 if p in patches or p == parent:
2225 ui.warn(_('Skipping already folded patch %s\n') % p)
2225 ui.warn(_('Skipping already folded patch %s\n') % p)
2226 if q.isapplied(p):
2226 if q.isapplied(p):
2227 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2227 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2228 patches.append(p)
2228 patches.append(p)
2229
2229
2230 for p in patches:
2230 for p in patches:
2231 if not message:
2231 if not message:
2232 ph = patchheader(q.join(p), q.plainmode)
2232 ph = patchheader(q.join(p), q.plainmode)
2233 if ph.message:
2233 if ph.message:
2234 messages.append(ph.message)
2234 messages.append(ph.message)
2235 pf = q.join(p)
2235 pf = q.join(p)
2236 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2236 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2237 if not patchsuccess:
2237 if not patchsuccess:
2238 raise util.Abort(_('error folding patch %s') % p)
2238 raise util.Abort(_('error folding patch %s') % p)
2239 cmdutil.updatedir(ui, repo, files)
2239 cmdutil.updatedir(ui, repo, files)
2240
2240
2241 if not message:
2241 if not message:
2242 ph = patchheader(q.join(parent), q.plainmode)
2242 ph = patchheader(q.join(parent), q.plainmode)
2243 message, user = ph.message, ph.user
2243 message, user = ph.message, ph.user
2244 for msg in messages:
2244 for msg in messages:
2245 message.append('* * *')
2245 message.append('* * *')
2246 message.extend(msg)
2246 message.extend(msg)
2247 message = '\n'.join(message)
2247 message = '\n'.join(message)
2248
2248
2249 if opts.get('edit'):
2249 if opts.get('edit'):
2250 message = ui.edit(message, user or ui.username())
2250 message = ui.edit(message, user or ui.username())
2251
2251
2252 diffopts = q.patchopts(q.diffopts(), *patches)
2252 diffopts = q.patchopts(q.diffopts(), *patches)
2253 q.refresh(repo, msg=message, git=diffopts.git)
2253 q.refresh(repo, msg=message, git=diffopts.git)
2254 q.delete(repo, patches, opts)
2254 q.delete(repo, patches, opts)
2255 q.save_dirty()
2255 q.save_dirty()
2256
2256
2257 def goto(ui, repo, patch, **opts):
2257 def goto(ui, repo, patch, **opts):
2258 '''push or pop patches until named patch is at top of stack
2258 '''push or pop patches until named patch is at top of stack
2259
2259
2260 Returns 0 on success.'''
2260 Returns 0 on success.'''
2261 q = repo.mq
2261 q = repo.mq
2262 patch = q.lookup(patch)
2262 patch = q.lookup(patch)
2263 if q.isapplied(patch):
2263 if q.isapplied(patch):
2264 ret = q.pop(repo, patch, force=opts.get('force'))
2264 ret = q.pop(repo, patch, force=opts.get('force'))
2265 else:
2265 else:
2266 ret = q.push(repo, patch, force=opts.get('force'))
2266 ret = q.push(repo, patch, force=opts.get('force'))
2267 q.save_dirty()
2267 q.save_dirty()
2268 return ret
2268 return ret
2269
2269
2270 def guard(ui, repo, *args, **opts):
2270 def guard(ui, repo, *args, **opts):
2271 '''set or print guards for a patch
2271 '''set or print guards for a patch
2272
2272
2273 Guards control whether a patch can be pushed. A patch with no
2273 Guards control whether a patch can be pushed. A patch with no
2274 guards is always pushed. A patch with a positive guard ("+foo") is
2274 guards is always pushed. A patch with a positive guard ("+foo") is
2275 pushed only if the :hg:`qselect` command has activated it. A patch with
2275 pushed only if the :hg:`qselect` command has activated it. A patch with
2276 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2276 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2277 has activated it.
2277 has activated it.
2278
2278
2279 With no arguments, print the currently active guards.
2279 With no arguments, print the currently active guards.
2280 With arguments, set guards for the named patch.
2280 With arguments, set guards for the named patch.
2281
2281
2282 .. note::
2282 .. note::
2283 Specifying negative guards now requires '--'.
2283 Specifying negative guards now requires '--'.
2284
2284
2285 To set guards on another patch::
2285 To set guards on another patch::
2286
2286
2287 hg qguard other.patch -- +2.6.17 -stable
2287 hg qguard other.patch -- +2.6.17 -stable
2288
2288
2289 Returns 0 on success.
2289 Returns 0 on success.
2290 '''
2290 '''
2291 def status(idx):
2291 def status(idx):
2292 guards = q.series_guards[idx] or ['unguarded']
2292 guards = q.series_guards[idx] or ['unguarded']
2293 if q.series[idx] in applied:
2293 if q.series[idx] in applied:
2294 state = 'applied'
2294 state = 'applied'
2295 elif q.pushable(idx)[0]:
2295 elif q.pushable(idx)[0]:
2296 state = 'unapplied'
2296 state = 'unapplied'
2297 else:
2297 else:
2298 state = 'guarded'
2298 state = 'guarded'
2299 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2299 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2300 ui.write('%s: ' % ui.label(q.series[idx], label))
2300 ui.write('%s: ' % ui.label(q.series[idx], label))
2301
2301
2302 for i, guard in enumerate(guards):
2302 for i, guard in enumerate(guards):
2303 if guard.startswith('+'):
2303 if guard.startswith('+'):
2304 ui.write(guard, label='qguard.positive')
2304 ui.write(guard, label='qguard.positive')
2305 elif guard.startswith('-'):
2305 elif guard.startswith('-'):
2306 ui.write(guard, label='qguard.negative')
2306 ui.write(guard, label='qguard.negative')
2307 else:
2307 else:
2308 ui.write(guard, label='qguard.unguarded')
2308 ui.write(guard, label='qguard.unguarded')
2309 if i != len(guards) - 1:
2309 if i != len(guards) - 1:
2310 ui.write(' ')
2310 ui.write(' ')
2311 ui.write('\n')
2311 ui.write('\n')
2312 q = repo.mq
2312 q = repo.mq
2313 applied = set(p.name for p in q.applied)
2313 applied = set(p.name for p in q.applied)
2314 patch = None
2314 patch = None
2315 args = list(args)
2315 args = list(args)
2316 if opts.get('list'):
2316 if opts.get('list'):
2317 if args or opts.get('none'):
2317 if args or opts.get('none'):
2318 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2318 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2319 for i in xrange(len(q.series)):
2319 for i in xrange(len(q.series)):
2320 status(i)
2320 status(i)
2321 return
2321 return
2322 if not args or args[0][0:1] in '-+':
2322 if not args or args[0][0:1] in '-+':
2323 if not q.applied:
2323 if not q.applied:
2324 raise util.Abort(_('no patches applied'))
2324 raise util.Abort(_('no patches applied'))
2325 patch = q.applied[-1].name
2325 patch = q.applied[-1].name
2326 if patch is None and args[0][0:1] not in '-+':
2326 if patch is None and args[0][0:1] not in '-+':
2327 patch = args.pop(0)
2327 patch = args.pop(0)
2328 if patch is None:
2328 if patch is None:
2329 raise util.Abort(_('no patch to work with'))
2329 raise util.Abort(_('no patch to work with'))
2330 if args or opts.get('none'):
2330 if args or opts.get('none'):
2331 idx = q.find_series(patch)
2331 idx = q.find_series(patch)
2332 if idx is None:
2332 if idx is None:
2333 raise util.Abort(_('no patch named %s') % patch)
2333 raise util.Abort(_('no patch named %s') % patch)
2334 q.set_guards(idx, args)
2334 q.set_guards(idx, args)
2335 q.save_dirty()
2335 q.save_dirty()
2336 else:
2336 else:
2337 status(q.series.index(q.lookup(patch)))
2337 status(q.series.index(q.lookup(patch)))
2338
2338
2339 def header(ui, repo, patch=None):
2339 def header(ui, repo, patch=None):
2340 """print the header of the topmost or specified patch
2340 """print the header of the topmost or specified patch
2341
2341
2342 Returns 0 on success."""
2342 Returns 0 on success."""
2343 q = repo.mq
2343 q = repo.mq
2344
2344
2345 if patch:
2345 if patch:
2346 patch = q.lookup(patch)
2346 patch = q.lookup(patch)
2347 else:
2347 else:
2348 if not q.applied:
2348 if not q.applied:
2349 ui.write(_('no patches applied\n'))
2349 ui.write(_('no patches applied\n'))
2350 return 1
2350 return 1
2351 patch = q.lookup('qtip')
2351 patch = q.lookup('qtip')
2352 ph = patchheader(q.join(patch), q.plainmode)
2352 ph = patchheader(q.join(patch), q.plainmode)
2353
2353
2354 ui.write('\n'.join(ph.message) + '\n')
2354 ui.write('\n'.join(ph.message) + '\n')
2355
2355
2356 def lastsavename(path):
2356 def lastsavename(path):
2357 (directory, base) = os.path.split(path)
2357 (directory, base) = os.path.split(path)
2358 names = os.listdir(directory)
2358 names = os.listdir(directory)
2359 namere = re.compile("%s.([0-9]+)" % base)
2359 namere = re.compile("%s.([0-9]+)" % base)
2360 maxindex = None
2360 maxindex = None
2361 maxname = None
2361 maxname = None
2362 for f in names:
2362 for f in names:
2363 m = namere.match(f)
2363 m = namere.match(f)
2364 if m:
2364 if m:
2365 index = int(m.group(1))
2365 index = int(m.group(1))
2366 if maxindex is None or index > maxindex:
2366 if maxindex is None or index > maxindex:
2367 maxindex = index
2367 maxindex = index
2368 maxname = f
2368 maxname = f
2369 if maxname:
2369 if maxname:
2370 return (os.path.join(directory, maxname), maxindex)
2370 return (os.path.join(directory, maxname), maxindex)
2371 return (None, None)
2371 return (None, None)
2372
2372
2373 def savename(path):
2373 def savename(path):
2374 (last, index) = lastsavename(path)
2374 (last, index) = lastsavename(path)
2375 if last is None:
2375 if last is None:
2376 index = 0
2376 index = 0
2377 newpath = path + ".%d" % (index + 1)
2377 newpath = path + ".%d" % (index + 1)
2378 return newpath
2378 return newpath
2379
2379
2380 def push(ui, repo, patch=None, **opts):
2380 def push(ui, repo, patch=None, **opts):
2381 """push the next patch onto the stack
2381 """push the next patch onto the stack
2382
2382
2383 When -f/--force is applied, all local changes in patched files
2383 When -f/--force is applied, all local changes in patched files
2384 will be lost.
2384 will be lost.
2385
2385
2386 Return 0 on success.
2386 Return 0 on success.
2387 """
2387 """
2388 q = repo.mq
2388 q = repo.mq
2389 mergeq = None
2389 mergeq = None
2390
2390
2391 if opts.get('merge'):
2391 if opts.get('merge'):
2392 if opts.get('name'):
2392 if opts.get('name'):
2393 newpath = repo.join(opts.get('name'))
2393 newpath = repo.join(opts.get('name'))
2394 else:
2394 else:
2395 newpath, i = lastsavename(q.path)
2395 newpath, i = lastsavename(q.path)
2396 if not newpath:
2396 if not newpath:
2397 ui.warn(_("no saved queues found, please use -n\n"))
2397 ui.warn(_("no saved queues found, please use -n\n"))
2398 return 1
2398 return 1
2399 mergeq = queue(ui, repo.join(""), newpath)
2399 mergeq = queue(ui, repo.join(""), newpath)
2400 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2400 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2401 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2401 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2402 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2402 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2403 exact=opts.get('exact'))
2403 exact=opts.get('exact'))
2404 return ret
2404 return ret
2405
2405
2406 def pop(ui, repo, patch=None, **opts):
2406 def pop(ui, repo, patch=None, **opts):
2407 """pop the current patch off the stack
2407 """pop the current patch off the stack
2408
2408
2409 By default, pops off the top of the patch stack. If given a patch
2409 By default, pops off the top of the patch stack. If given a patch
2410 name, keeps popping off patches until the named patch is at the
2410 name, keeps popping off patches until the named patch is at the
2411 top of the stack.
2411 top of the stack.
2412
2412
2413 Return 0 on success.
2413 Return 0 on success.
2414 """
2414 """
2415 localupdate = True
2415 localupdate = True
2416 if opts.get('name'):
2416 if opts.get('name'):
2417 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2417 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2418 ui.warn(_('using patch queue: %s\n') % q.path)
2418 ui.warn(_('using patch queue: %s\n') % q.path)
2419 localupdate = False
2419 localupdate = False
2420 else:
2420 else:
2421 q = repo.mq
2421 q = repo.mq
2422 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2422 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2423 all=opts.get('all'))
2423 all=opts.get('all'))
2424 q.save_dirty()
2424 q.save_dirty()
2425 return ret
2425 return ret
2426
2426
2427 def rename(ui, repo, patch, name=None, **opts):
2427 def rename(ui, repo, patch, name=None, **opts):
2428 """rename a patch
2428 """rename a patch
2429
2429
2430 With one argument, renames the current patch to PATCH1.
2430 With one argument, renames the current patch to PATCH1.
2431 With two arguments, renames PATCH1 to PATCH2.
2431 With two arguments, renames PATCH1 to PATCH2.
2432
2432
2433 Returns 0 on success."""
2433 Returns 0 on success."""
2434
2434
2435 q = repo.mq
2435 q = repo.mq
2436
2436
2437 if not name:
2437 if not name:
2438 name = patch
2438 name = patch
2439 patch = None
2439 patch = None
2440
2440
2441 if patch:
2441 if patch:
2442 patch = q.lookup(patch)
2442 patch = q.lookup(patch)
2443 else:
2443 else:
2444 if not q.applied:
2444 if not q.applied:
2445 ui.write(_('no patches applied\n'))
2445 ui.write(_('no patches applied\n'))
2446 return
2446 return
2447 patch = q.lookup('qtip')
2447 patch = q.lookup('qtip')
2448 absdest = q.join(name)
2448 absdest = q.join(name)
2449 if os.path.isdir(absdest):
2449 if os.path.isdir(absdest):
2450 name = normname(os.path.join(name, os.path.basename(patch)))
2450 name = normname(os.path.join(name, os.path.basename(patch)))
2451 absdest = q.join(name)
2451 absdest = q.join(name)
2452 if os.path.exists(absdest):
2452 if os.path.exists(absdest):
2453 raise util.Abort(_('%s already exists') % absdest)
2453 raise util.Abort(_('%s already exists') % absdest)
2454
2454
2455 if name in q.series:
2455 if name in q.series:
2456 raise util.Abort(
2456 raise util.Abort(
2457 _('A patch named %s already exists in the series file') % name)
2457 _('A patch named %s already exists in the series file') % name)
2458
2458
2459 ui.note(_('renaming %s to %s\n') % (patch, name))
2459 ui.note(_('renaming %s to %s\n') % (patch, name))
2460 i = q.find_series(patch)
2460 i = q.find_series(patch)
2461 guards = q.guard_re.findall(q.full_series[i])
2461 guards = q.guard_re.findall(q.full_series[i])
2462 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2462 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2463 q.parse_series()
2463 q.parse_series()
2464 q.series_dirty = 1
2464 q.series_dirty = 1
2465
2465
2466 info = q.isapplied(patch)
2466 info = q.isapplied(patch)
2467 if info:
2467 if info:
2468 q.applied[info[0]] = statusentry(info[1], name)
2468 q.applied[info[0]] = statusentry(info[1], name)
2469 q.applied_dirty = 1
2469 q.applied_dirty = 1
2470
2470
2471 destdir = os.path.dirname(absdest)
2471 destdir = os.path.dirname(absdest)
2472 if not os.path.isdir(destdir):
2472 if not os.path.isdir(destdir):
2473 os.makedirs(destdir)
2473 os.makedirs(destdir)
2474 util.rename(q.join(patch), absdest)
2474 util.rename(q.join(patch), absdest)
2475 r = q.qrepo()
2475 r = q.qrepo()
2476 if r and patch in r.dirstate:
2476 if r and patch in r.dirstate:
2477 wctx = r[None]
2477 wctx = r[None]
2478 wlock = r.wlock()
2478 wlock = r.wlock()
2479 try:
2479 try:
2480 if r.dirstate[patch] == 'a':
2480 if r.dirstate[patch] == 'a':
2481 r.dirstate.forget(patch)
2481 r.dirstate.forget(patch)
2482 r.dirstate.add(name)
2482 r.dirstate.add(name)
2483 else:
2483 else:
2484 if r.dirstate[name] == 'r':
2484 if r.dirstate[name] == 'r':
2485 wctx.undelete([name])
2485 wctx.undelete([name])
2486 wctx.copy(patch, name)
2486 wctx.copy(patch, name)
2487 wctx.remove([patch], False)
2487 wctx.remove([patch], False)
2488 finally:
2488 finally:
2489 wlock.release()
2489 wlock.release()
2490
2490
2491 q.save_dirty()
2491 q.save_dirty()
2492
2492
2493 def restore(ui, repo, rev, **opts):
2493 def restore(ui, repo, rev, **opts):
2494 """restore the queue state saved by a revision (DEPRECATED)
2494 """restore the queue state saved by a revision (DEPRECATED)
2495
2495
2496 This command is deprecated, use :hg:`rebase` instead."""
2496 This command is deprecated, use :hg:`rebase` instead."""
2497 rev = repo.lookup(rev)
2497 rev = repo.lookup(rev)
2498 q = repo.mq
2498 q = repo.mq
2499 q.restore(repo, rev, delete=opts.get('delete'),
2499 q.restore(repo, rev, delete=opts.get('delete'),
2500 qupdate=opts.get('update'))
2500 qupdate=opts.get('update'))
2501 q.save_dirty()
2501 q.save_dirty()
2502 return 0
2502 return 0
2503
2503
2504 def save(ui, repo, **opts):
2504 def save(ui, repo, **opts):
2505 """save current queue state (DEPRECATED)
2505 """save current queue state (DEPRECATED)
2506
2506
2507 This command is deprecated, use :hg:`rebase` instead."""
2507 This command is deprecated, use :hg:`rebase` instead."""
2508 q = repo.mq
2508 q = repo.mq
2509 message = cmdutil.logmessage(opts)
2509 message = cmdutil.logmessage(opts)
2510 ret = q.save(repo, msg=message)
2510 ret = q.save(repo, msg=message)
2511 if ret:
2511 if ret:
2512 return ret
2512 return ret
2513 q.save_dirty()
2513 q.save_dirty()
2514 if opts.get('copy'):
2514 if opts.get('copy'):
2515 path = q.path
2515 path = q.path
2516 if opts.get('name'):
2516 if opts.get('name'):
2517 newpath = os.path.join(q.basepath, opts.get('name'))
2517 newpath = os.path.join(q.basepath, opts.get('name'))
2518 if os.path.exists(newpath):
2518 if os.path.exists(newpath):
2519 if not os.path.isdir(newpath):
2519 if not os.path.isdir(newpath):
2520 raise util.Abort(_('destination %s exists and is not '
2520 raise util.Abort(_('destination %s exists and is not '
2521 'a directory') % newpath)
2521 'a directory') % newpath)
2522 if not opts.get('force'):
2522 if not opts.get('force'):
2523 raise util.Abort(_('destination %s exists, '
2523 raise util.Abort(_('destination %s exists, '
2524 'use -f to force') % newpath)
2524 'use -f to force') % newpath)
2525 else:
2525 else:
2526 newpath = savename(path)
2526 newpath = savename(path)
2527 ui.warn(_("copy %s to %s\n") % (path, newpath))
2527 ui.warn(_("copy %s to %s\n") % (path, newpath))
2528 util.copyfiles(path, newpath)
2528 util.copyfiles(path, newpath)
2529 if opts.get('empty'):
2529 if opts.get('empty'):
2530 try:
2530 try:
2531 os.unlink(q.join(q.status_path))
2531 os.unlink(q.join(q.status_path))
2532 except:
2532 except:
2533 pass
2533 pass
2534 return 0
2534 return 0
2535
2535
2536 def strip(ui, repo, *revs, **opts):
2536 def strip(ui, repo, *revs, **opts):
2537 """strip changesets and all their descendants from the repository
2537 """strip changesets and all their descendants from the repository
2538
2538
2539 The strip command removes the specified changesets and all their
2539 The strip command removes the specified changesets and all their
2540 descendants. If the working directory has uncommitted changes,
2540 descendants. If the working directory has uncommitted changes,
2541 the operation is aborted unless the --force flag is supplied.
2541 the operation is aborted unless the --force flag is supplied.
2542
2542
2543 If a parent of the working directory is stripped, then the working
2543 If a parent of the working directory is stripped, then the working
2544 directory will automatically be updated to the most recent
2544 directory will automatically be updated to the most recent
2545 available ancestor of the stripped parent after the operation
2545 available ancestor of the stripped parent after the operation
2546 completes.
2546 completes.
2547
2547
2548 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2548 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2549 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2549 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2550 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2550 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2551 where BUNDLE is the bundle file created by the strip. Note that
2551 where BUNDLE is the bundle file created by the strip. Note that
2552 the local revision numbers will in general be different after the
2552 the local revision numbers will in general be different after the
2553 restore.
2553 restore.
2554
2554
2555 Use the --no-backup option to discard the backup bundle once the
2555 Use the --no-backup option to discard the backup bundle once the
2556 operation completes.
2556 operation completes.
2557
2557
2558 Return 0 on success.
2558 Return 0 on success.
2559 """
2559 """
2560 backup = 'all'
2560 backup = 'all'
2561 if opts.get('backup'):
2561 if opts.get('backup'):
2562 backup = 'strip'
2562 backup = 'strip'
2563 elif opts.get('no_backup') or opts.get('nobackup'):
2563 elif opts.get('no_backup') or opts.get('nobackup'):
2564 backup = 'none'
2564 backup = 'none'
2565
2565
2566 cl = repo.changelog
2566 cl = repo.changelog
2567 revs = set(cmdutil.revrange(repo, revs))
2567 revs = set(cmdutil.revrange(repo, revs))
2568 if not revs:
2568 if not revs:
2569 raise util.Abort(_('empty revision set'))
2569 raise util.Abort(_('empty revision set'))
2570
2570
2571 descendants = set(cl.descendants(*revs))
2571 descendants = set(cl.descendants(*revs))
2572 strippedrevs = revs.union(descendants)
2572 strippedrevs = revs.union(descendants)
2573 roots = revs.difference(descendants)
2573 roots = revs.difference(descendants)
2574
2574
2575 update = False
2575 update = False
2576 # if one of the wdir parent is stripped we'll need
2576 # if one of the wdir parent is stripped we'll need
2577 # to update away to an earlier revision
2577 # to update away to an earlier revision
2578 for p in repo.dirstate.parents():
2578 for p in repo.dirstate.parents():
2579 if p != nullid and cl.rev(p) in strippedrevs:
2579 if p != nullid and cl.rev(p) in strippedrevs:
2580 update = True
2580 update = True
2581 break
2581 break
2582
2582
2583 rootnodes = set(cl.node(r) for r in roots)
2583 rootnodes = set(cl.node(r) for r in roots)
2584
2584
2585 q = repo.mq
2585 q = repo.mq
2586 if q.applied:
2586 if q.applied:
2587 # refresh queue state if we're about to strip
2587 # refresh queue state if we're about to strip
2588 # applied patches
2588 # applied patches
2589 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2589 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2590 q.applied_dirty = True
2590 q.applied_dirty = True
2591 start = 0
2591 start = 0
2592 end = len(q.applied)
2592 end = len(q.applied)
2593 for i, statusentry in enumerate(q.applied):
2593 for i, statusentry in enumerate(q.applied):
2594 if statusentry.node in rootnodes:
2594 if statusentry.node in rootnodes:
2595 # if one of the stripped roots is an applied
2595 # if one of the stripped roots is an applied
2596 # patch, only part of the queue is stripped
2596 # patch, only part of the queue is stripped
2597 start = i
2597 start = i
2598 break
2598 break
2599 del q.applied[start:end]
2599 del q.applied[start:end]
2600 q.save_dirty()
2600 q.save_dirty()
2601
2601
2602 revs = list(rootnodes)
2602 revs = list(rootnodes)
2603 if update and opts.get('keep'):
2603 if update and opts.get('keep'):
2604 wlock = repo.wlock()
2604 wlock = repo.wlock()
2605 try:
2605 try:
2606 urev = repo.mq.qparents(repo, revs[0])
2606 urev = repo.mq.qparents(repo, revs[0])
2607 repo.dirstate.rebuild(urev, repo[urev].manifest())
2607 repo.dirstate.rebuild(urev, repo[urev].manifest())
2608 repo.dirstate.write()
2608 repo.dirstate.write()
2609 update = False
2609 update = False
2610 finally:
2610 finally:
2611 wlock.release()
2611 wlock.release()
2612
2612
2613 repo.mq.strip(repo, revs, backup=backup, update=update,
2613 repo.mq.strip(repo, revs, backup=backup, update=update,
2614 force=opts.get('force'))
2614 force=opts.get('force'))
2615 return 0
2615 return 0
2616
2616
2617 def select(ui, repo, *args, **opts):
2617 def select(ui, repo, *args, **opts):
2618 '''set or print guarded patches to push
2618 '''set or print guarded patches to push
2619
2619
2620 Use the :hg:`qguard` command to set or print guards on patch, then use
2620 Use the :hg:`qguard` command to set or print guards on patch, then use
2621 qselect to tell mq which guards to use. A patch will be pushed if
2621 qselect to tell mq which guards to use. A patch will be pushed if
2622 it has no guards or any positive guards match the currently
2622 it has no guards or any positive guards match the currently
2623 selected guard, but will not be pushed if any negative guards
2623 selected guard, but will not be pushed if any negative guards
2624 match the current guard. For example::
2624 match the current guard. For example::
2625
2625
2626 qguard foo.patch -- -stable (negative guard)
2626 qguard foo.patch -- -stable (negative guard)
2627 qguard bar.patch +stable (positive guard)
2627 qguard bar.patch +stable (positive guard)
2628 qselect stable
2628 qselect stable
2629
2629
2630 This activates the "stable" guard. mq will skip foo.patch (because
2630 This activates the "stable" guard. mq will skip foo.patch (because
2631 it has a negative match) but push bar.patch (because it has a
2631 it has a negative match) but push bar.patch (because it has a
2632 positive match).
2632 positive match).
2633
2633
2634 With no arguments, prints the currently active guards.
2634 With no arguments, prints the currently active guards.
2635 With one argument, sets the active guard.
2635 With one argument, sets the active guard.
2636
2636
2637 Use -n/--none to deactivate guards (no other arguments needed).
2637 Use -n/--none to deactivate guards (no other arguments needed).
2638 When no guards are active, patches with positive guards are
2638 When no guards are active, patches with positive guards are
2639 skipped and patches with negative guards are pushed.
2639 skipped and patches with negative guards are pushed.
2640
2640
2641 qselect can change the guards on applied patches. It does not pop
2641 qselect can change the guards on applied patches. It does not pop
2642 guarded patches by default. Use --pop to pop back to the last
2642 guarded patches by default. Use --pop to pop back to the last
2643 applied patch that is not guarded. Use --reapply (which implies
2643 applied patch that is not guarded. Use --reapply (which implies
2644 --pop) to push back to the current patch afterwards, but skip
2644 --pop) to push back to the current patch afterwards, but skip
2645 guarded patches.
2645 guarded patches.
2646
2646
2647 Use -s/--series to print a list of all guards in the series file
2647 Use -s/--series to print a list of all guards in the series file
2648 (no other arguments needed). Use -v for more information.
2648 (no other arguments needed). Use -v for more information.
2649
2649
2650 Returns 0 on success.'''
2650 Returns 0 on success.'''
2651
2651
2652 q = repo.mq
2652 q = repo.mq
2653 guards = q.active()
2653 guards = q.active()
2654 if args or opts.get('none'):
2654 if args or opts.get('none'):
2655 old_unapplied = q.unapplied(repo)
2655 old_unapplied = q.unapplied(repo)
2656 old_guarded = [i for i in xrange(len(q.applied)) if
2656 old_guarded = [i for i in xrange(len(q.applied)) if
2657 not q.pushable(i)[0]]
2657 not q.pushable(i)[0]]
2658 q.set_active(args)
2658 q.set_active(args)
2659 q.save_dirty()
2659 q.save_dirty()
2660 if not args:
2660 if not args:
2661 ui.status(_('guards deactivated\n'))
2661 ui.status(_('guards deactivated\n'))
2662 if not opts.get('pop') and not opts.get('reapply'):
2662 if not opts.get('pop') and not opts.get('reapply'):
2663 unapplied = q.unapplied(repo)
2663 unapplied = q.unapplied(repo)
2664 guarded = [i for i in xrange(len(q.applied))
2664 guarded = [i for i in xrange(len(q.applied))
2665 if not q.pushable(i)[0]]
2665 if not q.pushable(i)[0]]
2666 if len(unapplied) != len(old_unapplied):
2666 if len(unapplied) != len(old_unapplied):
2667 ui.status(_('number of unguarded, unapplied patches has '
2667 ui.status(_('number of unguarded, unapplied patches has '
2668 'changed from %d to %d\n') %
2668 'changed from %d to %d\n') %
2669 (len(old_unapplied), len(unapplied)))
2669 (len(old_unapplied), len(unapplied)))
2670 if len(guarded) != len(old_guarded):
2670 if len(guarded) != len(old_guarded):
2671 ui.status(_('number of guarded, applied patches has changed '
2671 ui.status(_('number of guarded, applied patches has changed '
2672 'from %d to %d\n') %
2672 'from %d to %d\n') %
2673 (len(old_guarded), len(guarded)))
2673 (len(old_guarded), len(guarded)))
2674 elif opts.get('series'):
2674 elif opts.get('series'):
2675 guards = {}
2675 guards = {}
2676 noguards = 0
2676 noguards = 0
2677 for gs in q.series_guards:
2677 for gs in q.series_guards:
2678 if not gs:
2678 if not gs:
2679 noguards += 1
2679 noguards += 1
2680 for g in gs:
2680 for g in gs:
2681 guards.setdefault(g, 0)
2681 guards.setdefault(g, 0)
2682 guards[g] += 1
2682 guards[g] += 1
2683 if ui.verbose:
2683 if ui.verbose:
2684 guards['NONE'] = noguards
2684 guards['NONE'] = noguards
2685 guards = guards.items()
2685 guards = guards.items()
2686 guards.sort(key=lambda x: x[0][1:])
2686 guards.sort(key=lambda x: x[0][1:])
2687 if guards:
2687 if guards:
2688 ui.note(_('guards in series file:\n'))
2688 ui.note(_('guards in series file:\n'))
2689 for guard, count in guards:
2689 for guard, count in guards:
2690 ui.note('%2d ' % count)
2690 ui.note('%2d ' % count)
2691 ui.write(guard, '\n')
2691 ui.write(guard, '\n')
2692 else:
2692 else:
2693 ui.note(_('no guards in series file\n'))
2693 ui.note(_('no guards in series file\n'))
2694 else:
2694 else:
2695 if guards:
2695 if guards:
2696 ui.note(_('active guards:\n'))
2696 ui.note(_('active guards:\n'))
2697 for g in guards:
2697 for g in guards:
2698 ui.write(g, '\n')
2698 ui.write(g, '\n')
2699 else:
2699 else:
2700 ui.write(_('no active guards\n'))
2700 ui.write(_('no active guards\n'))
2701 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2701 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2702 popped = False
2702 popped = False
2703 if opts.get('pop') or opts.get('reapply'):
2703 if opts.get('pop') or opts.get('reapply'):
2704 for i in xrange(len(q.applied)):
2704 for i in xrange(len(q.applied)):
2705 pushable, reason = q.pushable(i)
2705 pushable, reason = q.pushable(i)
2706 if not pushable:
2706 if not pushable:
2707 ui.status(_('popping guarded patches\n'))
2707 ui.status(_('popping guarded patches\n'))
2708 popped = True
2708 popped = True
2709 if i == 0:
2709 if i == 0:
2710 q.pop(repo, all=True)
2710 q.pop(repo, all=True)
2711 else:
2711 else:
2712 q.pop(repo, i - 1)
2712 q.pop(repo, i - 1)
2713 break
2713 break
2714 if popped:
2714 if popped:
2715 try:
2715 try:
2716 if reapply:
2716 if reapply:
2717 ui.status(_('reapplying unguarded patches\n'))
2717 ui.status(_('reapplying unguarded patches\n'))
2718 q.push(repo, reapply)
2718 q.push(repo, reapply)
2719 finally:
2719 finally:
2720 q.save_dirty()
2720 q.save_dirty()
2721
2721
2722 def finish(ui, repo, *revrange, **opts):
2722 def finish(ui, repo, *revrange, **opts):
2723 """move applied patches into repository history
2723 """move applied patches into repository history
2724
2724
2725 Finishes the specified revisions (corresponding to applied
2725 Finishes the specified revisions (corresponding to applied
2726 patches) by moving them out of mq control into regular repository
2726 patches) by moving them out of mq control into regular repository
2727 history.
2727 history.
2728
2728
2729 Accepts a revision range or the -a/--applied option. If --applied
2729 Accepts a revision range or the -a/--applied option. If --applied
2730 is specified, all applied mq revisions are removed from mq
2730 is specified, all applied mq revisions are removed from mq
2731 control. Otherwise, the given revisions must be at the base of the
2731 control. Otherwise, the given revisions must be at the base of the
2732 stack of applied patches.
2732 stack of applied patches.
2733
2733
2734 This can be especially useful if your changes have been applied to
2734 This can be especially useful if your changes have been applied to
2735 an upstream repository, or if you are about to push your changes
2735 an upstream repository, or if you are about to push your changes
2736 to upstream.
2736 to upstream.
2737
2737
2738 Returns 0 on success.
2738 Returns 0 on success.
2739 """
2739 """
2740 if not opts.get('applied') and not revrange:
2740 if not opts.get('applied') and not revrange:
2741 raise util.Abort(_('no revisions specified'))
2741 raise util.Abort(_('no revisions specified'))
2742 elif opts.get('applied'):
2742 elif opts.get('applied'):
2743 revrange = ('qbase::qtip',) + revrange
2743 revrange = ('qbase::qtip',) + revrange
2744
2744
2745 q = repo.mq
2745 q = repo.mq
2746 if not q.applied:
2746 if not q.applied:
2747 ui.status(_('no patches applied\n'))
2747 ui.status(_('no patches applied\n'))
2748 return 0
2748 return 0
2749
2749
2750 revs = cmdutil.revrange(repo, revrange)
2750 revs = cmdutil.revrange(repo, revrange)
2751 q.finish(repo, revs)
2751 q.finish(repo, revs)
2752 q.save_dirty()
2752 q.save_dirty()
2753 return 0
2753 return 0
2754
2754
2755 def qqueue(ui, repo, name=None, **opts):
2755 def qqueue(ui, repo, name=None, **opts):
2756 '''manage multiple patch queues
2756 '''manage multiple patch queues
2757
2757
2758 Supports switching between different patch queues, as well as creating
2758 Supports switching between different patch queues, as well as creating
2759 new patch queues and deleting existing ones.
2759 new patch queues and deleting existing ones.
2760
2760
2761 Omitting a queue name or specifying -l/--list will show you the registered
2761 Omitting a queue name or specifying -l/--list will show you the registered
2762 queues - by default the "normal" patches queue is registered. The currently
2762 queues - by default the "normal" patches queue is registered. The currently
2763 active queue will be marked with "(active)".
2763 active queue will be marked with "(active)".
2764
2764
2765 To create a new queue, use -c/--create. The queue is automatically made
2765 To create a new queue, use -c/--create. The queue is automatically made
2766 active, except in the case where there are applied patches from the
2766 active, except in the case where there are applied patches from the
2767 currently active queue in the repository. Then the queue will only be
2767 currently active queue in the repository. Then the queue will only be
2768 created and switching will fail.
2768 created and switching will fail.
2769
2769
2770 To delete an existing queue, use --delete. You cannot delete the currently
2770 To delete an existing queue, use --delete. You cannot delete the currently
2771 active queue.
2771 active queue.
2772
2772
2773 Returns 0 on success.
2773 Returns 0 on success.
2774 '''
2774 '''
2775
2775
2776 q = repo.mq
2776 q = repo.mq
2777
2777
2778 _defaultqueue = 'patches'
2778 _defaultqueue = 'patches'
2779 _allqueues = 'patches.queues'
2779 _allqueues = 'patches.queues'
2780 _activequeue = 'patches.queue'
2780 _activequeue = 'patches.queue'
2781
2781
2782 def _getcurrent():
2782 def _getcurrent():
2783 cur = os.path.basename(q.path)
2783 cur = os.path.basename(q.path)
2784 if cur.startswith('patches-'):
2784 if cur.startswith('patches-'):
2785 cur = cur[8:]
2785 cur = cur[8:]
2786 return cur
2786 return cur
2787
2787
2788 def _noqueues():
2788 def _noqueues():
2789 try:
2789 try:
2790 fh = repo.opener(_allqueues, 'r')
2790 fh = repo.opener(_allqueues, 'r')
2791 fh.close()
2791 fh.close()
2792 except IOError:
2792 except IOError:
2793 return True
2793 return True
2794
2794
2795 return False
2795 return False
2796
2796
2797 def _getqueues():
2797 def _getqueues():
2798 current = _getcurrent()
2798 current = _getcurrent()
2799
2799
2800 try:
2800 try:
2801 fh = repo.opener(_allqueues, 'r')
2801 fh = repo.opener(_allqueues, 'r')
2802 queues = [queue.strip() for queue in fh if queue.strip()]
2802 queues = [queue.strip() for queue in fh if queue.strip()]
2803 fh.close()
2803 fh.close()
2804 if current not in queues:
2804 if current not in queues:
2805 queues.append(current)
2805 queues.append(current)
2806 except IOError:
2806 except IOError:
2807 queues = [_defaultqueue]
2807 queues = [_defaultqueue]
2808
2808
2809 return sorted(queues)
2809 return sorted(queues)
2810
2810
2811 def _setactive(name):
2811 def _setactive(name):
2812 if q.applied:
2812 if q.applied:
2813 raise util.Abort(_('patches applied - cannot set new queue active'))
2813 raise util.Abort(_('patches applied - cannot set new queue active'))
2814 _setactivenocheck(name)
2814 _setactivenocheck(name)
2815
2815
2816 def _setactivenocheck(name):
2816 def _setactivenocheck(name):
2817 fh = repo.opener(_activequeue, 'w')
2817 fh = repo.opener(_activequeue, 'w')
2818 if name != 'patches':
2818 if name != 'patches':
2819 fh.write(name)
2819 fh.write(name)
2820 fh.close()
2820 fh.close()
2821
2821
2822 def _addqueue(name):
2822 def _addqueue(name):
2823 fh = repo.opener(_allqueues, 'a')
2823 fh = repo.opener(_allqueues, 'a')
2824 fh.write('%s\n' % (name,))
2824 fh.write('%s\n' % (name,))
2825 fh.close()
2825 fh.close()
2826
2826
2827 def _queuedir(name):
2827 def _queuedir(name):
2828 if name == 'patches':
2828 if name == 'patches':
2829 return repo.join('patches')
2829 return repo.join('patches')
2830 else:
2830 else:
2831 return repo.join('patches-' + name)
2831 return repo.join('patches-' + name)
2832
2832
2833 def _validname(name):
2833 def _validname(name):
2834 for n in name:
2834 for n in name:
2835 if n in ':\\/.':
2835 if n in ':\\/.':
2836 return False
2836 return False
2837 return True
2837 return True
2838
2838
2839 def _delete(name):
2839 def _delete(name):
2840 if name not in existing:
2840 if name not in existing:
2841 raise util.Abort(_('cannot delete queue that does not exist'))
2841 raise util.Abort(_('cannot delete queue that does not exist'))
2842
2842
2843 current = _getcurrent()
2843 current = _getcurrent()
2844
2844
2845 if name == current:
2845 if name == current:
2846 raise util.Abort(_('cannot delete currently active queue'))
2846 raise util.Abort(_('cannot delete currently active queue'))
2847
2847
2848 fh = repo.opener('patches.queues.new', 'w')
2848 fh = repo.opener('patches.queues.new', 'w')
2849 for queue in existing:
2849 for queue in existing:
2850 if queue == name:
2850 if queue == name:
2851 continue
2851 continue
2852 fh.write('%s\n' % (queue,))
2852 fh.write('%s\n' % (queue,))
2853 fh.close()
2853 fh.close()
2854 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2854 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2855
2855
2856 if not name or opts.get('list'):
2856 if not name or opts.get('list'):
2857 current = _getcurrent()
2857 current = _getcurrent()
2858 for queue in _getqueues():
2858 for queue in _getqueues():
2859 ui.write('%s' % (queue,))
2859 ui.write('%s' % (queue,))
2860 if queue == current and not ui.quiet:
2860 if queue == current and not ui.quiet:
2861 ui.write(_(' (active)\n'))
2861 ui.write(_(' (active)\n'))
2862 else:
2862 else:
2863 ui.write('\n')
2863 ui.write('\n')
2864 return
2864 return
2865
2865
2866 if not _validname(name):
2866 if not _validname(name):
2867 raise util.Abort(
2867 raise util.Abort(
2868 _('invalid queue name, may not contain the characters ":\\/."'))
2868 _('invalid queue name, may not contain the characters ":\\/."'))
2869
2869
2870 existing = _getqueues()
2870 existing = _getqueues()
2871
2871
2872 if opts.get('create'):
2872 if opts.get('create'):
2873 if name in existing:
2873 if name in existing:
2874 raise util.Abort(_('queue "%s" already exists') % name)
2874 raise util.Abort(_('queue "%s" already exists') % name)
2875 if _noqueues():
2875 if _noqueues():
2876 _addqueue(_defaultqueue)
2876 _addqueue(_defaultqueue)
2877 _addqueue(name)
2877 _addqueue(name)
2878 _setactive(name)
2878 _setactive(name)
2879 elif opts.get('rename'):
2879 elif opts.get('rename'):
2880 current = _getcurrent()
2880 current = _getcurrent()
2881 if name == current:
2881 if name == current:
2882 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
2882 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
2883 if name in existing:
2883 if name in existing:
2884 raise util.Abort(_('queue "%s" already exists') % name)
2884 raise util.Abort(_('queue "%s" already exists') % name)
2885
2885
2886 olddir = _queuedir(current)
2886 olddir = _queuedir(current)
2887 newdir = _queuedir(name)
2887 newdir = _queuedir(name)
2888
2888
2889 if os.path.exists(newdir):
2889 if os.path.exists(newdir):
2890 raise util.Abort(_('non-queue directory "%s" already exists') %
2890 raise util.Abort(_('non-queue directory "%s" already exists') %
2891 newdir)
2891 newdir)
2892
2892
2893 fh = repo.opener('patches.queues.new', 'w')
2893 fh = repo.opener('patches.queues.new', 'w')
2894 for queue in existing:
2894 for queue in existing:
2895 if queue == current:
2895 if queue == current:
2896 fh.write('%s\n' % (name,))
2896 fh.write('%s\n' % (name,))
2897 if os.path.exists(olddir):
2897 if os.path.exists(olddir):
2898 util.rename(olddir, newdir)
2898 util.rename(olddir, newdir)
2899 else:
2899 else:
2900 fh.write('%s\n' % (queue,))
2900 fh.write('%s\n' % (queue,))
2901 fh.close()
2901 fh.close()
2902 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2902 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2903 _setactivenocheck(name)
2903 _setactivenocheck(name)
2904 elif opts.get('delete'):
2904 elif opts.get('delete'):
2905 _delete(name)
2905 _delete(name)
2906 elif opts.get('purge'):
2906 elif opts.get('purge'):
2907 if name in existing:
2907 if name in existing:
2908 _delete(name)
2908 _delete(name)
2909 qdir = _queuedir(name)
2909 qdir = _queuedir(name)
2910 if os.path.exists(qdir):
2910 if os.path.exists(qdir):
2911 shutil.rmtree(qdir)
2911 shutil.rmtree(qdir)
2912 else:
2912 else:
2913 if name not in existing:
2913 if name not in existing:
2914 raise util.Abort(_('use --create to create a new queue'))
2914 raise util.Abort(_('use --create to create a new queue'))
2915 _setactive(name)
2915 _setactive(name)
2916
2916
2917 def reposetup(ui, repo):
2917 def reposetup(ui, repo):
2918 class mqrepo(repo.__class__):
2918 class mqrepo(repo.__class__):
2919 @util.propertycache
2919 @util.propertycache
2920 def mq(self):
2920 def mq(self):
2921 return queue(self.ui, self.join(""))
2921 return queue(self.ui, self.join(""))
2922
2922
2923 def abort_if_wdir_patched(self, errmsg, force=False):
2923 def abort_if_wdir_patched(self, errmsg, force=False):
2924 if self.mq.applied and not force:
2924 if self.mq.applied and not force:
2925 parents = self.dirstate.parents()
2925 parents = self.dirstate.parents()
2926 patches = [s.node for s in self.mq.applied]
2926 patches = [s.node for s in self.mq.applied]
2927 if parents[0] in patches or parents[1] in patches:
2927 if parents[0] in patches or parents[1] in patches:
2928 raise util.Abort(errmsg)
2928 raise util.Abort(errmsg)
2929
2929
2930 def commit(self, text="", user=None, date=None, match=None,
2930 def commit(self, text="", user=None, date=None, match=None,
2931 force=False, editor=False, extra={}):
2931 force=False, editor=False, extra={}):
2932 self.abort_if_wdir_patched(
2932 self.abort_if_wdir_patched(
2933 _('cannot commit over an applied mq patch'),
2933 _('cannot commit over an applied mq patch'),
2934 force)
2934 force)
2935
2935
2936 return super(mqrepo, self).commit(text, user, date, match, force,
2936 return super(mqrepo, self).commit(text, user, date, match, force,
2937 editor, extra)
2937 editor, extra)
2938
2938
2939 def checkpush(self, force, revs):
2939 def checkpush(self, force, revs):
2940 if self.mq.applied and not force:
2940 if self.mq.applied and not force:
2941 haspatches = True
2941 haspatches = True
2942 if revs:
2942 if revs:
2943 # Assume applied patches have no non-patch descendants
2943 # Assume applied patches have no non-patch descendants
2944 # and are not on remote already. If they appear in the
2944 # and are not on remote already. If they appear in the
2945 # set of resolved 'revs', bail out.
2945 # set of resolved 'revs', bail out.
2946 applied = set(e.node for e in self.mq.applied)
2946 applied = set(e.node for e in self.mq.applied)
2947 haspatches = bool([n for n in revs if n in applied])
2947 haspatches = bool([n for n in revs if n in applied])
2948 if haspatches:
2948 if haspatches:
2949 raise util.Abort(_('source has mq patches applied'))
2949 raise util.Abort(_('source has mq patches applied'))
2950 super(mqrepo, self).checkpush(force, revs)
2950 super(mqrepo, self).checkpush(force, revs)
2951
2951
2952 def _findtags(self):
2952 def _findtags(self):
2953 '''augment tags from base class with patch tags'''
2953 '''augment tags from base class with patch tags'''
2954 result = super(mqrepo, self)._findtags()
2954 result = super(mqrepo, self)._findtags()
2955
2955
2956 q = self.mq
2956 q = self.mq
2957 if not q.applied:
2957 if not q.applied:
2958 return result
2958 return result
2959
2959
2960 mqtags = [(patch.node, patch.name) for patch in q.applied]
2960 mqtags = [(patch.node, patch.name) for patch in q.applied]
2961
2961
2962 try:
2962 try:
2963 r = self.changelog.rev(mqtags[-1][0])
2963 r = self.changelog.rev(mqtags[-1][0])
2964 except error.RepoLookupError:
2964 except error.RepoLookupError:
2965 self.ui.warn(_('mq status file refers to unknown node %s\n')
2965 self.ui.warn(_('mq status file refers to unknown node %s\n')
2966 % short(mqtags[-1][0]))
2966 % short(mqtags[-1][0]))
2967 return result
2967 return result
2968
2968
2969 mqtags.append((mqtags[-1][0], 'qtip'))
2969 mqtags.append((mqtags[-1][0], 'qtip'))
2970 mqtags.append((mqtags[0][0], 'qbase'))
2970 mqtags.append((mqtags[0][0], 'qbase'))
2971 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2971 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2972 tags = result[0]
2972 tags = result[0]
2973 for patch in mqtags:
2973 for patch in mqtags:
2974 if patch[1] in tags:
2974 if patch[1] in tags:
2975 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2975 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2976 % patch[1])
2976 % patch[1])
2977 else:
2977 else:
2978 tags[patch[1]] = patch[0]
2978 tags[patch[1]] = patch[0]
2979
2979
2980 return result
2980 return result
2981
2981
2982 def _branchtags(self, partial, lrev):
2982 def _branchtags(self, partial, lrev):
2983 q = self.mq
2983 q = self.mq
2984 if not q.applied:
2984 if not q.applied:
2985 return super(mqrepo, self)._branchtags(partial, lrev)
2985 return super(mqrepo, self)._branchtags(partial, lrev)
2986
2986
2987 cl = self.changelog
2987 cl = self.changelog
2988 qbasenode = q.applied[0].node
2988 qbasenode = q.applied[0].node
2989 try:
2989 try:
2990 qbase = cl.rev(qbasenode)
2990 qbase = cl.rev(qbasenode)
2991 except error.LookupError:
2991 except error.LookupError:
2992 self.ui.warn(_('mq status file refers to unknown node %s\n')
2992 self.ui.warn(_('mq status file refers to unknown node %s\n')
2993 % short(qbasenode))
2993 % short(qbasenode))
2994 return super(mqrepo, self)._branchtags(partial, lrev)
2994 return super(mqrepo, self)._branchtags(partial, lrev)
2995
2995
2996 start = lrev + 1
2996 start = lrev + 1
2997 if start < qbase:
2997 if start < qbase:
2998 # update the cache (excluding the patches) and save it
2998 # update the cache (excluding the patches) and save it
2999 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
2999 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3000 self._updatebranchcache(partial, ctxgen)
3000 self._updatebranchcache(partial, ctxgen)
3001 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3001 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3002 start = qbase
3002 start = qbase
3003 # if start = qbase, the cache is as updated as it should be.
3003 # if start = qbase, the cache is as updated as it should be.
3004 # if start > qbase, the cache includes (part of) the patches.
3004 # if start > qbase, the cache includes (part of) the patches.
3005 # we might as well use it, but we won't save it.
3005 # we might as well use it, but we won't save it.
3006
3006
3007 # update the cache up to the tip
3007 # update the cache up to the tip
3008 ctxgen = (self[r] for r in xrange(start, len(cl)))
3008 ctxgen = (self[r] for r in xrange(start, len(cl)))
3009 self._updatebranchcache(partial, ctxgen)
3009 self._updatebranchcache(partial, ctxgen)
3010
3010
3011 return partial
3011 return partial
3012
3012
3013 if repo.local():
3013 if repo.local():
3014 repo.__class__ = mqrepo
3014 repo.__class__ = mqrepo
3015
3015
3016 def mqimport(orig, ui, repo, *args, **kwargs):
3016 def mqimport(orig, ui, repo, *args, **kwargs):
3017 if (hasattr(repo, 'abort_if_wdir_patched')
3017 if (hasattr(repo, 'abort_if_wdir_patched')
3018 and not kwargs.get('no_commit', False)):
3018 and not kwargs.get('no_commit', False)):
3019 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
3019 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
3020 kwargs.get('force'))
3020 kwargs.get('force'))
3021 return orig(ui, repo, *args, **kwargs)
3021 return orig(ui, repo, *args, **kwargs)
3022
3022
3023 def mqinit(orig, ui, *args, **kwargs):
3023 def mqinit(orig, ui, *args, **kwargs):
3024 mq = kwargs.pop('mq', None)
3024 mq = kwargs.pop('mq', None)
3025
3025
3026 if not mq:
3026 if not mq:
3027 return orig(ui, *args, **kwargs)
3027 return orig(ui, *args, **kwargs)
3028
3028
3029 if args:
3029 if args:
3030 repopath = args[0]
3030 repopath = args[0]
3031 if not hg.islocal(repopath):
3031 if not hg.islocal(repopath):
3032 raise util.Abort(_('only a local queue repository '
3032 raise util.Abort(_('only a local queue repository '
3033 'may be initialized'))
3033 'may be initialized'))
3034 else:
3034 else:
3035 repopath = cmdutil.findrepo(os.getcwd())
3035 repopath = cmdutil.findrepo(os.getcwd())
3036 if not repopath:
3036 if not repopath:
3037 raise util.Abort(_('there is no Mercurial repository here '
3037 raise util.Abort(_('there is no Mercurial repository here '
3038 '(.hg not found)'))
3038 '(.hg not found)'))
3039 repo = hg.repository(ui, repopath)
3039 repo = hg.repository(ui, repopath)
3040 return qinit(ui, repo, True)
3040 return qinit(ui, repo, True)
3041
3041
3042 def mqcommand(orig, ui, repo, *args, **kwargs):
3042 def mqcommand(orig, ui, repo, *args, **kwargs):
3043 """Add --mq option to operate on patch repository instead of main"""
3043 """Add --mq option to operate on patch repository instead of main"""
3044
3044
3045 # some commands do not like getting unknown options
3045 # some commands do not like getting unknown options
3046 mq = kwargs.pop('mq', None)
3046 mq = kwargs.pop('mq', None)
3047
3047
3048 if not mq:
3048 if not mq:
3049 return orig(ui, repo, *args, **kwargs)
3049 return orig(ui, repo, *args, **kwargs)
3050
3050
3051 q = repo.mq
3051 q = repo.mq
3052 r = q.qrepo()
3052 r = q.qrepo()
3053 if not r:
3053 if not r:
3054 raise util.Abort(_('no queue repository'))
3054 raise util.Abort(_('no queue repository'))
3055 return orig(r.ui, r, *args, **kwargs)
3055 return orig(r.ui, r, *args, **kwargs)
3056
3056
3057 def summary(orig, ui, repo, *args, **kwargs):
3057 def summary(orig, ui, repo, *args, **kwargs):
3058 r = orig(ui, repo, *args, **kwargs)
3058 r = orig(ui, repo, *args, **kwargs)
3059 q = repo.mq
3059 q = repo.mq
3060 m = []
3060 m = []
3061 a, u = len(q.applied), len(q.unapplied(repo))
3061 a, u = len(q.applied), len(q.unapplied(repo))
3062 if a:
3062 if a:
3063 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3063 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3064 if u:
3064 if u:
3065 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3065 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3066 if m:
3066 if m:
3067 ui.write("mq: %s\n" % ', '.join(m))
3067 ui.write("mq: %s\n" % ', '.join(m))
3068 else:
3068 else:
3069 ui.note(_("mq: (empty queue)\n"))
3069 ui.note(_("mq: (empty queue)\n"))
3070 return r
3070 return r
3071
3071
3072 def uisetup(ui):
3072 def uisetup(ui):
3073 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3073 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3074
3074
3075 extensions.wrapcommand(commands.table, 'import', mqimport)
3075 extensions.wrapcommand(commands.table, 'import', mqimport)
3076 extensions.wrapcommand(commands.table, 'summary', summary)
3076 extensions.wrapcommand(commands.table, 'summary', summary)
3077
3077
3078 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3078 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3079 entry[1].extend(mqopt)
3079 entry[1].extend(mqopt)
3080
3080
3081 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
3081 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
3082
3082
3083 def dotable(cmdtable):
3083 def dotable(cmdtable):
3084 for cmd in cmdtable.keys():
3084 for cmd in cmdtable.keys():
3085 cmd = cmdutil.parsealiases(cmd)[0]
3085 cmd = cmdutil.parsealiases(cmd)[0]
3086 if cmd in nowrap:
3086 if cmd in nowrap:
3087 continue
3087 continue
3088 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3088 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3089 entry[1].extend(mqopt)
3089 entry[1].extend(mqopt)
3090
3090
3091 dotable(commands.table)
3091 dotable(commands.table)
3092
3092
3093 for extname, extmodule in extensions.extensions():
3093 for extname, extmodule in extensions.extensions():
3094 if extmodule.__file__ != __file__:
3094 if extmodule.__file__ != __file__:
3095 dotable(getattr(extmodule, 'cmdtable', {}))
3095 dotable(getattr(extmodule, 'cmdtable', {}))
3096
3096
3097 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
3097 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
3098
3098
3099 cmdtable = {
3099 cmdtable = {
3100 "qapplied":
3100 "qapplied":
3101 (applied,
3101 (applied,
3102 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
3102 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
3103 _('hg qapplied [-1] [-s] [PATCH]')),
3103 _('hg qapplied [-1] [-s] [PATCH]')),
3104 "qclone":
3104 "qclone":
3105 (clone,
3105 (clone,
3106 [('', 'pull', None, _('use pull protocol to copy metadata')),
3106 [('', 'pull', None, _('use pull protocol to copy metadata')),
3107 ('U', 'noupdate', None, _('do not update the new working directories')),
3107 ('U', 'noupdate', None, _('do not update the new working directories')),
3108 ('', 'uncompressed', None,
3108 ('', 'uncompressed', None,
3109 _('use uncompressed transfer (fast over LAN)')),
3109 _('use uncompressed transfer (fast over LAN)')),
3110 ('p', 'patches', '',
3110 ('p', 'patches', '',
3111 _('location of source patch repository'), _('REPO')),
3111 _('location of source patch repository'), _('REPO')),
3112 ] + commands.remoteopts,
3112 ] + commands.remoteopts,
3113 _('hg qclone [OPTION]... SOURCE [DEST]')),
3113 _('hg qclone [OPTION]... SOURCE [DEST]')),
3114 "qcommit|qci":
3114 "qcommit|qci":
3115 (commit,
3115 (commit,
3116 commands.table["^commit|ci"][1],
3116 commands.table["^commit|ci"][1],
3117 _('hg qcommit [OPTION]... [FILE]...')),
3117 _('hg qcommit [OPTION]... [FILE]...')),
3118 "^qdiff":
3118 "^qdiff":
3119 (diff,
3119 (diff,
3120 commands.diffopts + commands.diffopts2 + commands.walkopts,
3120 commands.diffopts + commands.diffopts2 + commands.walkopts,
3121 _('hg qdiff [OPTION]... [FILE]...')),
3121 _('hg qdiff [OPTION]... [FILE]...')),
3122 "qdelete|qremove|qrm":
3122 "qdelete|qremove|qrm":
3123 (delete,
3123 (delete,
3124 [('k', 'keep', None, _('keep patch file')),
3124 [('k', 'keep', None, _('keep patch file')),
3125 ('r', 'rev', [],
3125 ('r', 'rev', [],
3126 _('stop managing a revision (DEPRECATED)'), _('REV'))],
3126 _('stop managing a revision (DEPRECATED)'), _('REV'))],
3127 _('hg qdelete [-k] [PATCH]...')),
3127 _('hg qdelete [-k] [PATCH]...')),
3128 'qfold':
3128 'qfold':
3129 (fold,
3129 (fold,
3130 [('e', 'edit', None, _('edit patch header')),
3130 [('e', 'edit', None, _('edit patch header')),
3131 ('k', 'keep', None, _('keep folded patch files')),
3131 ('k', 'keep', None, _('keep folded patch files')),
3132 ] + commands.commitopts,
3132 ] + commands.commitopts,
3133 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
3133 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
3134 'qgoto':
3134 'qgoto':
3135 (goto,
3135 (goto,
3136 [('f', 'force', None, _('overwrite any local changes'))],
3136 [('f', 'force', None, _('overwrite any local changes'))],
3137 _('hg qgoto [OPTION]... PATCH')),
3137 _('hg qgoto [OPTION]... PATCH')),
3138 'qguard':
3138 'qguard':
3139 (guard,
3139 (guard,
3140 [('l', 'list', None, _('list all patches and guards')),
3140 [('l', 'list', None, _('list all patches and guards')),
3141 ('n', 'none', None, _('drop all guards'))],
3141 ('n', 'none', None, _('drop all guards'))],
3142 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
3142 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
3143 'qheader': (header, [], _('hg qheader [PATCH]')),
3143 'qheader': (header, [], _('hg qheader [PATCH]')),
3144 "qimport":
3144 "qimport":
3145 (qimport,
3145 (qimport,
3146 [('e', 'existing', None, _('import file in patch directory')),
3146 [('e', 'existing', None, _('import file in patch directory')),
3147 ('n', 'name', '',
3147 ('n', 'name', '',
3148 _('name of patch file'), _('NAME')),
3148 _('name of patch file'), _('NAME')),
3149 ('f', 'force', None, _('overwrite existing files')),
3149 ('f', 'force', None, _('overwrite existing files')),
3150 ('r', 'rev', [],
3150 ('r', 'rev', [],
3151 _('place existing revisions under mq control'), _('REV')),
3151 _('place existing revisions under mq control'), _('REV')),
3152 ('g', 'git', None, _('use git extended diff format')),
3152 ('g', 'git', None, _('use git extended diff format')),
3153 ('P', 'push', None, _('qpush after importing'))],
3153 ('P', 'push', None, _('qpush after importing'))],
3154 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
3154 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
3155 "^qinit":
3155 "^qinit":
3156 (init,
3156 (init,
3157 [('c', 'create-repo', None, _('create queue repository'))],
3157 [('c', 'create-repo', None, _('create queue repository'))],
3158 _('hg qinit [-c]')),
3158 _('hg qinit [-c]')),
3159 "^qnew":
3159 "^qnew":
3160 (new,
3160 (new,
3161 [('e', 'edit', None, _('edit commit message')),
3161 [('e', 'edit', None, _('edit commit message')),
3162 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
3162 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
3163 ('g', 'git', None, _('use git extended diff format')),
3163 ('g', 'git', None, _('use git extended diff format')),
3164 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
3164 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
3165 ('u', 'user', '',
3165 ('u', 'user', '',
3166 _('add "From: <USER>" to patch'), _('USER')),
3166 _('add "From: <USER>" to patch'), _('USER')),
3167 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
3167 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
3168 ('d', 'date', '',
3168 ('d', 'date', '',
3169 _('add "Date: <DATE>" to patch'), _('DATE'))
3169 _('add "Date: <DATE>" to patch'), _('DATE'))
3170 ] + commands.walkopts + commands.commitopts,
3170 ] + commands.walkopts + commands.commitopts,
3171 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
3171 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
3172 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
3172 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
3173 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
3173 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
3174 "^qpop":
3174 "^qpop":
3175 (pop,
3175 (pop,
3176 [('a', 'all', None, _('pop all patches')),
3176 [('a', 'all', None, _('pop all patches')),
3177 ('n', 'name', '',
3177 ('n', 'name', '',
3178 _('queue name to pop (DEPRECATED)'), _('NAME')),
3178 _('queue name to pop (DEPRECATED)'), _('NAME')),
3179 ('f', 'force', None, _('forget any local changes to patched files'))],
3179 ('f', 'force', None, _('forget any local changes to patched files'))],
3180 _('hg qpop [-a] [-f] [PATCH | INDEX]')),
3180 _('hg qpop [-a] [-f] [PATCH | INDEX]')),
3181 "^qpush":
3181 "^qpush":
3182 (push,
3182 (push,
3183 [('f', 'force', None, _('apply on top of local changes')),
3183 [('f', 'force', None, _('apply on top of local changes')),
3184 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
3184 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
3185 ('l', 'list', None, _('list patch name in commit text')),
3185 ('l', 'list', None, _('list patch name in commit text')),
3186 ('a', 'all', None, _('apply all patches')),
3186 ('a', 'all', None, _('apply all patches')),
3187 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
3187 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
3188 ('n', 'name', '',
3188 ('n', 'name', '',
3189 _('merge queue name (DEPRECATED)'), _('NAME')),
3189 _('merge queue name (DEPRECATED)'), _('NAME')),
3190 ('', 'move', None, _('reorder patch series and apply only the patch'))],
3190 ('', 'move', None, _('reorder patch series and apply only the patch'))],
3191 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]')),
3191 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]')),
3192 "^qrefresh":
3192 "^qrefresh":
3193 (refresh,
3193 (refresh,
3194 [('e', 'edit', None, _('edit commit message')),
3194 [('e', 'edit', None, _('edit commit message')),
3195 ('g', 'git', None, _('use git extended diff format')),
3195 ('g', 'git', None, _('use git extended diff format')),
3196 ('s', 'short', None,
3196 ('s', 'short', None,
3197 _('refresh only files already in the patch and specified files')),
3197 _('refresh only files already in the patch and specified files')),
3198 ('U', 'currentuser', None,
3198 ('U', 'currentuser', None,
3199 _('add/update author field in patch with current user')),
3199 _('add/update author field in patch with current user')),
3200 ('u', 'user', '',
3200 ('u', 'user', '',
3201 _('add/update author field in patch with given user'), _('USER')),
3201 _('add/update author field in patch with given user'), _('USER')),
3202 ('D', 'currentdate', None,
3202 ('D', 'currentdate', None,
3203 _('add/update date field in patch with current date')),
3203 _('add/update date field in patch with current date')),
3204 ('d', 'date', '',
3204 ('d', 'date', '',
3205 _('add/update date field in patch with given date'), _('DATE'))
3205 _('add/update date field in patch with given date'), _('DATE'))
3206 ] + commands.walkopts + commands.commitopts,
3206 ] + commands.walkopts + commands.commitopts,
3207 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
3207 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
3208 'qrename|qmv':
3208 'qrename|qmv':
3209 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
3209 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
3210 "qrestore":
3210 "qrestore":
3211 (restore,
3211 (restore,
3212 [('d', 'delete', None, _('delete save entry')),
3212 [('d', 'delete', None, _('delete save entry')),
3213 ('u', 'update', None, _('update queue working directory'))],
3213 ('u', 'update', None, _('update queue working directory'))],
3214 _('hg qrestore [-d] [-u] REV')),
3214 _('hg qrestore [-d] [-u] REV')),
3215 "qsave":
3215 "qsave":
3216 (save,
3216 (save,
3217 [('c', 'copy', None, _('copy patch directory')),
3217 [('c', 'copy', None, _('copy patch directory')),
3218 ('n', 'name', '',
3218 ('n', 'name', '',
3219 _('copy directory name'), _('NAME')),
3219 _('copy directory name'), _('NAME')),
3220 ('e', 'empty', None, _('clear queue status file')),
3220 ('e', 'empty', None, _('clear queue status file')),
3221 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3221 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3222 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
3222 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
3223 "qselect":
3223 "qselect":
3224 (select,
3224 (select,
3225 [('n', 'none', None, _('disable all guards')),
3225 [('n', 'none', None, _('disable all guards')),
3226 ('s', 'series', None, _('list all guards in series file')),
3226 ('s', 'series', None, _('list all guards in series file')),
3227 ('', 'pop', None, _('pop to before first guarded applied patch')),
3227 ('', 'pop', None, _('pop to before first guarded applied patch')),
3228 ('', 'reapply', None, _('pop, then reapply patches'))],
3228 ('', 'reapply', None, _('pop, then reapply patches'))],
3229 _('hg qselect [OPTION]... [GUARD]...')),
3229 _('hg qselect [OPTION]... [GUARD]...')),
3230 "qseries":
3230 "qseries":
3231 (series,
3231 (series,
3232 [('m', 'missing', None, _('print patches not in series')),
3232 [('m', 'missing', None, _('print patches not in series')),
3233 ] + seriesopts,
3233 ] + seriesopts,
3234 _('hg qseries [-ms]')),
3234 _('hg qseries [-ms]')),
3235 "strip":
3235 "strip":
3236 (strip,
3236 (strip,
3237 [('f', 'force', None, _('force removal of changesets even if the '
3237 [('f', 'force', None, _('force removal of changesets even if the '
3238 'working directory has uncommitted changes')),
3238 'working directory has uncommitted changes')),
3239 ('b', 'backup', None, _('bundle only changesets with local revision'
3239 ('b', 'backup', None, _('bundle only changesets with local revision'
3240 ' number greater than REV which are not'
3240 ' number greater than REV which are not'
3241 ' descendants of REV (DEPRECATED)')),
3241 ' descendants of REV (DEPRECATED)')),
3242 ('n', 'no-backup', None, _('no backups')),
3242 ('n', 'no-backup', None, _('no backups')),
3243 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
3243 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
3244 ('k', 'keep', None, _("do not modify working copy during strip"))],
3244 ('k', 'keep', None, _("do not modify working copy during strip"))],
3245 _('hg strip [-k] [-f] [-n] REV...')),
3245 _('hg strip [-k] [-f] [-n] REV...')),
3246 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
3246 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
3247 "qunapplied":
3247 "qunapplied":
3248 (unapplied,
3248 (unapplied,
3249 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
3249 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
3250 _('hg qunapplied [-1] [-s] [PATCH]')),
3250 _('hg qunapplied [-1] [-s] [PATCH]')),
3251 "qfinish":
3251 "qfinish":
3252 (finish,
3252 (finish,
3253 [('a', 'applied', None, _('finish all applied changesets'))],
3253 [('a', 'applied', None, _('finish all applied changesets'))],
3254 _('hg qfinish [-a] [REV]...')),
3254 _('hg qfinish [-a] [REV]...')),
3255 'qqueue':
3255 'qqueue':
3256 (qqueue,
3256 (qqueue,
3257 [
3257 [
3258 ('l', 'list', False, _('list all available queues')),
3258 ('l', 'list', False, _('list all available queues')),
3259 ('c', 'create', False, _('create new queue')),
3259 ('c', 'create', False, _('create new queue')),
3260 ('', 'rename', False, _('rename active queue')),
3260 ('', 'rename', False, _('rename active queue')),
3261 ('', 'delete', False, _('delete reference to queue')),
3261 ('', 'delete', False, _('delete reference to queue')),
3262 ('', 'purge', False, _('delete queue, and remove patch dir')),
3262 ('', 'purge', False, _('delete queue, and remove patch dir')),
3263 ],
3263 ],
3264 _('[OPTION] [QUEUE]')),
3264 _('[OPTION] [QUEUE]')),
3265 }
3265 }
3266
3266
3267 colortable = {'qguard.negative': 'red',
3267 colortable = {'qguard.negative': 'red',
3268 'qguard.positive': 'yellow',
3268 'qguard.positive': 'yellow',
3269 'qguard.unguarded': 'green',
3269 'qguard.unguarded': 'green',
3270 'qseries.applied': 'blue bold underline',
3270 'qseries.applied': 'blue bold underline',
3271 'qseries.guarded': 'black bold',
3271 'qseries.guarded': 'black bold',
3272 'qseries.missing': 'red bold',
3272 'qseries.missing': 'red bold',
3273 'qseries.unapplied': 'black bold'}
3273 'qseries.unapplied': 'black bold'}
@@ -1,316 +1,316 b''
1 # notify.py - email notifications for mercurial
1 # notify.py - email notifications for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 '''hooks for sending email notifications at commit/push time
8 '''hooks for sending email notifications at commit/push time
9
9
10 Subscriptions can be managed through a hgrc file. Default mode is to
10 Subscriptions can be managed through a hgrc file. Default mode is to
11 print messages to stdout, for testing and configuring.
11 print messages to stdout, for testing and configuring.
12
12
13 To use, configure the notify extension and enable it in hgrc like
13 To use, configure the notify extension and enable it in hgrc like
14 this::
14 this::
15
15
16 [extensions]
16 [extensions]
17 notify =
17 notify =
18
18
19 [hooks]
19 [hooks]
20 # one email for each incoming changeset
20 # one email for each incoming changeset
21 incoming.notify = python:hgext.notify.hook
21 incoming.notify = python:hgext.notify.hook
22 # batch emails when many changesets incoming at one time
22 # batch emails when many changesets incoming at one time
23 changegroup.notify = python:hgext.notify.hook
23 changegroup.notify = python:hgext.notify.hook
24
24
25 [notify]
25 [notify]
26 # config items go here
26 # config items go here
27
27
28 Required configuration items::
28 Required configuration items::
29
29
30 config = /path/to/file # file containing subscriptions
30 config = /path/to/file # file containing subscriptions
31
31
32 Optional configuration items::
32 Optional configuration items::
33
33
34 test = True # print messages to stdout for testing
34 test = True # print messages to stdout for testing
35 strip = 3 # number of slashes to strip for url paths
35 strip = 3 # number of slashes to strip for url paths
36 domain = example.com # domain to use if committer missing domain
36 domain = example.com # domain to use if committer missing domain
37 style = ... # style file to use when formatting email
37 style = ... # style file to use when formatting email
38 template = ... # template to use when formatting email
38 template = ... # template to use when formatting email
39 incoming = ... # template to use when run as incoming hook
39 incoming = ... # template to use when run as incoming hook
40 changegroup = ... # template when run as changegroup hook
40 changegroup = ... # template when run as changegroup hook
41 maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
41 maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
42 maxsubject = 67 # truncate subject line longer than this
42 maxsubject = 67 # truncate subject line longer than this
43 diffstat = True # add a diffstat before the diff content
43 diffstat = True # add a diffstat before the diff content
44 sources = serve # notify if source of incoming changes in this list
44 sources = serve # notify if source of incoming changes in this list
45 # (serve == ssh or http, push, pull, bundle)
45 # (serve == ssh or http, push, pull, bundle)
46 merge = False # send notification for merges (default True)
46 merge = False # send notification for merges (default True)
47 [email]
47 [email]
48 from = user@host.com # email address to send as if none given
48 from = user@host.com # email address to send as if none given
49 [web]
49 [web]
50 baseurl = http://hgserver/... # root of hg web site for browsing commits
50 baseurl = http://hgserver/... # root of hg web site for browsing commits
51
51
52 The notify config file has same format as a regular hgrc file. It has
52 The notify config file has same format as a regular hgrc file. It has
53 two sections so you can express subscriptions in whatever way is
53 two sections so you can express subscriptions in whatever way is
54 handier for you.
54 handier for you.
55
55
56 ::
56 ::
57
57
58 [usersubs]
58 [usersubs]
59 # key is subscriber email, value is ","-separated list of glob patterns
59 # key is subscriber email, value is ","-separated list of glob patterns
60 user@host = pattern
60 user@host = pattern
61
61
62 [reposubs]
62 [reposubs]
63 # key is glob pattern, value is ","-separated list of subscriber emails
63 # key is glob pattern, value is ","-separated list of subscriber emails
64 pattern = user@host
64 pattern = user@host
65
65
66 Glob patterns are matched against path to repository root.
66 Glob patterns are matched against path to repository root.
67
67
68 If you like, you can put notify config file in repository that users
68 If you like, you can put notify config file in repository that users
69 can push changes to, they can manage their own subscriptions.
69 can push changes to, they can manage their own subscriptions.
70 '''
70 '''
71
71
72 from mercurial.i18n import _
72 from mercurial.i18n import _
73 from mercurial import patch, cmdutil, templater, util, mail
73 from mercurial import patch, cmdutil, templater, util, mail
74 import email.Parser, email.Errors, fnmatch, socket, time
74 import email.Parser, email.Errors, fnmatch, socket, time
75
75
76 # template for single changeset can include email headers.
76 # template for single changeset can include email headers.
77 single_template = '''
77 single_template = '''
78 Subject: changeset in {webroot}: {desc|firstline|strip}
78 Subject: changeset in {webroot}: {desc|firstline|strip}
79 From: {author}
79 From: {author}
80
80
81 changeset {node|short} in {root}
81 changeset {node|short} in {root}
82 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
82 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
83 description:
83 description:
84 \t{desc|tabindent|strip}
84 \t{desc|tabindent|strip}
85 '''.lstrip()
85 '''.lstrip()
86
86
87 # template for multiple changesets should not contain email headers,
87 # template for multiple changesets should not contain email headers,
88 # because only first set of headers will be used and result will look
88 # because only first set of headers will be used and result will look
89 # strange.
89 # strange.
90 multiple_template = '''
90 multiple_template = '''
91 changeset {node|short} in {root}
91 changeset {node|short} in {root}
92 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
92 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
93 summary: {desc|firstline}
93 summary: {desc|firstline}
94 '''
94 '''
95
95
96 deftemplates = {
96 deftemplates = {
97 'changegroup': multiple_template,
97 'changegroup': multiple_template,
98 }
98 }
99
99
100 class notifier(object):
100 class notifier(object):
101 '''email notification class.'''
101 '''email notification class.'''
102
102
103 def __init__(self, ui, repo, hooktype):
103 def __init__(self, ui, repo, hooktype):
104 self.ui = ui
104 self.ui = ui
105 cfg = self.ui.config('notify', 'config')
105 cfg = self.ui.config('notify', 'config')
106 if cfg:
106 if cfg:
107 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
107 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
108 self.repo = repo
108 self.repo = repo
109 self.stripcount = int(self.ui.config('notify', 'strip', 0))
109 self.stripcount = int(self.ui.config('notify', 'strip', 0))
110 self.root = self.strip(self.repo.root)
110 self.root = self.strip(self.repo.root)
111 self.domain = self.ui.config('notify', 'domain')
111 self.domain = self.ui.config('notify', 'domain')
112 self.test = self.ui.configbool('notify', 'test', True)
112 self.test = self.ui.configbool('notify', 'test', True)
113 self.charsets = mail._charsets(self.ui)
113 self.charsets = mail._charsets(self.ui)
114 self.subs = self.subscribers()
114 self.subs = self.subscribers()
115 self.merge = self.ui.configbool('notify', 'merge', True)
115 self.merge = self.ui.configbool('notify', 'merge', True)
116
116
117 mapfile = self.ui.config('notify', 'style')
117 mapfile = self.ui.config('notify', 'style')
118 template = (self.ui.config('notify', hooktype) or
118 template = (self.ui.config('notify', hooktype) or
119 self.ui.config('notify', 'template'))
119 self.ui.config('notify', 'template'))
120 self.t = cmdutil.changeset_templater(self.ui, self.repo,
120 self.t = cmdutil.changeset_templater(self.ui, self.repo,
121 False, None, mapfile, False)
121 False, None, mapfile, False)
122 if not mapfile and not template:
122 if not mapfile and not template:
123 template = deftemplates.get(hooktype) or single_template
123 template = deftemplates.get(hooktype) or single_template
124 if template:
124 if template:
125 template = templater.parsestring(template, quoted=False)
125 template = templater.parsestring(template, quoted=False)
126 self.t.use_template(template)
126 self.t.use_template(template)
127
127
128 def strip(self, path):
128 def strip(self, path):
129 '''strip leading slashes from local path, turn into web-safe path.'''
129 '''strip leading slashes from local path, turn into web-safe path.'''
130
130
131 path = util.pconvert(path)
131 path = util.pconvert(path)
132 count = self.stripcount
132 count = self.stripcount
133 while count > 0:
133 while count > 0:
134 c = path.find('/')
134 c = path.find('/')
135 if c == -1:
135 if c == -1:
136 break
136 break
137 path = path[c + 1:]
137 path = path[c + 1:]
138 count -= 1
138 count -= 1
139 return path
139 return path
140
140
141 def fixmail(self, addr):
141 def fixmail(self, addr):
142 '''try to clean up email addresses.'''
142 '''try to clean up email addresses.'''
143
143
144 addr = util.email(addr.strip())
144 addr = util.email(addr.strip())
145 if self.domain:
145 if self.domain:
146 a = addr.find('@localhost')
146 a = addr.find('@localhost')
147 if a != -1:
147 if a != -1:
148 addr = addr[:a]
148 addr = addr[:a]
149 if '@' not in addr:
149 if '@' not in addr:
150 return addr + '@' + self.domain
150 return addr + '@' + self.domain
151 return addr
151 return addr
152
152
153 def subscribers(self):
153 def subscribers(self):
154 '''return list of email addresses of subscribers to this repo.'''
154 '''return list of email addresses of subscribers to this repo.'''
155 subs = set()
155 subs = set()
156 for user, pats in self.ui.configitems('usersubs'):
156 for user, pats in self.ui.configitems('usersubs'):
157 for pat in pats.split(','):
157 for pat in pats.split(','):
158 if fnmatch.fnmatch(self.repo.root, pat.strip()):
158 if fnmatch.fnmatch(self.repo.root, pat.strip()):
159 subs.add(self.fixmail(user))
159 subs.add(self.fixmail(user))
160 for pat, users in self.ui.configitems('reposubs'):
160 for pat, users in self.ui.configitems('reposubs'):
161 if fnmatch.fnmatch(self.repo.root, pat):
161 if fnmatch.fnmatch(self.repo.root, pat):
162 for user in users.split(','):
162 for user in users.split(','):
163 subs.add(self.fixmail(user))
163 subs.add(self.fixmail(user))
164 return [mail.addressencode(self.ui, s, self.charsets, self.test)
164 return [mail.addressencode(self.ui, s, self.charsets, self.test)
165 for s in sorted(subs)]
165 for s in sorted(subs)]
166
166
167 def url(self, path=None):
167 def url(self, path=None):
168 return self.ui.config('web', 'baseurl') + (path or self.root)
168 return self.ui.config('web', 'baseurl') + (path or self.root)
169
169
170 def node(self, ctx, **props):
170 def node(self, ctx, **props):
171 '''format one changeset, unless it is a suppressed merge.'''
171 '''format one changeset, unless it is a suppressed merge.'''
172 if not self.merge and len(ctx.parents()) > 1:
172 if not self.merge and len(ctx.parents()) > 1:
173 return False
173 return False
174 self.t.show(ctx, changes=ctx.changeset(),
174 self.t.show(ctx, changes=ctx.changeset(),
175 baseurl=self.ui.config('web', 'baseurl'),
175 baseurl=self.ui.config('web', 'baseurl'),
176 root=self.repo.root, webroot=self.root, **props)
176 root=self.repo.root, webroot=self.root, **props)
177 return True
177 return True
178
178
179 def skipsource(self, source):
179 def skipsource(self, source):
180 '''true if incoming changes from this source should be skipped.'''
180 '''true if incoming changes from this source should be skipped.'''
181 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
181 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
182 return source not in ok_sources
182 return source not in ok_sources
183
183
184 def send(self, ctx, count, data):
184 def send(self, ctx, count, data):
185 '''send message.'''
185 '''send message.'''
186
186
187 p = email.Parser.Parser()
187 p = email.Parser.Parser()
188 try:
188 try:
189 msg = p.parsestr(data)
189 msg = p.parsestr(data)
190 except email.Errors.MessageParseError, inst:
190 except email.Errors.MessageParseError, inst:
191 raise util.Abort(inst)
191 raise util.Abort(inst)
192
192
193 # store sender and subject
193 # store sender and subject
194 sender, subject = msg['From'], msg['Subject']
194 sender, subject = msg['From'], msg['Subject']
195 del msg['From'], msg['Subject']
195 del msg['From'], msg['Subject']
196
196
197 if not msg.is_multipart():
197 if not msg.is_multipart():
198 # create fresh mime message from scratch
198 # create fresh mime message from scratch
199 # (multipart templates must take care of this themselves)
199 # (multipart templates must take care of this themselves)
200 headers = msg.items()
200 headers = msg.items()
201 payload = msg.get_payload()
201 payload = msg.get_payload()
202 # for notification prefer readability over data precision
202 # for notification prefer readability over data precision
203 msg = mail.mimeencode(self.ui, payload, self.charsets, self.test)
203 msg = mail.mimeencode(self.ui, payload, self.charsets, self.test)
204 # reinstate custom headers
204 # reinstate custom headers
205 for k, v in headers:
205 for k, v in headers:
206 msg[k] = v
206 msg[k] = v
207
207
208 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
208 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
209
209
210 # try to make subject line exist and be useful
210 # try to make subject line exist and be useful
211 if not subject:
211 if not subject:
212 if count > 1:
212 if count > 1:
213 subject = _('%s: %d new changesets') % (self.root, count)
213 subject = _('%s: %d new changesets') % (self.root, count)
214 else:
214 else:
215 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
215 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
216 subject = '%s: %s' % (self.root, s)
216 subject = '%s: %s' % (self.root, s)
217 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
217 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
218 if maxsubject:
218 if maxsubject:
219 subject = util.ellipsis(subject, maxsubject)
219 subject = util.ellipsis(subject, maxsubject)
220 msg['Subject'] = mail.headencode(self.ui, subject,
220 msg['Subject'] = mail.headencode(self.ui, subject,
221 self.charsets, self.test)
221 self.charsets, self.test)
222
222
223 # try to make message have proper sender
223 # try to make message have proper sender
224 if not sender:
224 if not sender:
225 sender = self.ui.config('email', 'from') or self.ui.username()
225 sender = self.ui.config('email', 'from') or self.ui.username()
226 if '@' not in sender or '@localhost' in sender:
226 if '@' not in sender or '@localhost' in sender:
227 sender = self.fixmail(sender)
227 sender = self.fixmail(sender)
228 msg['From'] = mail.addressencode(self.ui, sender,
228 msg['From'] = mail.addressencode(self.ui, sender,
229 self.charsets, self.test)
229 self.charsets, self.test)
230
230
231 msg['X-Hg-Notification'] = 'changeset %s' % ctx
231 msg['X-Hg-Notification'] = 'changeset %s' % ctx
232 if not msg['Message-Id']:
232 if not msg['Message-Id']:
233 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
233 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
234 (ctx, int(time.time()),
234 (ctx, int(time.time()),
235 hash(self.repo.root), socket.getfqdn()))
235 hash(self.repo.root), socket.getfqdn()))
236 msg['To'] = ', '.join(self.subs)
236 msg['To'] = ', '.join(self.subs)
237
237
238 msgtext = msg.as_string()
238 msgtext = msg.as_string()
239 if self.test:
239 if self.test:
240 self.ui.write(msgtext)
240 self.ui.write(msgtext)
241 if not msgtext.endswith('\n'):
241 if not msgtext.endswith('\n'):
242 self.ui.write('\n')
242 self.ui.write('\n')
243 else:
243 else:
244 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
244 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
245 (len(self.subs), count))
245 (len(self.subs), count))
246 mail.sendmail(self.ui, util.email(msg['From']),
246 mail.sendmail(self.ui, util.email(msg['From']),
247 self.subs, msgtext)
247 self.subs, msgtext)
248
248
249 def diff(self, ctx, ref=None):
249 def diff(self, ctx, ref=None):
250
250
251 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
251 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
252 prev = ctx.parents()[0].node()
252 prev = ctx.p1().node()
253 ref = ref and ref.node() or ctx.node()
253 ref = ref and ref.node() or ctx.node()
254 chunks = patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
254 chunks = patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
255 difflines = ''.join(chunks).splitlines()
255 difflines = ''.join(chunks).splitlines()
256
256
257 if self.ui.configbool('notify', 'diffstat', True):
257 if self.ui.configbool('notify', 'diffstat', True):
258 s = patch.diffstat(difflines)
258 s = patch.diffstat(difflines)
259 # s may be nil, don't include the header if it is
259 # s may be nil, don't include the header if it is
260 if s:
260 if s:
261 self.ui.write('\ndiffstat:\n\n%s' % s)
261 self.ui.write('\ndiffstat:\n\n%s' % s)
262
262
263 if maxdiff == 0:
263 if maxdiff == 0:
264 return
264 return
265 elif maxdiff > 0 and len(difflines) > maxdiff:
265 elif maxdiff > 0 and len(difflines) > maxdiff:
266 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
266 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
267 self.ui.write(msg % (len(difflines), maxdiff))
267 self.ui.write(msg % (len(difflines), maxdiff))
268 difflines = difflines[:maxdiff]
268 difflines = difflines[:maxdiff]
269 elif difflines:
269 elif difflines:
270 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
270 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
271
271
272 self.ui.write("\n".join(difflines))
272 self.ui.write("\n".join(difflines))
273
273
274 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
274 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
275 '''send email notifications to interested subscribers.
275 '''send email notifications to interested subscribers.
276
276
277 if used as changegroup hook, send one email for all changesets in
277 if used as changegroup hook, send one email for all changesets in
278 changegroup. else send one email per changeset.'''
278 changegroup. else send one email per changeset.'''
279
279
280 n = notifier(ui, repo, hooktype)
280 n = notifier(ui, repo, hooktype)
281 ctx = repo[node]
281 ctx = repo[node]
282
282
283 if not n.subs:
283 if not n.subs:
284 ui.debug('notify: no subscribers to repository %s\n' % n.root)
284 ui.debug('notify: no subscribers to repository %s\n' % n.root)
285 return
285 return
286 if n.skipsource(source):
286 if n.skipsource(source):
287 ui.debug('notify: changes have source "%s" - skipping\n' % source)
287 ui.debug('notify: changes have source "%s" - skipping\n' % source)
288 return
288 return
289
289
290 ui.pushbuffer()
290 ui.pushbuffer()
291 data = ''
291 data = ''
292 count = 0
292 count = 0
293 if hooktype == 'changegroup':
293 if hooktype == 'changegroup':
294 start, end = ctx.rev(), len(repo)
294 start, end = ctx.rev(), len(repo)
295 for rev in xrange(start, end):
295 for rev in xrange(start, end):
296 if n.node(repo[rev]):
296 if n.node(repo[rev]):
297 count += 1
297 count += 1
298 else:
298 else:
299 data += ui.popbuffer()
299 data += ui.popbuffer()
300 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
300 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
301 (rev, repo[rev].hex()[:12]))
301 (rev, repo[rev].hex()[:12]))
302 ui.pushbuffer()
302 ui.pushbuffer()
303 if count:
303 if count:
304 n.diff(ctx, repo['tip'])
304 n.diff(ctx, repo['tip'])
305 else:
305 else:
306 if not n.node(ctx):
306 if not n.node(ctx):
307 ui.popbuffer()
307 ui.popbuffer()
308 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
308 ui.note(_('notify: suppressing notification for merge %d:%s\n') %
309 (ctx.rev(), ctx.hex()[:12]))
309 (ctx.rev(), ctx.hex()[:12]))
310 return
310 return
311 count += 1
311 count += 1
312 n.diff(ctx)
312 n.diff(ctx)
313
313
314 data += ui.popbuffer()
314 data += ui.popbuffer()
315 if count:
315 if count:
316 n.send(ctx, count, data)
316 n.send(ctx, count, data)
@@ -1,589 +1,589 b''
1 # rebase.py - rebasing feature for mercurial
1 # rebase.py - rebasing feature for mercurial
2 #
2 #
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot 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 '''command to move sets of revisions to a different ancestor
8 '''command to move sets of revisions to a different ancestor
9
9
10 This extension lets you rebase changesets in an existing Mercurial
10 This extension lets you rebase changesets in an existing Mercurial
11 repository.
11 repository.
12
12
13 For more information:
13 For more information:
14 http://mercurial.selenic.com/wiki/RebaseExtension
14 http://mercurial.selenic.com/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from mercurial import hg, util, repair, merge, cmdutil, commands
17 from mercurial import hg, util, repair, merge, cmdutil, commands
18 from mercurial import extensions, ancestor, copies, patch
18 from mercurial import extensions, ancestor, copies, patch
19 from mercurial.commands import templateopts
19 from mercurial.commands import templateopts
20 from mercurial.node import nullrev
20 from mercurial.node import nullrev
21 from mercurial.lock import release
21 from mercurial.lock import release
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 import os, errno
23 import os, errno
24
24
25 nullmerge = -2
25 nullmerge = -2
26
26
27 def rebase(ui, repo, **opts):
27 def rebase(ui, repo, **opts):
28 """move changeset (and descendants) to a different branch
28 """move changeset (and descendants) to a different branch
29
29
30 Rebase uses repeated merging to graft changesets from one part of
30 Rebase uses repeated merging to graft changesets from one part of
31 history (the source) onto another (the destination). This can be
31 history (the source) onto another (the destination). This can be
32 useful for linearizing *local* changes relative to a master
32 useful for linearizing *local* changes relative to a master
33 development tree.
33 development tree.
34
34
35 You should not rebase changesets that have already been shared
35 You should not rebase changesets that have already been shared
36 with others. Doing so will force everybody else to perform the
36 with others. Doing so will force everybody else to perform the
37 same rebase or they will end up with duplicated changesets after
37 same rebase or they will end up with duplicated changesets after
38 pulling in your rebased changesets.
38 pulling in your rebased changesets.
39
39
40 If you don't specify a destination changeset (``-d/--dest``),
40 If you don't specify a destination changeset (``-d/--dest``),
41 rebase uses the tipmost head of the current named branch as the
41 rebase uses the tipmost head of the current named branch as the
42 destination. (The destination changeset is not modified by
42 destination. (The destination changeset is not modified by
43 rebasing, but new changesets are added as its descendants.)
43 rebasing, but new changesets are added as its descendants.)
44
44
45 You can specify which changesets to rebase in two ways: as a
45 You can specify which changesets to rebase in two ways: as a
46 "source" changeset or as a "base" changeset. Both are shorthand
46 "source" changeset or as a "base" changeset. Both are shorthand
47 for a topologically related set of changesets (the "source
47 for a topologically related set of changesets (the "source
48 branch"). If you specify source (``-s/--source``), rebase will
48 branch"). If you specify source (``-s/--source``), rebase will
49 rebase that changeset and all of its descendants onto dest. If you
49 rebase that changeset and all of its descendants onto dest. If you
50 specify base (``-b/--base``), rebase will select ancestors of base
50 specify base (``-b/--base``), rebase will select ancestors of base
51 back to but not including the common ancestor with dest. Thus,
51 back to but not including the common ancestor with dest. Thus,
52 ``-b`` is less precise but more convenient than ``-s``: you can
52 ``-b`` is less precise but more convenient than ``-s``: you can
53 specify any changeset in the source branch, and rebase will select
53 specify any changeset in the source branch, and rebase will select
54 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
54 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
55 uses the parent of the working directory as the base.
55 uses the parent of the working directory as the base.
56
56
57 By default, rebase recreates the changesets in the source branch
57 By default, rebase recreates the changesets in the source branch
58 as descendants of dest and then destroys the originals. Use
58 as descendants of dest and then destroys the originals. Use
59 ``--keep`` to preserve the original source changesets. Some
59 ``--keep`` to preserve the original source changesets. Some
60 changesets in the source branch (e.g. merges from the destination
60 changesets in the source branch (e.g. merges from the destination
61 branch) may be dropped if they no longer contribute any change.
61 branch) may be dropped if they no longer contribute any change.
62
62
63 One result of the rules for selecting the destination changeset
63 One result of the rules for selecting the destination changeset
64 and source branch is that, unlike ``merge``, rebase will do
64 and source branch is that, unlike ``merge``, rebase will do
65 nothing if you are at the latest (tipmost) head of a named branch
65 nothing if you are at the latest (tipmost) head of a named branch
66 with two heads. You need to explicitly specify source and/or
66 with two heads. You need to explicitly specify source and/or
67 destination (or ``update`` to the other head, if it's the head of
67 destination (or ``update`` to the other head, if it's the head of
68 the intended source branch).
68 the intended source branch).
69
69
70 If a rebase is interrupted to manually resolve a merge, it can be
70 If a rebase is interrupted to manually resolve a merge, it can be
71 continued with --continue/-c or aborted with --abort/-a.
71 continued with --continue/-c or aborted with --abort/-a.
72
72
73 Returns 0 on success, 1 if nothing to rebase.
73 Returns 0 on success, 1 if nothing to rebase.
74 """
74 """
75 originalwd = target = None
75 originalwd = target = None
76 external = nullrev
76 external = nullrev
77 state = {}
77 state = {}
78 skipped = set()
78 skipped = set()
79 targetancestors = set()
79 targetancestors = set()
80
80
81 lock = wlock = None
81 lock = wlock = None
82 try:
82 try:
83 lock = repo.lock()
83 lock = repo.lock()
84 wlock = repo.wlock()
84 wlock = repo.wlock()
85
85
86 # Validate input and define rebasing points
86 # Validate input and define rebasing points
87 destf = opts.get('dest', None)
87 destf = opts.get('dest', None)
88 srcf = opts.get('source', None)
88 srcf = opts.get('source', None)
89 basef = opts.get('base', None)
89 basef = opts.get('base', None)
90 contf = opts.get('continue')
90 contf = opts.get('continue')
91 abortf = opts.get('abort')
91 abortf = opts.get('abort')
92 collapsef = opts.get('collapse', False)
92 collapsef = opts.get('collapse', False)
93 collapsemsg = cmdutil.logmessage(opts)
93 collapsemsg = cmdutil.logmessage(opts)
94 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
94 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
95 keepf = opts.get('keep', False)
95 keepf = opts.get('keep', False)
96 keepbranchesf = opts.get('keepbranches', False)
96 keepbranchesf = opts.get('keepbranches', False)
97 detachf = opts.get('detach', False)
97 detachf = opts.get('detach', False)
98 # keepopen is not meant for use on the command line, but by
98 # keepopen is not meant for use on the command line, but by
99 # other extensions
99 # other extensions
100 keepopen = opts.get('keepopen', False)
100 keepopen = opts.get('keepopen', False)
101
101
102 if collapsemsg and not collapsef:
102 if collapsemsg and not collapsef:
103 raise util.Abort(
103 raise util.Abort(
104 _('message can only be specified with collapse'))
104 _('message can only be specified with collapse'))
105
105
106 if contf or abortf:
106 if contf or abortf:
107 if contf and abortf:
107 if contf and abortf:
108 raise util.Abort(_('cannot use both abort and continue'))
108 raise util.Abort(_('cannot use both abort and continue'))
109 if collapsef:
109 if collapsef:
110 raise util.Abort(
110 raise util.Abort(
111 _('cannot use collapse with continue or abort'))
111 _('cannot use collapse with continue or abort'))
112 if detachf:
112 if detachf:
113 raise util.Abort(_('cannot use detach with continue or abort'))
113 raise util.Abort(_('cannot use detach with continue or abort'))
114 if srcf or basef or destf:
114 if srcf or basef or destf:
115 raise util.Abort(
115 raise util.Abort(
116 _('abort and continue do not allow specifying revisions'))
116 _('abort and continue do not allow specifying revisions'))
117 if opts.get('tool', False):
117 if opts.get('tool', False):
118 ui.warn(_('tool option will be ignored\n'))
118 ui.warn(_('tool option will be ignored\n'))
119
119
120 (originalwd, target, state, skipped, collapsef, keepf,
120 (originalwd, target, state, skipped, collapsef, keepf,
121 keepbranchesf, external) = restorestatus(repo)
121 keepbranchesf, external) = restorestatus(repo)
122 if abortf:
122 if abortf:
123 return abort(repo, originalwd, target, state)
123 return abort(repo, originalwd, target, state)
124 else:
124 else:
125 if srcf and basef:
125 if srcf and basef:
126 raise util.Abort(_('cannot specify both a '
126 raise util.Abort(_('cannot specify both a '
127 'revision and a base'))
127 'revision and a base'))
128 if detachf:
128 if detachf:
129 if not srcf:
129 if not srcf:
130 raise util.Abort(
130 raise util.Abort(
131 _('detach requires a revision to be specified'))
131 _('detach requires a revision to be specified'))
132 if basef:
132 if basef:
133 raise util.Abort(_('cannot specify a base with detach'))
133 raise util.Abort(_('cannot specify a base with detach'))
134
134
135 cmdutil.bail_if_changed(repo)
135 cmdutil.bail_if_changed(repo)
136 result = buildstate(repo, destf, srcf, basef, detachf)
136 result = buildstate(repo, destf, srcf, basef, detachf)
137 if not result:
137 if not result:
138 # Empty state built, nothing to rebase
138 # Empty state built, nothing to rebase
139 ui.status(_('nothing to rebase\n'))
139 ui.status(_('nothing to rebase\n'))
140 return 1
140 return 1
141 else:
141 else:
142 originalwd, target, state = result
142 originalwd, target, state = result
143 if collapsef:
143 if collapsef:
144 targetancestors = set(repo.changelog.ancestors(target))
144 targetancestors = set(repo.changelog.ancestors(target))
145 external = checkexternal(repo, state, targetancestors)
145 external = checkexternal(repo, state, targetancestors)
146
146
147 if keepbranchesf:
147 if keepbranchesf:
148 assert not extrafn, 'cannot use both keepbranches and extrafn'
148 assert not extrafn, 'cannot use both keepbranches and extrafn'
149 def extrafn(ctx, extra):
149 def extrafn(ctx, extra):
150 extra['branch'] = ctx.branch()
150 extra['branch'] = ctx.branch()
151
151
152 # Rebase
152 # Rebase
153 if not targetancestors:
153 if not targetancestors:
154 targetancestors = set(repo.changelog.ancestors(target))
154 targetancestors = set(repo.changelog.ancestors(target))
155 targetancestors.add(target)
155 targetancestors.add(target)
156
156
157 sortedstate = sorted(state)
157 sortedstate = sorted(state)
158 total = len(sortedstate)
158 total = len(sortedstate)
159 pos = 0
159 pos = 0
160 for rev in sortedstate:
160 for rev in sortedstate:
161 pos += 1
161 pos += 1
162 if state[rev] == -1:
162 if state[rev] == -1:
163 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
163 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
164 _('changesets'), total)
164 _('changesets'), total)
165 storestatus(repo, originalwd, target, state, collapsef, keepf,
165 storestatus(repo, originalwd, target, state, collapsef, keepf,
166 keepbranchesf, external)
166 keepbranchesf, external)
167 p1, p2 = defineparents(repo, rev, target, state,
167 p1, p2 = defineparents(repo, rev, target, state,
168 targetancestors)
168 targetancestors)
169 if len(repo.parents()) == 2:
169 if len(repo.parents()) == 2:
170 repo.ui.debug('resuming interrupted rebase\n')
170 repo.ui.debug('resuming interrupted rebase\n')
171 else:
171 else:
172 try:
172 try:
173 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
173 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
174 stats = rebasenode(repo, rev, p1, p2, state)
174 stats = rebasenode(repo, rev, p1, p2, state)
175 if stats and stats[3] > 0:
175 if stats and stats[3] > 0:
176 raise util.Abort(_('unresolved conflicts (see hg '
176 raise util.Abort(_('unresolved conflicts (see hg '
177 'resolve, then hg rebase --continue)'))
177 'resolve, then hg rebase --continue)'))
178 finally:
178 finally:
179 ui.setconfig('ui', 'forcemerge', '')
179 ui.setconfig('ui', 'forcemerge', '')
180 updatedirstate(repo, rev, target, p2)
180 updatedirstate(repo, rev, target, p2)
181 if not collapsef:
181 if not collapsef:
182 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
182 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
183 else:
183 else:
184 # Skip commit if we are collapsing
184 # Skip commit if we are collapsing
185 repo.dirstate.setparents(repo[p1].node())
185 repo.dirstate.setparents(repo[p1].node())
186 newrev = None
186 newrev = None
187 # Update the state
187 # Update the state
188 if newrev is not None:
188 if newrev is not None:
189 state[rev] = repo[newrev].rev()
189 state[rev] = repo[newrev].rev()
190 else:
190 else:
191 if not collapsef:
191 if not collapsef:
192 ui.note(_('no changes, revision %d skipped\n') % rev)
192 ui.note(_('no changes, revision %d skipped\n') % rev)
193 ui.debug('next revision set to %s\n' % p1)
193 ui.debug('next revision set to %s\n' % p1)
194 skipped.add(rev)
194 skipped.add(rev)
195 state[rev] = p1
195 state[rev] = p1
196
196
197 ui.progress(_('rebasing'), None)
197 ui.progress(_('rebasing'), None)
198 ui.note(_('rebase merging completed\n'))
198 ui.note(_('rebase merging completed\n'))
199
199
200 if collapsef and not keepopen:
200 if collapsef and not keepopen:
201 p1, p2 = defineparents(repo, min(state), target,
201 p1, p2 = defineparents(repo, min(state), target,
202 state, targetancestors)
202 state, targetancestors)
203 if collapsemsg:
203 if collapsemsg:
204 commitmsg = collapsemsg
204 commitmsg = collapsemsg
205 else:
205 else:
206 commitmsg = 'Collapsed revision'
206 commitmsg = 'Collapsed revision'
207 for rebased in state:
207 for rebased in state:
208 if rebased not in skipped and state[rebased] != nullmerge:
208 if rebased not in skipped and state[rebased] != nullmerge:
209 commitmsg += '\n* %s' % repo[rebased].description()
209 commitmsg += '\n* %s' % repo[rebased].description()
210 commitmsg = ui.edit(commitmsg, repo.ui.username())
210 commitmsg = ui.edit(commitmsg, repo.ui.username())
211 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
211 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
212 extrafn=extrafn)
212 extrafn=extrafn)
213
213
214 if 'qtip' in repo.tags():
214 if 'qtip' in repo.tags():
215 updatemq(repo, state, skipped, **opts)
215 updatemq(repo, state, skipped, **opts)
216
216
217 if not keepf:
217 if not keepf:
218 # Remove no more useful revisions
218 # Remove no more useful revisions
219 rebased = [rev for rev in state if state[rev] != nullmerge]
219 rebased = [rev for rev in state if state[rev] != nullmerge]
220 if rebased:
220 if rebased:
221 if set(repo.changelog.descendants(min(rebased))) - set(state):
221 if set(repo.changelog.descendants(min(rebased))) - set(state):
222 ui.warn(_("warning: new changesets detected "
222 ui.warn(_("warning: new changesets detected "
223 "on source branch, not stripping\n"))
223 "on source branch, not stripping\n"))
224 else:
224 else:
225 # backup the old csets by default
225 # backup the old csets by default
226 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
226 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
227
227
228 clearstatus(repo)
228 clearstatus(repo)
229 ui.note(_("rebase completed\n"))
229 ui.note(_("rebase completed\n"))
230 if os.path.exists(repo.sjoin('undo')):
230 if os.path.exists(repo.sjoin('undo')):
231 util.unlinkpath(repo.sjoin('undo'))
231 util.unlinkpath(repo.sjoin('undo'))
232 if skipped:
232 if skipped:
233 ui.note(_("%d revisions have been skipped\n") % len(skipped))
233 ui.note(_("%d revisions have been skipped\n") % len(skipped))
234 finally:
234 finally:
235 release(lock, wlock)
235 release(lock, wlock)
236
236
237 def checkexternal(repo, state, targetancestors):
237 def checkexternal(repo, state, targetancestors):
238 """Check whether one or more external revisions need to be taken in
238 """Check whether one or more external revisions need to be taken in
239 consideration. In the latter case, abort.
239 consideration. In the latter case, abort.
240 """
240 """
241 external = nullrev
241 external = nullrev
242 source = min(state)
242 source = min(state)
243 for rev in state:
243 for rev in state:
244 if rev == source:
244 if rev == source:
245 continue
245 continue
246 # Check externals and fail if there are more than one
246 # Check externals and fail if there are more than one
247 for p in repo[rev].parents():
247 for p in repo[rev].parents():
248 if (p.rev() not in state
248 if (p.rev() not in state
249 and p.rev() not in targetancestors):
249 and p.rev() not in targetancestors):
250 if external != nullrev:
250 if external != nullrev:
251 raise util.Abort(_('unable to collapse, there is more '
251 raise util.Abort(_('unable to collapse, there is more '
252 'than one external parent'))
252 'than one external parent'))
253 external = p.rev()
253 external = p.rev()
254 return external
254 return external
255
255
256 def updatedirstate(repo, rev, p1, p2):
256 def updatedirstate(repo, rev, p1, p2):
257 """Keep track of renamed files in the revision that is going to be rebased
257 """Keep track of renamed files in the revision that is going to be rebased
258 """
258 """
259 # Here we simulate the copies and renames in the source changeset
259 # Here we simulate the copies and renames in the source changeset
260 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
260 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
261 m1 = repo[rev].manifest()
261 m1 = repo[rev].manifest()
262 m2 = repo[p1].manifest()
262 m2 = repo[p1].manifest()
263 for k, v in cop.iteritems():
263 for k, v in cop.iteritems():
264 if k in m1:
264 if k in m1:
265 if v in m1 or v in m2:
265 if v in m1 or v in m2:
266 repo.dirstate.copy(v, k)
266 repo.dirstate.copy(v, k)
267 if v in m2 and v not in m1 and k in m2:
267 if v in m2 and v not in m1 and k in m2:
268 repo.dirstate.remove(v)
268 repo.dirstate.remove(v)
269
269
270 def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None):
270 def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None):
271 'Commit the changes and store useful information in extra'
271 'Commit the changes and store useful information in extra'
272 try:
272 try:
273 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
273 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
274 ctx = repo[rev]
274 ctx = repo[rev]
275 if commitmsg is None:
275 if commitmsg is None:
276 commitmsg = ctx.description()
276 commitmsg = ctx.description()
277 extra = {'rebase_source': ctx.hex()}
277 extra = {'rebase_source': ctx.hex()}
278 if extrafn:
278 if extrafn:
279 extrafn(ctx, extra)
279 extrafn(ctx, extra)
280 # Commit might fail if unresolved files exist
280 # Commit might fail if unresolved files exist
281 newrev = repo.commit(text=commitmsg, user=ctx.user(),
281 newrev = repo.commit(text=commitmsg, user=ctx.user(),
282 date=ctx.date(), extra=extra)
282 date=ctx.date(), extra=extra)
283 repo.dirstate.setbranch(repo[newrev].branch())
283 repo.dirstate.setbranch(repo[newrev].branch())
284 return newrev
284 return newrev
285 except util.Abort:
285 except util.Abort:
286 # Invalidate the previous setparents
286 # Invalidate the previous setparents
287 repo.dirstate.invalidate()
287 repo.dirstate.invalidate()
288 raise
288 raise
289
289
290 def rebasenode(repo, rev, p1, p2, state):
290 def rebasenode(repo, rev, p1, p2, state):
291 'Rebase a single revision'
291 'Rebase a single revision'
292 # Merge phase
292 # Merge phase
293 # Update to target and merge it with local
293 # Update to target and merge it with local
294 if repo['.'].rev() != repo[p1].rev():
294 if repo['.'].rev() != repo[p1].rev():
295 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
295 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
296 merge.update(repo, p1, False, True, False)
296 merge.update(repo, p1, False, True, False)
297 else:
297 else:
298 repo.ui.debug(" already in target\n")
298 repo.ui.debug(" already in target\n")
299 repo.dirstate.write()
299 repo.dirstate.write()
300 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
300 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
301 base = None
301 base = None
302 if repo[rev].rev() != repo[min(state)].rev():
302 if repo[rev].rev() != repo[min(state)].rev():
303 base = repo[rev].parents()[0].node()
303 base = repo[rev].p1().node()
304 return merge.update(repo, rev, True, True, False, base)
304 return merge.update(repo, rev, True, True, False, base)
305
305
306 def defineparents(repo, rev, target, state, targetancestors):
306 def defineparents(repo, rev, target, state, targetancestors):
307 'Return the new parent relationship of the revision that will be rebased'
307 'Return the new parent relationship of the revision that will be rebased'
308 parents = repo[rev].parents()
308 parents = repo[rev].parents()
309 p1 = p2 = nullrev
309 p1 = p2 = nullrev
310
310
311 P1n = parents[0].rev()
311 P1n = parents[0].rev()
312 if P1n in targetancestors:
312 if P1n in targetancestors:
313 p1 = target
313 p1 = target
314 elif P1n in state:
314 elif P1n in state:
315 if state[P1n] == nullmerge:
315 if state[P1n] == nullmerge:
316 p1 = target
316 p1 = target
317 else:
317 else:
318 p1 = state[P1n]
318 p1 = state[P1n]
319 else: # P1n external
319 else: # P1n external
320 p1 = target
320 p1 = target
321 p2 = P1n
321 p2 = P1n
322
322
323 if len(parents) == 2 and parents[1].rev() not in targetancestors:
323 if len(parents) == 2 and parents[1].rev() not in targetancestors:
324 P2n = parents[1].rev()
324 P2n = parents[1].rev()
325 # interesting second parent
325 # interesting second parent
326 if P2n in state:
326 if P2n in state:
327 if p1 == target: # P1n in targetancestors or external
327 if p1 == target: # P1n in targetancestors or external
328 p1 = state[P2n]
328 p1 = state[P2n]
329 else:
329 else:
330 p2 = state[P2n]
330 p2 = state[P2n]
331 else: # P2n external
331 else: # P2n external
332 if p2 != nullrev: # P1n external too => rev is a merged revision
332 if p2 != nullrev: # P1n external too => rev is a merged revision
333 raise util.Abort(_('cannot use revision %d as base, result '
333 raise util.Abort(_('cannot use revision %d as base, result '
334 'would have 3 parents') % rev)
334 'would have 3 parents') % rev)
335 p2 = P2n
335 p2 = P2n
336 repo.ui.debug(" future parents are %d and %d\n" %
336 repo.ui.debug(" future parents are %d and %d\n" %
337 (repo[p1].rev(), repo[p2].rev()))
337 (repo[p1].rev(), repo[p2].rev()))
338 return p1, p2
338 return p1, p2
339
339
340 def isagitpatch(repo, patchname):
340 def isagitpatch(repo, patchname):
341 'Return true if the given patch is in git format'
341 'Return true if the given patch is in git format'
342 mqpatch = os.path.join(repo.mq.path, patchname)
342 mqpatch = os.path.join(repo.mq.path, patchname)
343 for line in patch.linereader(file(mqpatch, 'rb')):
343 for line in patch.linereader(file(mqpatch, 'rb')):
344 if line.startswith('diff --git'):
344 if line.startswith('diff --git'):
345 return True
345 return True
346 return False
346 return False
347
347
348 def updatemq(repo, state, skipped, **opts):
348 def updatemq(repo, state, skipped, **opts):
349 'Update rebased mq patches - finalize and then import them'
349 'Update rebased mq patches - finalize and then import them'
350 mqrebase = {}
350 mqrebase = {}
351 mq = repo.mq
351 mq = repo.mq
352 original_series = mq.full_series[:]
352 original_series = mq.full_series[:]
353
353
354 for p in mq.applied:
354 for p in mq.applied:
355 rev = repo[p.node].rev()
355 rev = repo[p.node].rev()
356 if rev in state:
356 if rev in state:
357 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
357 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
358 (rev, p.name))
358 (rev, p.name))
359 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
359 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
360
360
361 if mqrebase:
361 if mqrebase:
362 mq.finish(repo, mqrebase.keys())
362 mq.finish(repo, mqrebase.keys())
363
363
364 # We must start import from the newest revision
364 # We must start import from the newest revision
365 for rev in sorted(mqrebase, reverse=True):
365 for rev in sorted(mqrebase, reverse=True):
366 if rev not in skipped:
366 if rev not in skipped:
367 name, isgit = mqrebase[rev]
367 name, isgit = mqrebase[rev]
368 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
368 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
369 mq.qimport(repo, (), patchname=name, git=isgit,
369 mq.qimport(repo, (), patchname=name, git=isgit,
370 rev=[str(state[rev])])
370 rev=[str(state[rev])])
371
371
372 # Restore missing guards
372 # Restore missing guards
373 for s in original_series:
373 for s in original_series:
374 pname = mq.guard_re.split(s, 1)[0]
374 pname = mq.guard_re.split(s, 1)[0]
375 if pname in mq.full_series:
375 if pname in mq.full_series:
376 repo.ui.debug('restoring guard for patch %s' % (pname))
376 repo.ui.debug('restoring guard for patch %s' % (pname))
377 mq.full_series.remove(pname)
377 mq.full_series.remove(pname)
378 mq.full_series.append(s)
378 mq.full_series.append(s)
379 mq.series_dirty = True
379 mq.series_dirty = True
380 mq.save_dirty()
380 mq.save_dirty()
381
381
382 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
382 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
383 external):
383 external):
384 'Store the current status to allow recovery'
384 'Store the current status to allow recovery'
385 f = repo.opener("rebasestate", "w")
385 f = repo.opener("rebasestate", "w")
386 f.write(repo[originalwd].hex() + '\n')
386 f.write(repo[originalwd].hex() + '\n')
387 f.write(repo[target].hex() + '\n')
387 f.write(repo[target].hex() + '\n')
388 f.write(repo[external].hex() + '\n')
388 f.write(repo[external].hex() + '\n')
389 f.write('%d\n' % int(collapse))
389 f.write('%d\n' % int(collapse))
390 f.write('%d\n' % int(keep))
390 f.write('%d\n' % int(keep))
391 f.write('%d\n' % int(keepbranches))
391 f.write('%d\n' % int(keepbranches))
392 for d, v in state.iteritems():
392 for d, v in state.iteritems():
393 oldrev = repo[d].hex()
393 oldrev = repo[d].hex()
394 newrev = repo[v].hex()
394 newrev = repo[v].hex()
395 f.write("%s:%s\n" % (oldrev, newrev))
395 f.write("%s:%s\n" % (oldrev, newrev))
396 f.close()
396 f.close()
397 repo.ui.debug('rebase status stored\n')
397 repo.ui.debug('rebase status stored\n')
398
398
399 def clearstatus(repo):
399 def clearstatus(repo):
400 'Remove the status files'
400 'Remove the status files'
401 if os.path.exists(repo.join("rebasestate")):
401 if os.path.exists(repo.join("rebasestate")):
402 util.unlinkpath(repo.join("rebasestate"))
402 util.unlinkpath(repo.join("rebasestate"))
403
403
404 def restorestatus(repo):
404 def restorestatus(repo):
405 'Restore a previously stored status'
405 'Restore a previously stored status'
406 try:
406 try:
407 target = None
407 target = None
408 collapse = False
408 collapse = False
409 external = nullrev
409 external = nullrev
410 state = {}
410 state = {}
411 f = repo.opener("rebasestate")
411 f = repo.opener("rebasestate")
412 for i, l in enumerate(f.read().splitlines()):
412 for i, l in enumerate(f.read().splitlines()):
413 if i == 0:
413 if i == 0:
414 originalwd = repo[l].rev()
414 originalwd = repo[l].rev()
415 elif i == 1:
415 elif i == 1:
416 target = repo[l].rev()
416 target = repo[l].rev()
417 elif i == 2:
417 elif i == 2:
418 external = repo[l].rev()
418 external = repo[l].rev()
419 elif i == 3:
419 elif i == 3:
420 collapse = bool(int(l))
420 collapse = bool(int(l))
421 elif i == 4:
421 elif i == 4:
422 keep = bool(int(l))
422 keep = bool(int(l))
423 elif i == 5:
423 elif i == 5:
424 keepbranches = bool(int(l))
424 keepbranches = bool(int(l))
425 else:
425 else:
426 oldrev, newrev = l.split(':')
426 oldrev, newrev = l.split(':')
427 state[repo[oldrev].rev()] = repo[newrev].rev()
427 state[repo[oldrev].rev()] = repo[newrev].rev()
428 skipped = set()
428 skipped = set()
429 # recompute the set of skipped revs
429 # recompute the set of skipped revs
430 if not collapse:
430 if not collapse:
431 seen = set([target])
431 seen = set([target])
432 for old, new in sorted(state.items()):
432 for old, new in sorted(state.items()):
433 if new != nullrev and new in seen:
433 if new != nullrev and new in seen:
434 skipped.add(old)
434 skipped.add(old)
435 seen.add(new)
435 seen.add(new)
436 repo.ui.debug('computed skipped revs: %s\n' % skipped)
436 repo.ui.debug('computed skipped revs: %s\n' % skipped)
437 repo.ui.debug('rebase status resumed\n')
437 repo.ui.debug('rebase status resumed\n')
438 return (originalwd, target, state, skipped,
438 return (originalwd, target, state, skipped,
439 collapse, keep, keepbranches, external)
439 collapse, keep, keepbranches, external)
440 except IOError, err:
440 except IOError, err:
441 if err.errno != errno.ENOENT:
441 if err.errno != errno.ENOENT:
442 raise
442 raise
443 raise util.Abort(_('no rebase in progress'))
443 raise util.Abort(_('no rebase in progress'))
444
444
445 def abort(repo, originalwd, target, state):
445 def abort(repo, originalwd, target, state):
446 'Restore the repository to its original state'
446 'Restore the repository to its original state'
447 if set(repo.changelog.descendants(target)) - set(state.values()):
447 if set(repo.changelog.descendants(target)) - set(state.values()):
448 repo.ui.warn(_("warning: new changesets detected on target branch, "
448 repo.ui.warn(_("warning: new changesets detected on target branch, "
449 "can't abort\n"))
449 "can't abort\n"))
450 return -1
450 return -1
451 else:
451 else:
452 # Strip from the first rebased revision
452 # Strip from the first rebased revision
453 merge.update(repo, repo[originalwd].rev(), False, True, False)
453 merge.update(repo, repo[originalwd].rev(), False, True, False)
454 rebased = filter(lambda x: x > -1 and x != target, state.values())
454 rebased = filter(lambda x: x > -1 and x != target, state.values())
455 if rebased:
455 if rebased:
456 strippoint = min(rebased)
456 strippoint = min(rebased)
457 # no backup of rebased cset versions needed
457 # no backup of rebased cset versions needed
458 repair.strip(repo.ui, repo, repo[strippoint].node())
458 repair.strip(repo.ui, repo, repo[strippoint].node())
459 clearstatus(repo)
459 clearstatus(repo)
460 repo.ui.warn(_('rebase aborted\n'))
460 repo.ui.warn(_('rebase aborted\n'))
461 return 0
461 return 0
462
462
463 def buildstate(repo, dest, src, base, detach):
463 def buildstate(repo, dest, src, base, detach):
464 'Define which revisions are going to be rebased and where'
464 'Define which revisions are going to be rebased and where'
465 targetancestors = set()
465 targetancestors = set()
466 detachset = set()
466 detachset = set()
467
467
468 if not dest:
468 if not dest:
469 # Destination defaults to the latest revision in the current branch
469 # Destination defaults to the latest revision in the current branch
470 branch = repo[None].branch()
470 branch = repo[None].branch()
471 dest = repo[branch].rev()
471 dest = repo[branch].rev()
472 else:
472 else:
473 dest = repo[dest].rev()
473 dest = repo[dest].rev()
474
474
475 # This check isn't strictly necessary, since mq detects commits over an
475 # This check isn't strictly necessary, since mq detects commits over an
476 # applied patch. But it prevents messing up the working directory when
476 # applied patch. But it prevents messing up the working directory when
477 # a partially completed rebase is blocked by mq.
477 # a partially completed rebase is blocked by mq.
478 if 'qtip' in repo.tags() and (repo[dest].node() in
478 if 'qtip' in repo.tags() and (repo[dest].node() in
479 [s.node for s in repo.mq.applied]):
479 [s.node for s in repo.mq.applied]):
480 raise util.Abort(_('cannot rebase onto an applied mq patch'))
480 raise util.Abort(_('cannot rebase onto an applied mq patch'))
481
481
482 if src:
482 if src:
483 commonbase = repo[src].ancestor(repo[dest])
483 commonbase = repo[src].ancestor(repo[dest])
484 samebranch = repo[src].branch() == repo[dest].branch()
484 samebranch = repo[src].branch() == repo[dest].branch()
485 if commonbase == repo[src]:
485 if commonbase == repo[src]:
486 raise util.Abort(_('source is ancestor of destination'))
486 raise util.Abort(_('source is ancestor of destination'))
487 if samebranch and commonbase == repo[dest]:
487 if samebranch and commonbase == repo[dest]:
488 raise util.Abort(_('source is descendant of destination'))
488 raise util.Abort(_('source is descendant of destination'))
489 source = repo[src].rev()
489 source = repo[src].rev()
490 if detach:
490 if detach:
491 # We need to keep track of source's ancestors up to the common base
491 # We need to keep track of source's ancestors up to the common base
492 srcancestors = set(repo.changelog.ancestors(source))
492 srcancestors = set(repo.changelog.ancestors(source))
493 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
493 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
494 detachset = srcancestors - baseancestors
494 detachset = srcancestors - baseancestors
495 detachset.discard(commonbase.rev())
495 detachset.discard(commonbase.rev())
496 else:
496 else:
497 if base:
497 if base:
498 cwd = repo[base].rev()
498 cwd = repo[base].rev()
499 else:
499 else:
500 cwd = repo['.'].rev()
500 cwd = repo['.'].rev()
501
501
502 if cwd == dest:
502 if cwd == dest:
503 repo.ui.debug('source and destination are the same\n')
503 repo.ui.debug('source and destination are the same\n')
504 return None
504 return None
505
505
506 targetancestors = set(repo.changelog.ancestors(dest))
506 targetancestors = set(repo.changelog.ancestors(dest))
507 if cwd in targetancestors:
507 if cwd in targetancestors:
508 repo.ui.debug('source is ancestor of destination\n')
508 repo.ui.debug('source is ancestor of destination\n')
509 return None
509 return None
510
510
511 cwdancestors = set(repo.changelog.ancestors(cwd))
511 cwdancestors = set(repo.changelog.ancestors(cwd))
512 if dest in cwdancestors:
512 if dest in cwdancestors:
513 repo.ui.debug('source is descendant of destination\n')
513 repo.ui.debug('source is descendant of destination\n')
514 return None
514 return None
515
515
516 cwdancestors.add(cwd)
516 cwdancestors.add(cwd)
517 rebasingbranch = cwdancestors - targetancestors
517 rebasingbranch = cwdancestors - targetancestors
518 source = min(rebasingbranch)
518 source = min(rebasingbranch)
519
519
520 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
520 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
521 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
521 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
522 state.update(dict.fromkeys(detachset, nullmerge))
522 state.update(dict.fromkeys(detachset, nullmerge))
523 state[source] = nullrev
523 state[source] = nullrev
524 return repo['.'].rev(), repo[dest].rev(), state
524 return repo['.'].rev(), repo[dest].rev(), state
525
525
526 def pullrebase(orig, ui, repo, *args, **opts):
526 def pullrebase(orig, ui, repo, *args, **opts):
527 'Call rebase after pull if the latter has been invoked with --rebase'
527 'Call rebase after pull if the latter has been invoked with --rebase'
528 if opts.get('rebase'):
528 if opts.get('rebase'):
529 if opts.get('update'):
529 if opts.get('update'):
530 del opts['update']
530 del opts['update']
531 ui.debug('--update and --rebase are not compatible, ignoring '
531 ui.debug('--update and --rebase are not compatible, ignoring '
532 'the update flag\n')
532 'the update flag\n')
533
533
534 cmdutil.bail_if_changed(repo)
534 cmdutil.bail_if_changed(repo)
535 revsprepull = len(repo)
535 revsprepull = len(repo)
536 origpostincoming = commands.postincoming
536 origpostincoming = commands.postincoming
537 def _dummy(*args, **kwargs):
537 def _dummy(*args, **kwargs):
538 pass
538 pass
539 commands.postincoming = _dummy
539 commands.postincoming = _dummy
540 try:
540 try:
541 orig(ui, repo, *args, **opts)
541 orig(ui, repo, *args, **opts)
542 finally:
542 finally:
543 commands.postincoming = origpostincoming
543 commands.postincoming = origpostincoming
544 revspostpull = len(repo)
544 revspostpull = len(repo)
545 if revspostpull > revsprepull:
545 if revspostpull > revsprepull:
546 rebase(ui, repo, **opts)
546 rebase(ui, repo, **opts)
547 branch = repo[None].branch()
547 branch = repo[None].branch()
548 dest = repo[branch].rev()
548 dest = repo[branch].rev()
549 if dest != repo['.'].rev():
549 if dest != repo['.'].rev():
550 # there was nothing to rebase we force an update
550 # there was nothing to rebase we force an update
551 hg.update(repo, dest)
551 hg.update(repo, dest)
552 else:
552 else:
553 orig(ui, repo, *args, **opts)
553 orig(ui, repo, *args, **opts)
554
554
555 def uisetup(ui):
555 def uisetup(ui):
556 'Replace pull with a decorator to provide --rebase option'
556 'Replace pull with a decorator to provide --rebase option'
557 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
557 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
558 entry[1].append(('', 'rebase', None,
558 entry[1].append(('', 'rebase', None,
559 _("rebase working directory to branch head"))
559 _("rebase working directory to branch head"))
560 )
560 )
561
561
562 cmdtable = {
562 cmdtable = {
563 "rebase":
563 "rebase":
564 (rebase,
564 (rebase,
565 [
565 [
566 ('s', 'source', '',
566 ('s', 'source', '',
567 _('rebase from the specified changeset'), _('REV')),
567 _('rebase from the specified changeset'), _('REV')),
568 ('b', 'base', '',
568 ('b', 'base', '',
569 _('rebase from the base of the specified changeset '
569 _('rebase from the base of the specified changeset '
570 '(up to greatest common ancestor of base and dest)'),
570 '(up to greatest common ancestor of base and dest)'),
571 _('REV')),
571 _('REV')),
572 ('d', 'dest', '',
572 ('d', 'dest', '',
573 _('rebase onto the specified changeset'), _('REV')),
573 _('rebase onto the specified changeset'), _('REV')),
574 ('', 'collapse', False, _('collapse the rebased changesets')),
574 ('', 'collapse', False, _('collapse the rebased changesets')),
575 ('m', 'message', '',
575 ('m', 'message', '',
576 _('use text as collapse commit message'), _('TEXT')),
576 _('use text as collapse commit message'), _('TEXT')),
577 ('l', 'logfile', '',
577 ('l', 'logfile', '',
578 _('read collapse commit message from file'), _('FILE')),
578 _('read collapse commit message from file'), _('FILE')),
579 ('', 'keep', False, _('keep original changesets')),
579 ('', 'keep', False, _('keep original changesets')),
580 ('', 'keepbranches', False, _('keep original branch names')),
580 ('', 'keepbranches', False, _('keep original branch names')),
581 ('', 'detach', False, _('force detaching of source from its original '
581 ('', 'detach', False, _('force detaching of source from its original '
582 'branch')),
582 'branch')),
583 ('t', 'tool', '', _('specify merge tool')),
583 ('t', 'tool', '', _('specify merge tool')),
584 ('c', 'continue', False, _('continue an interrupted rebase')),
584 ('c', 'continue', False, _('continue an interrupted rebase')),
585 ('a', 'abort', False, _('abort an interrupted rebase'))] +
585 ('a', 'abort', False, _('abort an interrupted rebase'))] +
586 templateopts,
586 templateopts,
587 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
587 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
588 'hg rebase {-a|-c}'))
588 'hg rebase {-a|-c}'))
589 }
589 }
@@ -1,554 +1,554 b''
1 # record.py
1 # record.py
2 #
2 #
3 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
3 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''commands to interactively select changes for commit/qrefresh'''
8 '''commands to interactively select changes for commit/qrefresh'''
9
9
10 from mercurial.i18n import gettext, _
10 from mercurial.i18n import gettext, _
11 from mercurial import cmdutil, commands, extensions, hg, mdiff, patch
11 from mercurial import cmdutil, commands, extensions, hg, mdiff, patch
12 from mercurial import util
12 from mercurial import util
13 import copy, cStringIO, errno, os, re, shutil, tempfile
13 import copy, cStringIO, errno, os, re, shutil, tempfile
14
14
15 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
15 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
16
16
17 def scanpatch(fp):
17 def scanpatch(fp):
18 """like patch.iterhunks, but yield different events
18 """like patch.iterhunks, but yield different events
19
19
20 - ('file', [header_lines + fromfile + tofile])
20 - ('file', [header_lines + fromfile + tofile])
21 - ('context', [context_lines])
21 - ('context', [context_lines])
22 - ('hunk', [hunk_lines])
22 - ('hunk', [hunk_lines])
23 - ('range', (-start,len, +start,len, diffp))
23 - ('range', (-start,len, +start,len, diffp))
24 """
24 """
25 lr = patch.linereader(fp)
25 lr = patch.linereader(fp)
26
26
27 def scanwhile(first, p):
27 def scanwhile(first, p):
28 """scan lr while predicate holds"""
28 """scan lr while predicate holds"""
29 lines = [first]
29 lines = [first]
30 while True:
30 while True:
31 line = lr.readline()
31 line = lr.readline()
32 if not line:
32 if not line:
33 break
33 break
34 if p(line):
34 if p(line):
35 lines.append(line)
35 lines.append(line)
36 else:
36 else:
37 lr.push(line)
37 lr.push(line)
38 break
38 break
39 return lines
39 return lines
40
40
41 while True:
41 while True:
42 line = lr.readline()
42 line = lr.readline()
43 if not line:
43 if not line:
44 break
44 break
45 if line.startswith('diff --git a/') or line.startswith('diff -r '):
45 if line.startswith('diff --git a/') or line.startswith('diff -r '):
46 def notheader(line):
46 def notheader(line):
47 s = line.split(None, 1)
47 s = line.split(None, 1)
48 return not s or s[0] not in ('---', 'diff')
48 return not s or s[0] not in ('---', 'diff')
49 header = scanwhile(line, notheader)
49 header = scanwhile(line, notheader)
50 fromfile = lr.readline()
50 fromfile = lr.readline()
51 if fromfile.startswith('---'):
51 if fromfile.startswith('---'):
52 tofile = lr.readline()
52 tofile = lr.readline()
53 header += [fromfile, tofile]
53 header += [fromfile, tofile]
54 else:
54 else:
55 lr.push(fromfile)
55 lr.push(fromfile)
56 yield 'file', header
56 yield 'file', header
57 elif line[0] == ' ':
57 elif line[0] == ' ':
58 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
58 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
59 elif line[0] in '-+':
59 elif line[0] in '-+':
60 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
60 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
61 else:
61 else:
62 m = lines_re.match(line)
62 m = lines_re.match(line)
63 if m:
63 if m:
64 yield 'range', m.groups()
64 yield 'range', m.groups()
65 else:
65 else:
66 raise patch.PatchError('unknown patch content: %r' % line)
66 raise patch.PatchError('unknown patch content: %r' % line)
67
67
68 class header(object):
68 class header(object):
69 """patch header
69 """patch header
70
70
71 XXX shoudn't we move this to mercurial/patch.py ?
71 XXX shoudn't we move this to mercurial/patch.py ?
72 """
72 """
73 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
73 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
74 diff_re = re.compile('diff -r .* (.*)$')
74 diff_re = re.compile('diff -r .* (.*)$')
75 allhunks_re = re.compile('(?:index|new file|deleted file) ')
75 allhunks_re = re.compile('(?:index|new file|deleted file) ')
76 pretty_re = re.compile('(?:new file|deleted file) ')
76 pretty_re = re.compile('(?:new file|deleted file) ')
77 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
77 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
78
78
79 def __init__(self, header):
79 def __init__(self, header):
80 self.header = header
80 self.header = header
81 self.hunks = []
81 self.hunks = []
82
82
83 def binary(self):
83 def binary(self):
84 return util.any(h.startswith('index ') for h in self.header)
84 return util.any(h.startswith('index ') for h in self.header)
85
85
86 def pretty(self, fp):
86 def pretty(self, fp):
87 for h in self.header:
87 for h in self.header:
88 if h.startswith('index '):
88 if h.startswith('index '):
89 fp.write(_('this modifies a binary file (all or nothing)\n'))
89 fp.write(_('this modifies a binary file (all or nothing)\n'))
90 break
90 break
91 if self.pretty_re.match(h):
91 if self.pretty_re.match(h):
92 fp.write(h)
92 fp.write(h)
93 if self.binary():
93 if self.binary():
94 fp.write(_('this is a binary file\n'))
94 fp.write(_('this is a binary file\n'))
95 break
95 break
96 if h.startswith('---'):
96 if h.startswith('---'):
97 fp.write(_('%d hunks, %d lines changed\n') %
97 fp.write(_('%d hunks, %d lines changed\n') %
98 (len(self.hunks),
98 (len(self.hunks),
99 sum([max(h.added, h.removed) for h in self.hunks])))
99 sum([max(h.added, h.removed) for h in self.hunks])))
100 break
100 break
101 fp.write(h)
101 fp.write(h)
102
102
103 def write(self, fp):
103 def write(self, fp):
104 fp.write(''.join(self.header))
104 fp.write(''.join(self.header))
105
105
106 def allhunks(self):
106 def allhunks(self):
107 return util.any(self.allhunks_re.match(h) for h in self.header)
107 return util.any(self.allhunks_re.match(h) for h in self.header)
108
108
109 def files(self):
109 def files(self):
110 match = self.diffgit_re.match(self.header[0])
110 match = self.diffgit_re.match(self.header[0])
111 if match:
111 if match:
112 fromfile, tofile = match.groups()
112 fromfile, tofile = match.groups()
113 if fromfile == tofile:
113 if fromfile == tofile:
114 return [fromfile]
114 return [fromfile]
115 return [fromfile, tofile]
115 return [fromfile, tofile]
116 else:
116 else:
117 return self.diff_re.match(self.header[0]).groups()
117 return self.diff_re.match(self.header[0]).groups()
118
118
119 def filename(self):
119 def filename(self):
120 return self.files()[-1]
120 return self.files()[-1]
121
121
122 def __repr__(self):
122 def __repr__(self):
123 return '<header %s>' % (' '.join(map(repr, self.files())))
123 return '<header %s>' % (' '.join(map(repr, self.files())))
124
124
125 def special(self):
125 def special(self):
126 return util.any(self.special_re.match(h) for h in self.header)
126 return util.any(self.special_re.match(h) for h in self.header)
127
127
128 def countchanges(hunk):
128 def countchanges(hunk):
129 """hunk -> (n+,n-)"""
129 """hunk -> (n+,n-)"""
130 add = len([h for h in hunk if h[0] == '+'])
130 add = len([h for h in hunk if h[0] == '+'])
131 rem = len([h for h in hunk if h[0] == '-'])
131 rem = len([h for h in hunk if h[0] == '-'])
132 return add, rem
132 return add, rem
133
133
134 class hunk(object):
134 class hunk(object):
135 """patch hunk
135 """patch hunk
136
136
137 XXX shouldn't we merge this with patch.hunk ?
137 XXX shouldn't we merge this with patch.hunk ?
138 """
138 """
139 maxcontext = 3
139 maxcontext = 3
140
140
141 def __init__(self, header, fromline, toline, proc, before, hunk, after):
141 def __init__(self, header, fromline, toline, proc, before, hunk, after):
142 def trimcontext(number, lines):
142 def trimcontext(number, lines):
143 delta = len(lines) - self.maxcontext
143 delta = len(lines) - self.maxcontext
144 if False and delta > 0:
144 if False and delta > 0:
145 return number + delta, lines[:self.maxcontext]
145 return number + delta, lines[:self.maxcontext]
146 return number, lines
146 return number, lines
147
147
148 self.header = header
148 self.header = header
149 self.fromline, self.before = trimcontext(fromline, before)
149 self.fromline, self.before = trimcontext(fromline, before)
150 self.toline, self.after = trimcontext(toline, after)
150 self.toline, self.after = trimcontext(toline, after)
151 self.proc = proc
151 self.proc = proc
152 self.hunk = hunk
152 self.hunk = hunk
153 self.added, self.removed = countchanges(self.hunk)
153 self.added, self.removed = countchanges(self.hunk)
154
154
155 def write(self, fp):
155 def write(self, fp):
156 delta = len(self.before) + len(self.after)
156 delta = len(self.before) + len(self.after)
157 if self.after and self.after[-1] == '\\ No newline at end of file\n':
157 if self.after and self.after[-1] == '\\ No newline at end of file\n':
158 delta -= 1
158 delta -= 1
159 fromlen = delta + self.removed
159 fromlen = delta + self.removed
160 tolen = delta + self.added
160 tolen = delta + self.added
161 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
161 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
162 (self.fromline, fromlen, self.toline, tolen,
162 (self.fromline, fromlen, self.toline, tolen,
163 self.proc and (' ' + self.proc)))
163 self.proc and (' ' + self.proc)))
164 fp.write(''.join(self.before + self.hunk + self.after))
164 fp.write(''.join(self.before + self.hunk + self.after))
165
165
166 pretty = write
166 pretty = write
167
167
168 def filename(self):
168 def filename(self):
169 return self.header.filename()
169 return self.header.filename()
170
170
171 def __repr__(self):
171 def __repr__(self):
172 return '<hunk %r@%d>' % (self.filename(), self.fromline)
172 return '<hunk %r@%d>' % (self.filename(), self.fromline)
173
173
174 def parsepatch(fp):
174 def parsepatch(fp):
175 """patch -> [] of headers -> [] of hunks """
175 """patch -> [] of headers -> [] of hunks """
176 class parser(object):
176 class parser(object):
177 """patch parsing state machine"""
177 """patch parsing state machine"""
178 def __init__(self):
178 def __init__(self):
179 self.fromline = 0
179 self.fromline = 0
180 self.toline = 0
180 self.toline = 0
181 self.proc = ''
181 self.proc = ''
182 self.header = None
182 self.header = None
183 self.context = []
183 self.context = []
184 self.before = []
184 self.before = []
185 self.hunk = []
185 self.hunk = []
186 self.headers = []
186 self.headers = []
187
187
188 def addrange(self, limits):
188 def addrange(self, limits):
189 fromstart, fromend, tostart, toend, proc = limits
189 fromstart, fromend, tostart, toend, proc = limits
190 self.fromline = int(fromstart)
190 self.fromline = int(fromstart)
191 self.toline = int(tostart)
191 self.toline = int(tostart)
192 self.proc = proc
192 self.proc = proc
193
193
194 def addcontext(self, context):
194 def addcontext(self, context):
195 if self.hunk:
195 if self.hunk:
196 h = hunk(self.header, self.fromline, self.toline, self.proc,
196 h = hunk(self.header, self.fromline, self.toline, self.proc,
197 self.before, self.hunk, context)
197 self.before, self.hunk, context)
198 self.header.hunks.append(h)
198 self.header.hunks.append(h)
199 self.fromline += len(self.before) + h.removed
199 self.fromline += len(self.before) + h.removed
200 self.toline += len(self.before) + h.added
200 self.toline += len(self.before) + h.added
201 self.before = []
201 self.before = []
202 self.hunk = []
202 self.hunk = []
203 self.proc = ''
203 self.proc = ''
204 self.context = context
204 self.context = context
205
205
206 def addhunk(self, hunk):
206 def addhunk(self, hunk):
207 if self.context:
207 if self.context:
208 self.before = self.context
208 self.before = self.context
209 self.context = []
209 self.context = []
210 self.hunk = hunk
210 self.hunk = hunk
211
211
212 def newfile(self, hdr):
212 def newfile(self, hdr):
213 self.addcontext([])
213 self.addcontext([])
214 h = header(hdr)
214 h = header(hdr)
215 self.headers.append(h)
215 self.headers.append(h)
216 self.header = h
216 self.header = h
217
217
218 def finished(self):
218 def finished(self):
219 self.addcontext([])
219 self.addcontext([])
220 return self.headers
220 return self.headers
221
221
222 transitions = {
222 transitions = {
223 'file': {'context': addcontext,
223 'file': {'context': addcontext,
224 'file': newfile,
224 'file': newfile,
225 'hunk': addhunk,
225 'hunk': addhunk,
226 'range': addrange},
226 'range': addrange},
227 'context': {'file': newfile,
227 'context': {'file': newfile,
228 'hunk': addhunk,
228 'hunk': addhunk,
229 'range': addrange},
229 'range': addrange},
230 'hunk': {'context': addcontext,
230 'hunk': {'context': addcontext,
231 'file': newfile,
231 'file': newfile,
232 'range': addrange},
232 'range': addrange},
233 'range': {'context': addcontext,
233 'range': {'context': addcontext,
234 'hunk': addhunk},
234 'hunk': addhunk},
235 }
235 }
236
236
237 p = parser()
237 p = parser()
238
238
239 state = 'context'
239 state = 'context'
240 for newstate, data in scanpatch(fp):
240 for newstate, data in scanpatch(fp):
241 try:
241 try:
242 p.transitions[state][newstate](p, data)
242 p.transitions[state][newstate](p, data)
243 except KeyError:
243 except KeyError:
244 raise patch.PatchError('unhandled transition: %s -> %s' %
244 raise patch.PatchError('unhandled transition: %s -> %s' %
245 (state, newstate))
245 (state, newstate))
246 state = newstate
246 state = newstate
247 return p.finished()
247 return p.finished()
248
248
249 def filterpatch(ui, headers):
249 def filterpatch(ui, headers):
250 """Interactively filter patch chunks into applied-only chunks"""
250 """Interactively filter patch chunks into applied-only chunks"""
251
251
252 def prompt(skipfile, skipall, query):
252 def prompt(skipfile, skipall, query):
253 """prompt query, and process base inputs
253 """prompt query, and process base inputs
254
254
255 - y/n for the rest of file
255 - y/n for the rest of file
256 - y/n for the rest
256 - y/n for the rest
257 - ? (help)
257 - ? (help)
258 - q (quit)
258 - q (quit)
259
259
260 Return True/False and possibly updated skipfile and skipall.
260 Return True/False and possibly updated skipfile and skipall.
261 """
261 """
262 if skipall is not None:
262 if skipall is not None:
263 return skipall, skipfile, skipall
263 return skipall, skipfile, skipall
264 if skipfile is not None:
264 if skipfile is not None:
265 return skipfile, skipfile, skipall
265 return skipfile, skipfile, skipall
266 while True:
266 while True:
267 resps = _('[Ynsfdaq?]')
267 resps = _('[Ynsfdaq?]')
268 choices = (_('&Yes, record this change'),
268 choices = (_('&Yes, record this change'),
269 _('&No, skip this change'),
269 _('&No, skip this change'),
270 _('&Skip remaining changes to this file'),
270 _('&Skip remaining changes to this file'),
271 _('Record remaining changes to this &file'),
271 _('Record remaining changes to this &file'),
272 _('&Done, skip remaining changes and files'),
272 _('&Done, skip remaining changes and files'),
273 _('Record &all changes to all remaining files'),
273 _('Record &all changes to all remaining files'),
274 _('&Quit, recording no changes'),
274 _('&Quit, recording no changes'),
275 _('&?'))
275 _('&?'))
276 r = ui.promptchoice("%s %s" % (query, resps), choices)
276 r = ui.promptchoice("%s %s" % (query, resps), choices)
277 ui.write("\n")
277 ui.write("\n")
278 if r == 7: # ?
278 if r == 7: # ?
279 doc = gettext(record.__doc__)
279 doc = gettext(record.__doc__)
280 c = doc.find('::') + 2
280 c = doc.find('::') + 2
281 for l in doc[c:].splitlines():
281 for l in doc[c:].splitlines():
282 if l.startswith(' '):
282 if l.startswith(' '):
283 ui.write(l.strip(), '\n')
283 ui.write(l.strip(), '\n')
284 continue
284 continue
285 elif r == 0: # yes
285 elif r == 0: # yes
286 ret = True
286 ret = True
287 elif r == 1: # no
287 elif r == 1: # no
288 ret = False
288 ret = False
289 elif r == 2: # Skip
289 elif r == 2: # Skip
290 ret = skipfile = False
290 ret = skipfile = False
291 elif r == 3: # file (Record remaining)
291 elif r == 3: # file (Record remaining)
292 ret = skipfile = True
292 ret = skipfile = True
293 elif r == 4: # done, skip remaining
293 elif r == 4: # done, skip remaining
294 ret = skipall = False
294 ret = skipall = False
295 elif r == 5: # all
295 elif r == 5: # all
296 ret = skipall = True
296 ret = skipall = True
297 elif r == 6: # quit
297 elif r == 6: # quit
298 raise util.Abort(_('user quit'))
298 raise util.Abort(_('user quit'))
299 return ret, skipfile, skipall
299 return ret, skipfile, skipall
300
300
301 seen = set()
301 seen = set()
302 applied = {} # 'filename' -> [] of chunks
302 applied = {} # 'filename' -> [] of chunks
303 skipfile, skipall = None, None
303 skipfile, skipall = None, None
304 pos, total = 1, sum(len(h.hunks) for h in headers)
304 pos, total = 1, sum(len(h.hunks) for h in headers)
305 for h in headers:
305 for h in headers:
306 pos += len(h.hunks)
306 pos += len(h.hunks)
307 skipfile = None
307 skipfile = None
308 fixoffset = 0
308 fixoffset = 0
309 hdr = ''.join(h.header)
309 hdr = ''.join(h.header)
310 if hdr in seen:
310 if hdr in seen:
311 continue
311 continue
312 seen.add(hdr)
312 seen.add(hdr)
313 if skipall is None:
313 if skipall is None:
314 h.pretty(ui)
314 h.pretty(ui)
315 msg = (_('examine changes to %s?') %
315 msg = (_('examine changes to %s?') %
316 _(' and ').join(map(repr, h.files())))
316 _(' and ').join(map(repr, h.files())))
317 r, skipfile, skipall = prompt(skipfile, skipall, msg)
317 r, skipfile, skipall = prompt(skipfile, skipall, msg)
318 if not r:
318 if not r:
319 continue
319 continue
320 applied[h.filename()] = [h]
320 applied[h.filename()] = [h]
321 if h.allhunks():
321 if h.allhunks():
322 applied[h.filename()] += h.hunks
322 applied[h.filename()] += h.hunks
323 continue
323 continue
324 for i, chunk in enumerate(h.hunks):
324 for i, chunk in enumerate(h.hunks):
325 if skipfile is None and skipall is None:
325 if skipfile is None and skipall is None:
326 chunk.pretty(ui)
326 chunk.pretty(ui)
327 if total == 1:
327 if total == 1:
328 msg = _('record this change to %r?') % chunk.filename()
328 msg = _('record this change to %r?') % chunk.filename()
329 else:
329 else:
330 idx = pos - len(h.hunks) + i
330 idx = pos - len(h.hunks) + i
331 msg = _('record change %d/%d to %r?') % (idx, total,
331 msg = _('record change %d/%d to %r?') % (idx, total,
332 chunk.filename())
332 chunk.filename())
333 r, skipfile, skipall = prompt(skipfile, skipall, msg)
333 r, skipfile, skipall = prompt(skipfile, skipall, msg)
334 if r:
334 if r:
335 if fixoffset:
335 if fixoffset:
336 chunk = copy.copy(chunk)
336 chunk = copy.copy(chunk)
337 chunk.toline += fixoffset
337 chunk.toline += fixoffset
338 applied[chunk.filename()].append(chunk)
338 applied[chunk.filename()].append(chunk)
339 else:
339 else:
340 fixoffset += chunk.removed - chunk.added
340 fixoffset += chunk.removed - chunk.added
341 return sum([h for h in applied.itervalues()
341 return sum([h for h in applied.itervalues()
342 if h[0].special() or len(h) > 1], [])
342 if h[0].special() or len(h) > 1], [])
343
343
344 def record(ui, repo, *pats, **opts):
344 def record(ui, repo, *pats, **opts):
345 '''interactively select changes to commit
345 '''interactively select changes to commit
346
346
347 If a list of files is omitted, all changes reported by :hg:`status`
347 If a list of files is omitted, all changes reported by :hg:`status`
348 will be candidates for recording.
348 will be candidates for recording.
349
349
350 See :hg:`help dates` for a list of formats valid for -d/--date.
350 See :hg:`help dates` for a list of formats valid for -d/--date.
351
351
352 You will be prompted for whether to record changes to each
352 You will be prompted for whether to record changes to each
353 modified file, and for files with multiple changes, for each
353 modified file, and for files with multiple changes, for each
354 change to use. For each query, the following responses are
354 change to use. For each query, the following responses are
355 possible::
355 possible::
356
356
357 y - record this change
357 y - record this change
358 n - skip this change
358 n - skip this change
359
359
360 s - skip remaining changes to this file
360 s - skip remaining changes to this file
361 f - record remaining changes to this file
361 f - record remaining changes to this file
362
362
363 d - done, skip remaining changes and files
363 d - done, skip remaining changes and files
364 a - record all changes to all remaining files
364 a - record all changes to all remaining files
365 q - quit, recording no changes
365 q - quit, recording no changes
366
366
367 ? - display help
367 ? - display help
368
368
369 This command is not available when committing a merge.'''
369 This command is not available when committing a merge.'''
370
370
371 dorecord(ui, repo, commands.commit, *pats, **opts)
371 dorecord(ui, repo, commands.commit, *pats, **opts)
372
372
373
373
374 def qrecord(ui, repo, patch, *pats, **opts):
374 def qrecord(ui, repo, patch, *pats, **opts):
375 '''interactively record a new patch
375 '''interactively record a new patch
376
376
377 See :hg:`help qnew` & :hg:`help record` for more information and
377 See :hg:`help qnew` & :hg:`help record` for more information and
378 usage.
378 usage.
379 '''
379 '''
380
380
381 try:
381 try:
382 mq = extensions.find('mq')
382 mq = extensions.find('mq')
383 except KeyError:
383 except KeyError:
384 raise util.Abort(_("'mq' extension not loaded"))
384 raise util.Abort(_("'mq' extension not loaded"))
385
385
386 def committomq(ui, repo, *pats, **opts):
386 def committomq(ui, repo, *pats, **opts):
387 mq.new(ui, repo, patch, *pats, **opts)
387 mq.new(ui, repo, patch, *pats, **opts)
388
388
389 dorecord(ui, repo, committomq, *pats, **opts)
389 dorecord(ui, repo, committomq, *pats, **opts)
390
390
391
391
392 def dorecord(ui, repo, commitfunc, *pats, **opts):
392 def dorecord(ui, repo, commitfunc, *pats, **opts):
393 if not ui.interactive():
393 if not ui.interactive():
394 raise util.Abort(_('running non-interactively, use commit instead'))
394 raise util.Abort(_('running non-interactively, use commit instead'))
395
395
396 def recordfunc(ui, repo, message, match, opts):
396 def recordfunc(ui, repo, message, match, opts):
397 """This is generic record driver.
397 """This is generic record driver.
398
398
399 Its job is to interactively filter local changes, and
399 Its job is to interactively filter local changes, and
400 accordingly prepare working directory into a state in which the
400 accordingly prepare working directory into a state in which the
401 job can be delegated to a non-interactive commit command such as
401 job can be delegated to a non-interactive commit command such as
402 'commit' or 'qrefresh'.
402 'commit' or 'qrefresh'.
403
403
404 After the actual job is done by non-interactive command, the
404 After the actual job is done by non-interactive command, the
405 working directory is restored to its original state.
405 working directory is restored to its original state.
406
406
407 In the end we'll record interesting changes, and everything else
407 In the end we'll record interesting changes, and everything else
408 will be left in place, so the user can continue working.
408 will be left in place, so the user can continue working.
409 """
409 """
410
410
411 merge = len(repo[None].parents()) > 1
411 merge = len(repo[None].parents()) > 1
412 if merge:
412 if merge:
413 raise util.Abort(_('cannot partially commit a merge '
413 raise util.Abort(_('cannot partially commit a merge '
414 '(use "hg commit" instead)'))
414 '(use "hg commit" instead)'))
415
415
416 changes = repo.status(match=match)[:3]
416 changes = repo.status(match=match)[:3]
417 diffopts = mdiff.diffopts(git=True, nodates=True)
417 diffopts = mdiff.diffopts(git=True, nodates=True)
418 chunks = patch.diff(repo, changes=changes, opts=diffopts)
418 chunks = patch.diff(repo, changes=changes, opts=diffopts)
419 fp = cStringIO.StringIO()
419 fp = cStringIO.StringIO()
420 fp.write(''.join(chunks))
420 fp.write(''.join(chunks))
421 fp.seek(0)
421 fp.seek(0)
422
422
423 # 1. filter patch, so we have intending-to apply subset of it
423 # 1. filter patch, so we have intending-to apply subset of it
424 chunks = filterpatch(ui, parsepatch(fp))
424 chunks = filterpatch(ui, parsepatch(fp))
425 del fp
425 del fp
426
426
427 contenders = set()
427 contenders = set()
428 for h in chunks:
428 for h in chunks:
429 try:
429 try:
430 contenders.update(set(h.files()))
430 contenders.update(set(h.files()))
431 except AttributeError:
431 except AttributeError:
432 pass
432 pass
433
433
434 changed = changes[0] + changes[1] + changes[2]
434 changed = changes[0] + changes[1] + changes[2]
435 newfiles = [f for f in changed if f in contenders]
435 newfiles = [f for f in changed if f in contenders]
436 if not newfiles:
436 if not newfiles:
437 ui.status(_('no changes to record\n'))
437 ui.status(_('no changes to record\n'))
438 return 0
438 return 0
439
439
440 modified = set(changes[0])
440 modified = set(changes[0])
441
441
442 # 2. backup changed files, so we can restore them in the end
442 # 2. backup changed files, so we can restore them in the end
443 backups = {}
443 backups = {}
444 backupdir = repo.join('record-backups')
444 backupdir = repo.join('record-backups')
445 try:
445 try:
446 os.mkdir(backupdir)
446 os.mkdir(backupdir)
447 except OSError, err:
447 except OSError, err:
448 if err.errno != errno.EEXIST:
448 if err.errno != errno.EEXIST:
449 raise
449 raise
450 try:
450 try:
451 # backup continues
451 # backup continues
452 for f in newfiles:
452 for f in newfiles:
453 if f not in modified:
453 if f not in modified:
454 continue
454 continue
455 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
455 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
456 dir=backupdir)
456 dir=backupdir)
457 os.close(fd)
457 os.close(fd)
458 ui.debug('backup %r as %r\n' % (f, tmpname))
458 ui.debug('backup %r as %r\n' % (f, tmpname))
459 util.copyfile(repo.wjoin(f), tmpname)
459 util.copyfile(repo.wjoin(f), tmpname)
460 shutil.copystat(repo.wjoin(f), tmpname)
460 shutil.copystat(repo.wjoin(f), tmpname)
461 backups[f] = tmpname
461 backups[f] = tmpname
462
462
463 fp = cStringIO.StringIO()
463 fp = cStringIO.StringIO()
464 for c in chunks:
464 for c in chunks:
465 if c.filename() in backups:
465 if c.filename() in backups:
466 c.write(fp)
466 c.write(fp)
467 dopatch = fp.tell()
467 dopatch = fp.tell()
468 fp.seek(0)
468 fp.seek(0)
469
469
470 # 3a. apply filtered patch to clean repo (clean)
470 # 3a. apply filtered patch to clean repo (clean)
471 if backups:
471 if backups:
472 hg.revert(repo, repo.dirstate.parents()[0],
472 hg.revert(repo, repo.dirstate.p1(),
473 lambda key: key in backups)
473 lambda key: key in backups)
474
474
475 # 3b. (apply)
475 # 3b. (apply)
476 if dopatch:
476 if dopatch:
477 try:
477 try:
478 ui.debug('applying patch\n')
478 ui.debug('applying patch\n')
479 ui.debug(fp.getvalue())
479 ui.debug(fp.getvalue())
480 pfiles = {}
480 pfiles = {}
481 patch.internalpatch(fp, ui, 1, repo.root, files=pfiles,
481 patch.internalpatch(fp, ui, 1, repo.root, files=pfiles,
482 eolmode=None)
482 eolmode=None)
483 cmdutil.updatedir(ui, repo, pfiles)
483 cmdutil.updatedir(ui, repo, pfiles)
484 except patch.PatchError, err:
484 except patch.PatchError, err:
485 raise util.Abort(str(err))
485 raise util.Abort(str(err))
486 del fp
486 del fp
487
487
488 # 4. We prepared working directory according to filtered
488 # 4. We prepared working directory according to filtered
489 # patch. Now is the time to delegate the job to
489 # patch. Now is the time to delegate the job to
490 # commit/qrefresh or the like!
490 # commit/qrefresh or the like!
491
491
492 # it is important to first chdir to repo root -- we'll call
492 # it is important to first chdir to repo root -- we'll call
493 # a highlevel command with list of pathnames relative to
493 # a highlevel command with list of pathnames relative to
494 # repo root
494 # repo root
495 cwd = os.getcwd()
495 cwd = os.getcwd()
496 os.chdir(repo.root)
496 os.chdir(repo.root)
497 try:
497 try:
498 commitfunc(ui, repo, *newfiles, **opts)
498 commitfunc(ui, repo, *newfiles, **opts)
499 finally:
499 finally:
500 os.chdir(cwd)
500 os.chdir(cwd)
501
501
502 return 0
502 return 0
503 finally:
503 finally:
504 # 5. finally restore backed-up files
504 # 5. finally restore backed-up files
505 try:
505 try:
506 for realname, tmpname in backups.iteritems():
506 for realname, tmpname in backups.iteritems():
507 ui.debug('restoring %r to %r\n' % (tmpname, realname))
507 ui.debug('restoring %r to %r\n' % (tmpname, realname))
508 util.copyfile(tmpname, repo.wjoin(realname))
508 util.copyfile(tmpname, repo.wjoin(realname))
509 # Our calls to copystat() here and above are a
509 # Our calls to copystat() here and above are a
510 # hack to trick any editors that have f open that
510 # hack to trick any editors that have f open that
511 # we haven't modified them.
511 # we haven't modified them.
512 #
512 #
513 # Also note that this racy as an editor could
513 # Also note that this racy as an editor could
514 # notice the file's mtime before we've finished
514 # notice the file's mtime before we've finished
515 # writing it.
515 # writing it.
516 shutil.copystat(tmpname, repo.wjoin(realname))
516 shutil.copystat(tmpname, repo.wjoin(realname))
517 os.unlink(tmpname)
517 os.unlink(tmpname)
518 os.rmdir(backupdir)
518 os.rmdir(backupdir)
519 except OSError:
519 except OSError:
520 pass
520 pass
521
521
522 # wrap ui.write so diff output can be labeled/colorized
522 # wrap ui.write so diff output can be labeled/colorized
523 def wrapwrite(orig, *args, **kw):
523 def wrapwrite(orig, *args, **kw):
524 label = kw.pop('label', '')
524 label = kw.pop('label', '')
525 for chunk, l in patch.difflabel(lambda: args):
525 for chunk, l in patch.difflabel(lambda: args):
526 orig(chunk, label=label + l)
526 orig(chunk, label=label + l)
527 oldwrite = ui.write
527 oldwrite = ui.write
528 extensions.wrapfunction(ui, 'write', wrapwrite)
528 extensions.wrapfunction(ui, 'write', wrapwrite)
529 try:
529 try:
530 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
530 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
531 finally:
531 finally:
532 ui.write = oldwrite
532 ui.write = oldwrite
533
533
534 cmdtable = {
534 cmdtable = {
535 "record":
535 "record":
536 (record, commands.table['^commit|ci'][1], # same options as commit
536 (record, commands.table['^commit|ci'][1], # same options as commit
537 _('hg record [OPTION]... [FILE]...')),
537 _('hg record [OPTION]... [FILE]...')),
538 }
538 }
539
539
540
540
541 def uisetup(ui):
541 def uisetup(ui):
542 try:
542 try:
543 mq = extensions.find('mq')
543 mq = extensions.find('mq')
544 except KeyError:
544 except KeyError:
545 return
545 return
546
546
547 qcmdtable = {
547 qcmdtable = {
548 "qrecord":
548 "qrecord":
549 (qrecord, mq.cmdtable['^qnew'][1], # same options as qnew
549 (qrecord, mq.cmdtable['^qnew'][1], # same options as qnew
550 _('hg qrecord [OPTION]... PATCH [FILE]...')),
550 _('hg qrecord [OPTION]... PATCH [FILE]...')),
551 }
551 }
552
552
553 cmdtable.update(qcmdtable)
553 cmdtable.update(qcmdtable)
554
554
@@ -1,647 +1,647 b''
1 # Patch transplanting extension for Mercurial
1 # Patch transplanting extension for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.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 '''command to transplant changesets from another branch
8 '''command to transplant changesets from another branch
9
9
10 This extension allows you to transplant patches from another branch.
10 This extension allows you to transplant patches from another branch.
11
11
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
13 map from a changeset hash to its hash in the source repository.
13 map from a changeset hash to its hash in the source repository.
14 '''
14 '''
15
15
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17 import os, tempfile
17 import os, tempfile
18 from mercurial import bundlerepo, cmdutil, hg, merge, match
18 from mercurial import bundlerepo, cmdutil, hg, merge, match
19 from mercurial import patch, revlog, util, error
19 from mercurial import patch, revlog, util, error
20 from mercurial import revset, templatekw
20 from mercurial import revset, templatekw
21
21
22 class transplantentry(object):
22 class transplantentry(object):
23 def __init__(self, lnode, rnode):
23 def __init__(self, lnode, rnode):
24 self.lnode = lnode
24 self.lnode = lnode
25 self.rnode = rnode
25 self.rnode = rnode
26
26
27 class transplants(object):
27 class transplants(object):
28 def __init__(self, path=None, transplantfile=None, opener=None):
28 def __init__(self, path=None, transplantfile=None, opener=None):
29 self.path = path
29 self.path = path
30 self.transplantfile = transplantfile
30 self.transplantfile = transplantfile
31 self.opener = opener
31 self.opener = opener
32
32
33 if not opener:
33 if not opener:
34 self.opener = util.opener(self.path)
34 self.opener = util.opener(self.path)
35 self.transplants = {}
35 self.transplants = {}
36 self.dirty = False
36 self.dirty = False
37 self.read()
37 self.read()
38
38
39 def read(self):
39 def read(self):
40 abspath = os.path.join(self.path, self.transplantfile)
40 abspath = os.path.join(self.path, self.transplantfile)
41 if self.transplantfile and os.path.exists(abspath):
41 if self.transplantfile and os.path.exists(abspath):
42 for line in self.opener(self.transplantfile).read().splitlines():
42 for line in self.opener(self.transplantfile).read().splitlines():
43 lnode, rnode = map(revlog.bin, line.split(':'))
43 lnode, rnode = map(revlog.bin, line.split(':'))
44 list = self.transplants.setdefault(rnode, [])
44 list = self.transplants.setdefault(rnode, [])
45 list.append(transplantentry(lnode, rnode))
45 list.append(transplantentry(lnode, rnode))
46
46
47 def write(self):
47 def write(self):
48 if self.dirty and self.transplantfile:
48 if self.dirty and self.transplantfile:
49 if not os.path.isdir(self.path):
49 if not os.path.isdir(self.path):
50 os.mkdir(self.path)
50 os.mkdir(self.path)
51 fp = self.opener(self.transplantfile, 'w')
51 fp = self.opener(self.transplantfile, 'w')
52 for list in self.transplants.itervalues():
52 for list in self.transplants.itervalues():
53 for t in list:
53 for t in list:
54 l, r = map(revlog.hex, (t.lnode, t.rnode))
54 l, r = map(revlog.hex, (t.lnode, t.rnode))
55 fp.write(l + ':' + r + '\n')
55 fp.write(l + ':' + r + '\n')
56 fp.close()
56 fp.close()
57 self.dirty = False
57 self.dirty = False
58
58
59 def get(self, rnode):
59 def get(self, rnode):
60 return self.transplants.get(rnode) or []
60 return self.transplants.get(rnode) or []
61
61
62 def set(self, lnode, rnode):
62 def set(self, lnode, rnode):
63 list = self.transplants.setdefault(rnode, [])
63 list = self.transplants.setdefault(rnode, [])
64 list.append(transplantentry(lnode, rnode))
64 list.append(transplantentry(lnode, rnode))
65 self.dirty = True
65 self.dirty = True
66
66
67 def remove(self, transplant):
67 def remove(self, transplant):
68 list = self.transplants.get(transplant.rnode)
68 list = self.transplants.get(transplant.rnode)
69 if list:
69 if list:
70 del list[list.index(transplant)]
70 del list[list.index(transplant)]
71 self.dirty = True
71 self.dirty = True
72
72
73 class transplanter(object):
73 class transplanter(object):
74 def __init__(self, ui, repo):
74 def __init__(self, ui, repo):
75 self.ui = ui
75 self.ui = ui
76 self.path = repo.join('transplant')
76 self.path = repo.join('transplant')
77 self.opener = util.opener(self.path)
77 self.opener = util.opener(self.path)
78 self.transplants = transplants(self.path, 'transplants',
78 self.transplants = transplants(self.path, 'transplants',
79 opener=self.opener)
79 opener=self.opener)
80
80
81 def applied(self, repo, node, parent):
81 def applied(self, repo, node, parent):
82 '''returns True if a node is already an ancestor of parent
82 '''returns True if a node is already an ancestor of parent
83 or has already been transplanted'''
83 or has already been transplanted'''
84 if hasnode(repo, node):
84 if hasnode(repo, node):
85 if node in repo.changelog.reachable(parent, stop=node):
85 if node in repo.changelog.reachable(parent, stop=node):
86 return True
86 return True
87 for t in self.transplants.get(node):
87 for t in self.transplants.get(node):
88 # it might have been stripped
88 # it might have been stripped
89 if not hasnode(repo, t.lnode):
89 if not hasnode(repo, t.lnode):
90 self.transplants.remove(t)
90 self.transplants.remove(t)
91 return False
91 return False
92 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
92 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
93 return True
93 return True
94 return False
94 return False
95
95
96 def apply(self, repo, source, revmap, merges, opts={}):
96 def apply(self, repo, source, revmap, merges, opts={}):
97 '''apply the revisions in revmap one by one in revision order'''
97 '''apply the revisions in revmap one by one in revision order'''
98 revs = sorted(revmap)
98 revs = sorted(revmap)
99 p1, p2 = repo.dirstate.parents()
99 p1, p2 = repo.dirstate.parents()
100 pulls = []
100 pulls = []
101 diffopts = patch.diffopts(self.ui, opts)
101 diffopts = patch.diffopts(self.ui, opts)
102 diffopts.git = True
102 diffopts.git = True
103
103
104 lock = wlock = None
104 lock = wlock = None
105 try:
105 try:
106 wlock = repo.wlock()
106 wlock = repo.wlock()
107 lock = repo.lock()
107 lock = repo.lock()
108 for rev in revs:
108 for rev in revs:
109 node = revmap[rev]
109 node = revmap[rev]
110 revstr = '%s:%s' % (rev, revlog.short(node))
110 revstr = '%s:%s' % (rev, revlog.short(node))
111
111
112 if self.applied(repo, node, p1):
112 if self.applied(repo, node, p1):
113 self.ui.warn(_('skipping already applied revision %s\n') %
113 self.ui.warn(_('skipping already applied revision %s\n') %
114 revstr)
114 revstr)
115 continue
115 continue
116
116
117 parents = source.changelog.parents(node)
117 parents = source.changelog.parents(node)
118 if not opts.get('filter'):
118 if not opts.get('filter'):
119 # If the changeset parent is the same as the
119 # If the changeset parent is the same as the
120 # wdir's parent, just pull it.
120 # wdir's parent, just pull it.
121 if parents[0] == p1:
121 if parents[0] == p1:
122 pulls.append(node)
122 pulls.append(node)
123 p1 = node
123 p1 = node
124 continue
124 continue
125 if pulls:
125 if pulls:
126 if source != repo:
126 if source != repo:
127 repo.pull(source, heads=pulls)
127 repo.pull(source, heads=pulls)
128 merge.update(repo, pulls[-1], False, False, None)
128 merge.update(repo, pulls[-1], False, False, None)
129 p1, p2 = repo.dirstate.parents()
129 p1, p2 = repo.dirstate.parents()
130 pulls = []
130 pulls = []
131
131
132 domerge = False
132 domerge = False
133 if node in merges:
133 if node in merges:
134 # pulling all the merge revs at once would mean we
134 # pulling all the merge revs at once would mean we
135 # couldn't transplant after the latest even if
135 # couldn't transplant after the latest even if
136 # transplants before them fail.
136 # transplants before them fail.
137 domerge = True
137 domerge = True
138 if not hasnode(repo, node):
138 if not hasnode(repo, node):
139 repo.pull(source, heads=[node])
139 repo.pull(source, heads=[node])
140
140
141 if parents[1] != revlog.nullid:
141 if parents[1] != revlog.nullid:
142 self.ui.note(_('skipping merge changeset %s:%s\n')
142 self.ui.note(_('skipping merge changeset %s:%s\n')
143 % (rev, revlog.short(node)))
143 % (rev, revlog.short(node)))
144 patchfile = None
144 patchfile = None
145 else:
145 else:
146 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
146 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
147 fp = os.fdopen(fd, 'w')
147 fp = os.fdopen(fd, 'w')
148 gen = patch.diff(source, parents[0], node, opts=diffopts)
148 gen = patch.diff(source, parents[0], node, opts=diffopts)
149 for chunk in gen:
149 for chunk in gen:
150 fp.write(chunk)
150 fp.write(chunk)
151 fp.close()
151 fp.close()
152
152
153 del revmap[rev]
153 del revmap[rev]
154 if patchfile or domerge:
154 if patchfile or domerge:
155 try:
155 try:
156 n = self.applyone(repo, node,
156 n = self.applyone(repo, node,
157 source.changelog.read(node),
157 source.changelog.read(node),
158 patchfile, merge=domerge,
158 patchfile, merge=domerge,
159 log=opts.get('log'),
159 log=opts.get('log'),
160 filter=opts.get('filter'))
160 filter=opts.get('filter'))
161 if n and domerge:
161 if n and domerge:
162 self.ui.status(_('%s merged at %s\n') % (revstr,
162 self.ui.status(_('%s merged at %s\n') % (revstr,
163 revlog.short(n)))
163 revlog.short(n)))
164 elif n:
164 elif n:
165 self.ui.status(_('%s transplanted to %s\n')
165 self.ui.status(_('%s transplanted to %s\n')
166 % (revlog.short(node),
166 % (revlog.short(node),
167 revlog.short(n)))
167 revlog.short(n)))
168 finally:
168 finally:
169 if patchfile:
169 if patchfile:
170 os.unlink(patchfile)
170 os.unlink(patchfile)
171 if pulls:
171 if pulls:
172 repo.pull(source, heads=pulls)
172 repo.pull(source, heads=pulls)
173 merge.update(repo, pulls[-1], False, False, None)
173 merge.update(repo, pulls[-1], False, False, None)
174 finally:
174 finally:
175 self.saveseries(revmap, merges)
175 self.saveseries(revmap, merges)
176 self.transplants.write()
176 self.transplants.write()
177 lock.release()
177 lock.release()
178 wlock.release()
178 wlock.release()
179
179
180 def filter(self, filter, node, changelog, patchfile):
180 def filter(self, filter, node, changelog, patchfile):
181 '''arbitrarily rewrite changeset before applying it'''
181 '''arbitrarily rewrite changeset before applying it'''
182
182
183 self.ui.status(_('filtering %s\n') % patchfile)
183 self.ui.status(_('filtering %s\n') % patchfile)
184 user, date, msg = (changelog[1], changelog[2], changelog[4])
184 user, date, msg = (changelog[1], changelog[2], changelog[4])
185 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
185 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
186 fp = os.fdopen(fd, 'w')
186 fp = os.fdopen(fd, 'w')
187 fp.write("# HG changeset patch\n")
187 fp.write("# HG changeset patch\n")
188 fp.write("# User %s\n" % user)
188 fp.write("# User %s\n" % user)
189 fp.write("# Date %d %d\n" % date)
189 fp.write("# Date %d %d\n" % date)
190 fp.write(msg + '\n')
190 fp.write(msg + '\n')
191 fp.close()
191 fp.close()
192
192
193 try:
193 try:
194 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
194 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
195 util.shellquote(patchfile)),
195 util.shellquote(patchfile)),
196 environ={'HGUSER': changelog[1],
196 environ={'HGUSER': changelog[1],
197 'HGREVISION': revlog.hex(node),
197 'HGREVISION': revlog.hex(node),
198 },
198 },
199 onerr=util.Abort, errprefix=_('filter failed'))
199 onerr=util.Abort, errprefix=_('filter failed'))
200 user, date, msg = self.parselog(file(headerfile))[1:4]
200 user, date, msg = self.parselog(file(headerfile))[1:4]
201 finally:
201 finally:
202 os.unlink(headerfile)
202 os.unlink(headerfile)
203
203
204 return (user, date, msg)
204 return (user, date, msg)
205
205
206 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
206 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
207 filter=None):
207 filter=None):
208 '''apply the patch in patchfile to the repository as a transplant'''
208 '''apply the patch in patchfile to the repository as a transplant'''
209 (manifest, user, (time, timezone), files, message) = cl[:5]
209 (manifest, user, (time, timezone), files, message) = cl[:5]
210 date = "%d %d" % (time, timezone)
210 date = "%d %d" % (time, timezone)
211 extra = {'transplant_source': node}
211 extra = {'transplant_source': node}
212 if filter:
212 if filter:
213 (user, date, message) = self.filter(filter, node, cl, patchfile)
213 (user, date, message) = self.filter(filter, node, cl, patchfile)
214
214
215 if log:
215 if log:
216 # we don't translate messages inserted into commits
216 # we don't translate messages inserted into commits
217 message += '\n(transplanted from %s)' % revlog.hex(node)
217 message += '\n(transplanted from %s)' % revlog.hex(node)
218
218
219 self.ui.status(_('applying %s\n') % revlog.short(node))
219 self.ui.status(_('applying %s\n') % revlog.short(node))
220 self.ui.note('%s %s\n%s\n' % (user, date, message))
220 self.ui.note('%s %s\n%s\n' % (user, date, message))
221
221
222 if not patchfile and not merge:
222 if not patchfile and not merge:
223 raise util.Abort(_('can only omit patchfile if merging'))
223 raise util.Abort(_('can only omit patchfile if merging'))
224 if patchfile:
224 if patchfile:
225 try:
225 try:
226 files = {}
226 files = {}
227 try:
227 try:
228 patch.patch(patchfile, self.ui, cwd=repo.root,
228 patch.patch(patchfile, self.ui, cwd=repo.root,
229 files=files, eolmode=None)
229 files=files, eolmode=None)
230 if not files:
230 if not files:
231 self.ui.warn(_('%s: empty changeset')
231 self.ui.warn(_('%s: empty changeset')
232 % revlog.hex(node))
232 % revlog.hex(node))
233 return None
233 return None
234 finally:
234 finally:
235 files = cmdutil.updatedir(self.ui, repo, files)
235 files = cmdutil.updatedir(self.ui, repo, files)
236 except Exception, inst:
236 except Exception, inst:
237 seriespath = os.path.join(self.path, 'series')
237 seriespath = os.path.join(self.path, 'series')
238 if os.path.exists(seriespath):
238 if os.path.exists(seriespath):
239 os.unlink(seriespath)
239 os.unlink(seriespath)
240 p1 = repo.dirstate.parents()[0]
240 p1 = repo.dirstate.p1()
241 p2 = node
241 p2 = node
242 self.log(user, date, message, p1, p2, merge=merge)
242 self.log(user, date, message, p1, p2, merge=merge)
243 self.ui.write(str(inst) + '\n')
243 self.ui.write(str(inst) + '\n')
244 raise util.Abort(_('fix up the merge and run '
244 raise util.Abort(_('fix up the merge and run '
245 'hg transplant --continue'))
245 'hg transplant --continue'))
246 else:
246 else:
247 files = None
247 files = None
248 if merge:
248 if merge:
249 p1, p2 = repo.dirstate.parents()
249 p1, p2 = repo.dirstate.parents()
250 repo.dirstate.setparents(p1, node)
250 repo.dirstate.setparents(p1, node)
251 m = match.always(repo.root, '')
251 m = match.always(repo.root, '')
252 else:
252 else:
253 m = match.exact(repo.root, '', files)
253 m = match.exact(repo.root, '', files)
254
254
255 n = repo.commit(message, user, date, extra=extra, match=m)
255 n = repo.commit(message, user, date, extra=extra, match=m)
256 if not n:
256 if not n:
257 # Crash here to prevent an unclear crash later, in
257 # Crash here to prevent an unclear crash later, in
258 # transplants.write(). This can happen if patch.patch()
258 # transplants.write(). This can happen if patch.patch()
259 # does nothing but claims success or if repo.status() fails
259 # does nothing but claims success or if repo.status() fails
260 # to report changes done by patch.patch(). These both
260 # to report changes done by patch.patch(). These both
261 # appear to be bugs in other parts of Mercurial, but dying
261 # appear to be bugs in other parts of Mercurial, but dying
262 # here, as soon as we can detect the problem, is preferable
262 # here, as soon as we can detect the problem, is preferable
263 # to silently dropping changesets on the floor.
263 # to silently dropping changesets on the floor.
264 raise RuntimeError('nothing committed after transplant')
264 raise RuntimeError('nothing committed after transplant')
265 if not merge:
265 if not merge:
266 self.transplants.set(n, node)
266 self.transplants.set(n, node)
267
267
268 return n
268 return n
269
269
270 def resume(self, repo, source, opts=None):
270 def resume(self, repo, source, opts=None):
271 '''recover last transaction and apply remaining changesets'''
271 '''recover last transaction and apply remaining changesets'''
272 if os.path.exists(os.path.join(self.path, 'journal')):
272 if os.path.exists(os.path.join(self.path, 'journal')):
273 n, node = self.recover(repo)
273 n, node = self.recover(repo)
274 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
274 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
275 revlog.short(n)))
275 revlog.short(n)))
276 seriespath = os.path.join(self.path, 'series')
276 seriespath = os.path.join(self.path, 'series')
277 if not os.path.exists(seriespath):
277 if not os.path.exists(seriespath):
278 self.transplants.write()
278 self.transplants.write()
279 return
279 return
280 nodes, merges = self.readseries()
280 nodes, merges = self.readseries()
281 revmap = {}
281 revmap = {}
282 for n in nodes:
282 for n in nodes:
283 revmap[source.changelog.rev(n)] = n
283 revmap[source.changelog.rev(n)] = n
284 os.unlink(seriespath)
284 os.unlink(seriespath)
285
285
286 self.apply(repo, source, revmap, merges, opts)
286 self.apply(repo, source, revmap, merges, opts)
287
287
288 def recover(self, repo):
288 def recover(self, repo):
289 '''commit working directory using journal metadata'''
289 '''commit working directory using journal metadata'''
290 node, user, date, message, parents = self.readlog()
290 node, user, date, message, parents = self.readlog()
291 merge = len(parents) == 2
291 merge = len(parents) == 2
292
292
293 if not user or not date or not message or not parents[0]:
293 if not user or not date or not message or not parents[0]:
294 raise util.Abort(_('transplant log file is corrupt'))
294 raise util.Abort(_('transplant log file is corrupt'))
295
295
296 extra = {'transplant_source': node}
296 extra = {'transplant_source': node}
297 wlock = repo.wlock()
297 wlock = repo.wlock()
298 try:
298 try:
299 p1, p2 = repo.dirstate.parents()
299 p1, p2 = repo.dirstate.parents()
300 if p1 != parents[0]:
300 if p1 != parents[0]:
301 raise util.Abort(
301 raise util.Abort(
302 _('working dir not at transplant parent %s') %
302 _('working dir not at transplant parent %s') %
303 revlog.hex(parents[0]))
303 revlog.hex(parents[0]))
304 if merge:
304 if merge:
305 repo.dirstate.setparents(p1, parents[1])
305 repo.dirstate.setparents(p1, parents[1])
306 n = repo.commit(message, user, date, extra=extra)
306 n = repo.commit(message, user, date, extra=extra)
307 if not n:
307 if not n:
308 raise util.Abort(_('commit failed'))
308 raise util.Abort(_('commit failed'))
309 if not merge:
309 if not merge:
310 self.transplants.set(n, node)
310 self.transplants.set(n, node)
311 self.unlog()
311 self.unlog()
312
312
313 return n, node
313 return n, node
314 finally:
314 finally:
315 wlock.release()
315 wlock.release()
316
316
317 def readseries(self):
317 def readseries(self):
318 nodes = []
318 nodes = []
319 merges = []
319 merges = []
320 cur = nodes
320 cur = nodes
321 for line in self.opener('series').read().splitlines():
321 for line in self.opener('series').read().splitlines():
322 if line.startswith('# Merges'):
322 if line.startswith('# Merges'):
323 cur = merges
323 cur = merges
324 continue
324 continue
325 cur.append(revlog.bin(line))
325 cur.append(revlog.bin(line))
326
326
327 return (nodes, merges)
327 return (nodes, merges)
328
328
329 def saveseries(self, revmap, merges):
329 def saveseries(self, revmap, merges):
330 if not revmap:
330 if not revmap:
331 return
331 return
332
332
333 if not os.path.isdir(self.path):
333 if not os.path.isdir(self.path):
334 os.mkdir(self.path)
334 os.mkdir(self.path)
335 series = self.opener('series', 'w')
335 series = self.opener('series', 'w')
336 for rev in sorted(revmap):
336 for rev in sorted(revmap):
337 series.write(revlog.hex(revmap[rev]) + '\n')
337 series.write(revlog.hex(revmap[rev]) + '\n')
338 if merges:
338 if merges:
339 series.write('# Merges\n')
339 series.write('# Merges\n')
340 for m in merges:
340 for m in merges:
341 series.write(revlog.hex(m) + '\n')
341 series.write(revlog.hex(m) + '\n')
342 series.close()
342 series.close()
343
343
344 def parselog(self, fp):
344 def parselog(self, fp):
345 parents = []
345 parents = []
346 message = []
346 message = []
347 node = revlog.nullid
347 node = revlog.nullid
348 inmsg = False
348 inmsg = False
349 user = None
349 user = None
350 date = None
350 date = None
351 for line in fp.read().splitlines():
351 for line in fp.read().splitlines():
352 if inmsg:
352 if inmsg:
353 message.append(line)
353 message.append(line)
354 elif line.startswith('# User '):
354 elif line.startswith('# User '):
355 user = line[7:]
355 user = line[7:]
356 elif line.startswith('# Date '):
356 elif line.startswith('# Date '):
357 date = line[7:]
357 date = line[7:]
358 elif line.startswith('# Node ID '):
358 elif line.startswith('# Node ID '):
359 node = revlog.bin(line[10:])
359 node = revlog.bin(line[10:])
360 elif line.startswith('# Parent '):
360 elif line.startswith('# Parent '):
361 parents.append(revlog.bin(line[9:]))
361 parents.append(revlog.bin(line[9:]))
362 elif not line.startswith('# '):
362 elif not line.startswith('# '):
363 inmsg = True
363 inmsg = True
364 message.append(line)
364 message.append(line)
365 if None in (user, date):
365 if None in (user, date):
366 raise util.Abort(_("filter corrupted changeset (no user or date)"))
366 raise util.Abort(_("filter corrupted changeset (no user or date)"))
367 return (node, user, date, '\n'.join(message), parents)
367 return (node, user, date, '\n'.join(message), parents)
368
368
369 def log(self, user, date, message, p1, p2, merge=False):
369 def log(self, user, date, message, p1, p2, merge=False):
370 '''journal changelog metadata for later recover'''
370 '''journal changelog metadata for later recover'''
371
371
372 if not os.path.isdir(self.path):
372 if not os.path.isdir(self.path):
373 os.mkdir(self.path)
373 os.mkdir(self.path)
374 fp = self.opener('journal', 'w')
374 fp = self.opener('journal', 'w')
375 fp.write('# User %s\n' % user)
375 fp.write('# User %s\n' % user)
376 fp.write('# Date %s\n' % date)
376 fp.write('# Date %s\n' % date)
377 fp.write('# Node ID %s\n' % revlog.hex(p2))
377 fp.write('# Node ID %s\n' % revlog.hex(p2))
378 fp.write('# Parent ' + revlog.hex(p1) + '\n')
378 fp.write('# Parent ' + revlog.hex(p1) + '\n')
379 if merge:
379 if merge:
380 fp.write('# Parent ' + revlog.hex(p2) + '\n')
380 fp.write('# Parent ' + revlog.hex(p2) + '\n')
381 fp.write(message.rstrip() + '\n')
381 fp.write(message.rstrip() + '\n')
382 fp.close()
382 fp.close()
383
383
384 def readlog(self):
384 def readlog(self):
385 return self.parselog(self.opener('journal'))
385 return self.parselog(self.opener('journal'))
386
386
387 def unlog(self):
387 def unlog(self):
388 '''remove changelog journal'''
388 '''remove changelog journal'''
389 absdst = os.path.join(self.path, 'journal')
389 absdst = os.path.join(self.path, 'journal')
390 if os.path.exists(absdst):
390 if os.path.exists(absdst):
391 os.unlink(absdst)
391 os.unlink(absdst)
392
392
393 def transplantfilter(self, repo, source, root):
393 def transplantfilter(self, repo, source, root):
394 def matchfn(node):
394 def matchfn(node):
395 if self.applied(repo, node, root):
395 if self.applied(repo, node, root):
396 return False
396 return False
397 if source.changelog.parents(node)[1] != revlog.nullid:
397 if source.changelog.parents(node)[1] != revlog.nullid:
398 return False
398 return False
399 extra = source.changelog.read(node)[5]
399 extra = source.changelog.read(node)[5]
400 cnode = extra.get('transplant_source')
400 cnode = extra.get('transplant_source')
401 if cnode and self.applied(repo, cnode, root):
401 if cnode and self.applied(repo, cnode, root):
402 return False
402 return False
403 return True
403 return True
404
404
405 return matchfn
405 return matchfn
406
406
407 def hasnode(repo, node):
407 def hasnode(repo, node):
408 try:
408 try:
409 return repo.changelog.rev(node) is not None
409 return repo.changelog.rev(node) is not None
410 except error.RevlogError:
410 except error.RevlogError:
411 return False
411 return False
412
412
413 def browserevs(ui, repo, nodes, opts):
413 def browserevs(ui, repo, nodes, opts):
414 '''interactively transplant changesets'''
414 '''interactively transplant changesets'''
415 def browsehelp(ui):
415 def browsehelp(ui):
416 ui.write(_('y: transplant this changeset\n'
416 ui.write(_('y: transplant this changeset\n'
417 'n: skip this changeset\n'
417 'n: skip this changeset\n'
418 'm: merge at this changeset\n'
418 'm: merge at this changeset\n'
419 'p: show patch\n'
419 'p: show patch\n'
420 'c: commit selected changesets\n'
420 'c: commit selected changesets\n'
421 'q: cancel transplant\n'
421 'q: cancel transplant\n'
422 '?: show this help\n'))
422 '?: show this help\n'))
423
423
424 displayer = cmdutil.show_changeset(ui, repo, opts)
424 displayer = cmdutil.show_changeset(ui, repo, opts)
425 transplants = []
425 transplants = []
426 merges = []
426 merges = []
427 for node in nodes:
427 for node in nodes:
428 displayer.show(repo[node])
428 displayer.show(repo[node])
429 action = None
429 action = None
430 while not action:
430 while not action:
431 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
431 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
432 if action == '?':
432 if action == '?':
433 browsehelp(ui)
433 browsehelp(ui)
434 action = None
434 action = None
435 elif action == 'p':
435 elif action == 'p':
436 parent = repo.changelog.parents(node)[0]
436 parent = repo.changelog.parents(node)[0]
437 for chunk in patch.diff(repo, parent, node):
437 for chunk in patch.diff(repo, parent, node):
438 ui.write(chunk)
438 ui.write(chunk)
439 action = None
439 action = None
440 elif action not in ('y', 'n', 'm', 'c', 'q'):
440 elif action not in ('y', 'n', 'm', 'c', 'q'):
441 ui.write(_('no such option\n'))
441 ui.write(_('no such option\n'))
442 action = None
442 action = None
443 if action == 'y':
443 if action == 'y':
444 transplants.append(node)
444 transplants.append(node)
445 elif action == 'm':
445 elif action == 'm':
446 merges.append(node)
446 merges.append(node)
447 elif action == 'c':
447 elif action == 'c':
448 break
448 break
449 elif action == 'q':
449 elif action == 'q':
450 transplants = ()
450 transplants = ()
451 merges = ()
451 merges = ()
452 break
452 break
453 displayer.close()
453 displayer.close()
454 return (transplants, merges)
454 return (transplants, merges)
455
455
456 def transplant(ui, repo, *revs, **opts):
456 def transplant(ui, repo, *revs, **opts):
457 '''transplant changesets from another branch
457 '''transplant changesets from another branch
458
458
459 Selected changesets will be applied on top of the current working
459 Selected changesets will be applied on top of the current working
460 directory with the log of the original changeset. The changesets
460 directory with the log of the original changeset. The changesets
461 are copied and will thus appear twice in the history. Use the
461 are copied and will thus appear twice in the history. Use the
462 rebase extension instead if you want to move a whole branch of
462 rebase extension instead if you want to move a whole branch of
463 unpublished changesets.
463 unpublished changesets.
464
464
465 If --log is specified, log messages will have a comment appended
465 If --log is specified, log messages will have a comment appended
466 of the form::
466 of the form::
467
467
468 (transplanted from CHANGESETHASH)
468 (transplanted from CHANGESETHASH)
469
469
470 You can rewrite the changelog message with the --filter option.
470 You can rewrite the changelog message with the --filter option.
471 Its argument will be invoked with the current changelog message as
471 Its argument will be invoked with the current changelog message as
472 $1 and the patch as $2.
472 $1 and the patch as $2.
473
473
474 If --source/-s is specified, selects changesets from the named
474 If --source/-s is specified, selects changesets from the named
475 repository. If --branch/-b is specified, selects changesets from
475 repository. If --branch/-b is specified, selects changesets from
476 the branch holding the named revision, up to that revision. If
476 the branch holding the named revision, up to that revision. If
477 --all/-a is specified, all changesets on the branch will be
477 --all/-a is specified, all changesets on the branch will be
478 transplanted, otherwise you will be prompted to select the
478 transplanted, otherwise you will be prompted to select the
479 changesets you want.
479 changesets you want.
480
480
481 :hg:`transplant --branch REVISION --all` will transplant the
481 :hg:`transplant --branch REVISION --all` will transplant the
482 selected branch (up to the named revision) onto your current
482 selected branch (up to the named revision) onto your current
483 working directory.
483 working directory.
484
484
485 You can optionally mark selected transplanted changesets as merge
485 You can optionally mark selected transplanted changesets as merge
486 changesets. You will not be prompted to transplant any ancestors
486 changesets. You will not be prompted to transplant any ancestors
487 of a merged transplant, and you can merge descendants of them
487 of a merged transplant, and you can merge descendants of them
488 normally instead of transplanting them.
488 normally instead of transplanting them.
489
489
490 If no merges or revisions are provided, :hg:`transplant` will
490 If no merges or revisions are provided, :hg:`transplant` will
491 start an interactive changeset browser.
491 start an interactive changeset browser.
492
492
493 If a changeset application fails, you can fix the merge by hand
493 If a changeset application fails, you can fix the merge by hand
494 and then resume where you left off by calling :hg:`transplant
494 and then resume where you left off by calling :hg:`transplant
495 --continue/-c`.
495 --continue/-c`.
496 '''
496 '''
497 def incwalk(repo, incoming, branches, match=util.always):
497 def incwalk(repo, incoming, branches, match=util.always):
498 if not branches:
498 if not branches:
499 branches = None
499 branches = None
500 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
500 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
501 if match(node):
501 if match(node):
502 yield node
502 yield node
503
503
504 def transplantwalk(repo, root, branches, match=util.always):
504 def transplantwalk(repo, root, branches, match=util.always):
505 if not branches:
505 if not branches:
506 branches = repo.heads()
506 branches = repo.heads()
507 ancestors = []
507 ancestors = []
508 for branch in branches:
508 for branch in branches:
509 ancestors.append(repo.changelog.ancestor(root, branch))
509 ancestors.append(repo.changelog.ancestor(root, branch))
510 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
510 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
511 if match(node):
511 if match(node):
512 yield node
512 yield node
513
513
514 def checkopts(opts, revs):
514 def checkopts(opts, revs):
515 if opts.get('continue'):
515 if opts.get('continue'):
516 if opts.get('branch') or opts.get('all') or opts.get('merge'):
516 if opts.get('branch') or opts.get('all') or opts.get('merge'):
517 raise util.Abort(_('--continue is incompatible with '
517 raise util.Abort(_('--continue is incompatible with '
518 'branch, all or merge'))
518 'branch, all or merge'))
519 return
519 return
520 if not (opts.get('source') or revs or
520 if not (opts.get('source') or revs or
521 opts.get('merge') or opts.get('branch')):
521 opts.get('merge') or opts.get('branch')):
522 raise util.Abort(_('no source URL, branch tag or revision '
522 raise util.Abort(_('no source URL, branch tag or revision '
523 'list provided'))
523 'list provided'))
524 if opts.get('all'):
524 if opts.get('all'):
525 if not opts.get('branch'):
525 if not opts.get('branch'):
526 raise util.Abort(_('--all requires a branch revision'))
526 raise util.Abort(_('--all requires a branch revision'))
527 if revs:
527 if revs:
528 raise util.Abort(_('--all is incompatible with a '
528 raise util.Abort(_('--all is incompatible with a '
529 'revision list'))
529 'revision list'))
530
530
531 checkopts(opts, revs)
531 checkopts(opts, revs)
532
532
533 if not opts.get('log'):
533 if not opts.get('log'):
534 opts['log'] = ui.config('transplant', 'log')
534 opts['log'] = ui.config('transplant', 'log')
535 if not opts.get('filter'):
535 if not opts.get('filter'):
536 opts['filter'] = ui.config('transplant', 'filter')
536 opts['filter'] = ui.config('transplant', 'filter')
537
537
538 tp = transplanter(ui, repo)
538 tp = transplanter(ui, repo)
539
539
540 p1, p2 = repo.dirstate.parents()
540 p1, p2 = repo.dirstate.parents()
541 if len(repo) > 0 and p1 == revlog.nullid:
541 if len(repo) > 0 and p1 == revlog.nullid:
542 raise util.Abort(_('no revision checked out'))
542 raise util.Abort(_('no revision checked out'))
543 if not opts.get('continue'):
543 if not opts.get('continue'):
544 if p2 != revlog.nullid:
544 if p2 != revlog.nullid:
545 raise util.Abort(_('outstanding uncommitted merges'))
545 raise util.Abort(_('outstanding uncommitted merges'))
546 m, a, r, d = repo.status()[:4]
546 m, a, r, d = repo.status()[:4]
547 if m or a or r or d:
547 if m or a or r or d:
548 raise util.Abort(_('outstanding local changes'))
548 raise util.Abort(_('outstanding local changes'))
549
549
550 bundle = None
550 bundle = None
551 source = opts.get('source')
551 source = opts.get('source')
552 if source:
552 if source:
553 sourcerepo = ui.expandpath(source)
553 sourcerepo = ui.expandpath(source)
554 source = hg.repository(ui, sourcerepo)
554 source = hg.repository(ui, sourcerepo)
555 source, common, incoming, bundle = bundlerepo.getremotechanges(ui, repo,
555 source, common, incoming, bundle = bundlerepo.getremotechanges(ui, repo,
556 source, force=True)
556 source, force=True)
557 else:
557 else:
558 source = repo
558 source = repo
559
559
560 try:
560 try:
561 if opts.get('continue'):
561 if opts.get('continue'):
562 tp.resume(repo, source, opts)
562 tp.resume(repo, source, opts)
563 return
563 return
564
564
565 tf = tp.transplantfilter(repo, source, p1)
565 tf = tp.transplantfilter(repo, source, p1)
566 if opts.get('prune'):
566 if opts.get('prune'):
567 prune = [source.lookup(r)
567 prune = [source.lookup(r)
568 for r in cmdutil.revrange(source, opts.get('prune'))]
568 for r in cmdutil.revrange(source, opts.get('prune'))]
569 matchfn = lambda x: tf(x) and x not in prune
569 matchfn = lambda x: tf(x) and x not in prune
570 else:
570 else:
571 matchfn = tf
571 matchfn = tf
572 branches = map(source.lookup, opts.get('branch', ()))
572 branches = map(source.lookup, opts.get('branch', ()))
573 merges = map(source.lookup, opts.get('merge', ()))
573 merges = map(source.lookup, opts.get('merge', ()))
574 revmap = {}
574 revmap = {}
575 if revs:
575 if revs:
576 for r in cmdutil.revrange(source, revs):
576 for r in cmdutil.revrange(source, revs):
577 revmap[int(r)] = source.lookup(r)
577 revmap[int(r)] = source.lookup(r)
578 elif opts.get('all') or not merges:
578 elif opts.get('all') or not merges:
579 if source != repo:
579 if source != repo:
580 alltransplants = incwalk(source, incoming, branches,
580 alltransplants = incwalk(source, incoming, branches,
581 match=matchfn)
581 match=matchfn)
582 else:
582 else:
583 alltransplants = transplantwalk(source, p1, branches,
583 alltransplants = transplantwalk(source, p1, branches,
584 match=matchfn)
584 match=matchfn)
585 if opts.get('all'):
585 if opts.get('all'):
586 revs = alltransplants
586 revs = alltransplants
587 else:
587 else:
588 revs, newmerges = browserevs(ui, source, alltransplants, opts)
588 revs, newmerges = browserevs(ui, source, alltransplants, opts)
589 merges.extend(newmerges)
589 merges.extend(newmerges)
590 for r in revs:
590 for r in revs:
591 revmap[source.changelog.rev(r)] = r
591 revmap[source.changelog.rev(r)] = r
592 for r in merges:
592 for r in merges:
593 revmap[source.changelog.rev(r)] = r
593 revmap[source.changelog.rev(r)] = r
594
594
595 tp.apply(repo, source, revmap, merges, opts)
595 tp.apply(repo, source, revmap, merges, opts)
596 finally:
596 finally:
597 if bundle:
597 if bundle:
598 source.close()
598 source.close()
599 os.unlink(bundle)
599 os.unlink(bundle)
600
600
601 def revsettransplanted(repo, subset, x):
601 def revsettransplanted(repo, subset, x):
602 """``transplanted(set)``
602 """``transplanted(set)``
603 Transplanted changesets in set.
603 Transplanted changesets in set.
604 """
604 """
605 if x:
605 if x:
606 s = revset.getset(repo, subset, x)
606 s = revset.getset(repo, subset, x)
607 else:
607 else:
608 s = subset
608 s = subset
609 cs = set()
609 cs = set()
610 for r in xrange(0, len(repo)):
610 for r in xrange(0, len(repo)):
611 if repo[r].extra().get('transplant_source'):
611 if repo[r].extra().get('transplant_source'):
612 cs.add(r)
612 cs.add(r)
613 return [r for r in s if r in cs]
613 return [r for r in s if r in cs]
614
614
615 def kwtransplanted(repo, ctx, **args):
615 def kwtransplanted(repo, ctx, **args):
616 """:transplanted: String. The node identifier of the transplanted
616 """:transplanted: String. The node identifier of the transplanted
617 changeset if any."""
617 changeset if any."""
618 n = ctx.extra().get('transplant_source')
618 n = ctx.extra().get('transplant_source')
619 return n and revlog.hex(n) or ''
619 return n and revlog.hex(n) or ''
620
620
621 def extsetup(ui):
621 def extsetup(ui):
622 revset.symbols['transplanted'] = revsettransplanted
622 revset.symbols['transplanted'] = revsettransplanted
623 templatekw.keywords['transplanted'] = kwtransplanted
623 templatekw.keywords['transplanted'] = kwtransplanted
624
624
625 cmdtable = {
625 cmdtable = {
626 "transplant":
626 "transplant":
627 (transplant,
627 (transplant,
628 [('s', 'source', '',
628 [('s', 'source', '',
629 _('pull patches from REPO'), _('REPO')),
629 _('pull patches from REPO'), _('REPO')),
630 ('b', 'branch', [],
630 ('b', 'branch', [],
631 _('pull patches from branch BRANCH'), _('BRANCH')),
631 _('pull patches from branch BRANCH'), _('BRANCH')),
632 ('a', 'all', None, _('pull all changesets up to BRANCH')),
632 ('a', 'all', None, _('pull all changesets up to BRANCH')),
633 ('p', 'prune', [],
633 ('p', 'prune', [],
634 _('skip over REV'), _('REV')),
634 _('skip over REV'), _('REV')),
635 ('m', 'merge', [],
635 ('m', 'merge', [],
636 _('merge at REV'), _('REV')),
636 _('merge at REV'), _('REV')),
637 ('', 'log', None, _('append transplant info to log message')),
637 ('', 'log', None, _('append transplant info to log message')),
638 ('c', 'continue', None, _('continue last transplant session '
638 ('c', 'continue', None, _('continue last transplant session '
639 'after repair')),
639 'after repair')),
640 ('', 'filter', '',
640 ('', 'filter', '',
641 _('filter changesets through command'), _('CMD'))],
641 _('filter changesets through command'), _('CMD'))],
642 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
642 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
643 '[-m REV] [REV]...'))
643 '[-m REV] [REV]...'))
644 }
644 }
645
645
646 # tell hggettext to extract docstrings from these functions:
646 # tell hggettext to extract docstrings from these functions:
647 i18nfunctions = [revsettransplanted, kwtransplanted]
647 i18nfunctions = [revsettransplanted, kwtransplanted]
@@ -1,1389 +1,1389 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, glob, tempfile
10 import os, sys, errno, re, glob, tempfile
11 import util, templater, patch, error, encoding, templatekw
11 import util, templater, patch, error, encoding, templatekw
12 import match as matchmod
12 import match as matchmod
13 import similar, revset, subrepo
13 import similar, revset, subrepo
14
14
15 revrangesep = ':'
15 revrangesep = ':'
16
16
17 def parsealiases(cmd):
17 def parsealiases(cmd):
18 return cmd.lstrip("^").split("|")
18 return cmd.lstrip("^").split("|")
19
19
20 def findpossible(cmd, table, strict=False):
20 def findpossible(cmd, table, strict=False):
21 """
21 """
22 Return cmd -> (aliases, command table entry)
22 Return cmd -> (aliases, command table entry)
23 for each matching command.
23 for each matching command.
24 Return debug commands (or their aliases) only if no normal command matches.
24 Return debug commands (or their aliases) only if no normal command matches.
25 """
25 """
26 choice = {}
26 choice = {}
27 debugchoice = {}
27 debugchoice = {}
28 for e in table.keys():
28 for e in table.keys():
29 aliases = parsealiases(e)
29 aliases = parsealiases(e)
30 found = None
30 found = None
31 if cmd in aliases:
31 if cmd in aliases:
32 found = cmd
32 found = cmd
33 elif not strict:
33 elif not strict:
34 for a in aliases:
34 for a in aliases:
35 if a.startswith(cmd):
35 if a.startswith(cmd):
36 found = a
36 found = a
37 break
37 break
38 if found is not None:
38 if found is not None:
39 if aliases[0].startswith("debug") or found.startswith("debug"):
39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 debugchoice[found] = (aliases, table[e])
40 debugchoice[found] = (aliases, table[e])
41 else:
41 else:
42 choice[found] = (aliases, table[e])
42 choice[found] = (aliases, table[e])
43
43
44 if not choice and debugchoice:
44 if not choice and debugchoice:
45 choice = debugchoice
45 choice = debugchoice
46
46
47 return choice
47 return choice
48
48
49 def findcmd(cmd, table, strict=True):
49 def findcmd(cmd, table, strict=True):
50 """Return (aliases, command table entry) for command string."""
50 """Return (aliases, command table entry) for command string."""
51 choice = findpossible(cmd, table, strict)
51 choice = findpossible(cmd, table, strict)
52
52
53 if cmd in choice:
53 if cmd in choice:
54 return choice[cmd]
54 return choice[cmd]
55
55
56 if len(choice) > 1:
56 if len(choice) > 1:
57 clist = choice.keys()
57 clist = choice.keys()
58 clist.sort()
58 clist.sort()
59 raise error.AmbiguousCommand(cmd, clist)
59 raise error.AmbiguousCommand(cmd, clist)
60
60
61 if choice:
61 if choice:
62 return choice.values()[0]
62 return choice.values()[0]
63
63
64 raise error.UnknownCommand(cmd)
64 raise error.UnknownCommand(cmd)
65
65
66 def findrepo(p):
66 def findrepo(p):
67 while not os.path.isdir(os.path.join(p, ".hg")):
67 while not os.path.isdir(os.path.join(p, ".hg")):
68 oldp, p = p, os.path.dirname(p)
68 oldp, p = p, os.path.dirname(p)
69 if p == oldp:
69 if p == oldp:
70 return None
70 return None
71
71
72 return p
72 return p
73
73
74 def bail_if_changed(repo):
74 def bail_if_changed(repo):
75 if repo.dirstate.parents()[1] != nullid:
75 if repo.dirstate.p2() != nullid:
76 raise util.Abort(_('outstanding uncommitted merge'))
76 raise util.Abort(_('outstanding uncommitted merge'))
77 modified, added, removed, deleted = repo.status()[:4]
77 modified, added, removed, deleted = repo.status()[:4]
78 if modified or added or removed or deleted:
78 if modified or added or removed or deleted:
79 raise util.Abort(_("outstanding uncommitted changes"))
79 raise util.Abort(_("outstanding uncommitted changes"))
80
80
81 def logmessage(opts):
81 def logmessage(opts):
82 """ get the log message according to -m and -l option """
82 """ get the log message according to -m and -l option """
83 message = opts.get('message')
83 message = opts.get('message')
84 logfile = opts.get('logfile')
84 logfile = opts.get('logfile')
85
85
86 if message and logfile:
86 if message and logfile:
87 raise util.Abort(_('options --message and --logfile are mutually '
87 raise util.Abort(_('options --message and --logfile are mutually '
88 'exclusive'))
88 'exclusive'))
89 if not message and logfile:
89 if not message and logfile:
90 try:
90 try:
91 if logfile == '-':
91 if logfile == '-':
92 message = sys.stdin.read()
92 message = sys.stdin.read()
93 else:
93 else:
94 message = open(logfile).read()
94 message = open(logfile).read()
95 except IOError, inst:
95 except IOError, inst:
96 raise util.Abort(_("can't read commit message '%s': %s") %
96 raise util.Abort(_("can't read commit message '%s': %s") %
97 (logfile, inst.strerror))
97 (logfile, inst.strerror))
98 return message
98 return message
99
99
100 def loglimit(opts):
100 def loglimit(opts):
101 """get the log limit according to option -l/--limit"""
101 """get the log limit according to option -l/--limit"""
102 limit = opts.get('limit')
102 limit = opts.get('limit')
103 if limit:
103 if limit:
104 try:
104 try:
105 limit = int(limit)
105 limit = int(limit)
106 except ValueError:
106 except ValueError:
107 raise util.Abort(_('limit must be a positive integer'))
107 raise util.Abort(_('limit must be a positive integer'))
108 if limit <= 0:
108 if limit <= 0:
109 raise util.Abort(_('limit must be positive'))
109 raise util.Abort(_('limit must be positive'))
110 else:
110 else:
111 limit = None
111 limit = None
112 return limit
112 return limit
113
113
114 def revsingle(repo, revspec, default='.'):
114 def revsingle(repo, revspec, default='.'):
115 if not revspec:
115 if not revspec:
116 return repo[default]
116 return repo[default]
117
117
118 l = revrange(repo, [revspec])
118 l = revrange(repo, [revspec])
119 if len(l) < 1:
119 if len(l) < 1:
120 raise util.Abort(_('empty revision set'))
120 raise util.Abort(_('empty revision set'))
121 return repo[l[-1]]
121 return repo[l[-1]]
122
122
123 def revpair(repo, revs):
123 def revpair(repo, revs):
124 if not revs:
124 if not revs:
125 return repo.dirstate.parents()[0], None
125 return repo.dirstate.p1(), None
126
126
127 l = revrange(repo, revs)
127 l = revrange(repo, revs)
128
128
129 if len(l) == 0:
129 if len(l) == 0:
130 return repo.dirstate.parents()[0], None
130 return repo.dirstate.p1(), None
131
131
132 if len(l) == 1:
132 if len(l) == 1:
133 return repo.lookup(l[0]), None
133 return repo.lookup(l[0]), None
134
134
135 return repo.lookup(l[0]), repo.lookup(l[-1])
135 return repo.lookup(l[0]), repo.lookup(l[-1])
136
136
137 def revrange(repo, revs):
137 def revrange(repo, revs):
138 """Yield revision as strings from a list of revision specifications."""
138 """Yield revision as strings from a list of revision specifications."""
139
139
140 def revfix(repo, val, defval):
140 def revfix(repo, val, defval):
141 if not val and val != 0 and defval is not None:
141 if not val and val != 0 and defval is not None:
142 return defval
142 return defval
143 return repo.changelog.rev(repo.lookup(val))
143 return repo.changelog.rev(repo.lookup(val))
144
144
145 seen, l = set(), []
145 seen, l = set(), []
146 for spec in revs:
146 for spec in revs:
147 # attempt to parse old-style ranges first to deal with
147 # attempt to parse old-style ranges first to deal with
148 # things like old-tag which contain query metacharacters
148 # things like old-tag which contain query metacharacters
149 try:
149 try:
150 if isinstance(spec, int):
150 if isinstance(spec, int):
151 seen.add(spec)
151 seen.add(spec)
152 l.append(spec)
152 l.append(spec)
153 continue
153 continue
154
154
155 if revrangesep in spec:
155 if revrangesep in spec:
156 start, end = spec.split(revrangesep, 1)
156 start, end = spec.split(revrangesep, 1)
157 start = revfix(repo, start, 0)
157 start = revfix(repo, start, 0)
158 end = revfix(repo, end, len(repo) - 1)
158 end = revfix(repo, end, len(repo) - 1)
159 step = start > end and -1 or 1
159 step = start > end and -1 or 1
160 for rev in xrange(start, end + step, step):
160 for rev in xrange(start, end + step, step):
161 if rev in seen:
161 if rev in seen:
162 continue
162 continue
163 seen.add(rev)
163 seen.add(rev)
164 l.append(rev)
164 l.append(rev)
165 continue
165 continue
166 elif spec and spec in repo: # single unquoted rev
166 elif spec and spec in repo: # single unquoted rev
167 rev = revfix(repo, spec, None)
167 rev = revfix(repo, spec, None)
168 if rev in seen:
168 if rev in seen:
169 continue
169 continue
170 seen.add(rev)
170 seen.add(rev)
171 l.append(rev)
171 l.append(rev)
172 continue
172 continue
173 except error.RepoLookupError:
173 except error.RepoLookupError:
174 pass
174 pass
175
175
176 # fall through to new-style queries if old-style fails
176 # fall through to new-style queries if old-style fails
177 m = revset.match(spec)
177 m = revset.match(spec)
178 for r in m(repo, range(len(repo))):
178 for r in m(repo, range(len(repo))):
179 if r not in seen:
179 if r not in seen:
180 l.append(r)
180 l.append(r)
181 seen.update(l)
181 seen.update(l)
182
182
183 return l
183 return l
184
184
185 def make_filename(repo, pat, node,
185 def make_filename(repo, pat, node,
186 total=None, seqno=None, revwidth=None, pathname=None):
186 total=None, seqno=None, revwidth=None, pathname=None):
187 node_expander = {
187 node_expander = {
188 'H': lambda: hex(node),
188 'H': lambda: hex(node),
189 'R': lambda: str(repo.changelog.rev(node)),
189 'R': lambda: str(repo.changelog.rev(node)),
190 'h': lambda: short(node),
190 'h': lambda: short(node),
191 }
191 }
192 expander = {
192 expander = {
193 '%': lambda: '%',
193 '%': lambda: '%',
194 'b': lambda: os.path.basename(repo.root),
194 'b': lambda: os.path.basename(repo.root),
195 }
195 }
196
196
197 try:
197 try:
198 if node:
198 if node:
199 expander.update(node_expander)
199 expander.update(node_expander)
200 if node:
200 if node:
201 expander['r'] = (lambda:
201 expander['r'] = (lambda:
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
203 if total is not None:
203 if total is not None:
204 expander['N'] = lambda: str(total)
204 expander['N'] = lambda: str(total)
205 if seqno is not None:
205 if seqno is not None:
206 expander['n'] = lambda: str(seqno)
206 expander['n'] = lambda: str(seqno)
207 if total is not None and seqno is not None:
207 if total is not None and seqno is not None:
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
209 if pathname is not None:
209 if pathname is not None:
210 expander['s'] = lambda: os.path.basename(pathname)
210 expander['s'] = lambda: os.path.basename(pathname)
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
212 expander['p'] = lambda: pathname
212 expander['p'] = lambda: pathname
213
213
214 newname = []
214 newname = []
215 patlen = len(pat)
215 patlen = len(pat)
216 i = 0
216 i = 0
217 while i < patlen:
217 while i < patlen:
218 c = pat[i]
218 c = pat[i]
219 if c == '%':
219 if c == '%':
220 i += 1
220 i += 1
221 c = pat[i]
221 c = pat[i]
222 c = expander[c]()
222 c = expander[c]()
223 newname.append(c)
223 newname.append(c)
224 i += 1
224 i += 1
225 return ''.join(newname)
225 return ''.join(newname)
226 except KeyError, inst:
226 except KeyError, inst:
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
228 inst.args[0])
228 inst.args[0])
229
229
230 def make_file(repo, pat, node=None,
230 def make_file(repo, pat, node=None,
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
232
232
233 writable = mode not in ('r', 'rb')
233 writable = mode not in ('r', 'rb')
234
234
235 if not pat or pat == '-':
235 if not pat or pat == '-':
236 fp = writable and sys.stdout or sys.stdin
236 fp = writable and sys.stdout or sys.stdin
237 return os.fdopen(os.dup(fp.fileno()), mode)
237 return os.fdopen(os.dup(fp.fileno()), mode)
238 if hasattr(pat, 'write') and writable:
238 if hasattr(pat, 'write') and writable:
239 return pat
239 return pat
240 if hasattr(pat, 'read') and 'r' in mode:
240 if hasattr(pat, 'read') and 'r' in mode:
241 return pat
241 return pat
242 return open(make_filename(repo, pat, node, total, seqno, revwidth,
242 return open(make_filename(repo, pat, node, total, seqno, revwidth,
243 pathname),
243 pathname),
244 mode)
244 mode)
245
245
246 def expandpats(pats):
246 def expandpats(pats):
247 if not util.expandglobs:
247 if not util.expandglobs:
248 return list(pats)
248 return list(pats)
249 ret = []
249 ret = []
250 for p in pats:
250 for p in pats:
251 kind, name = matchmod._patsplit(p, None)
251 kind, name = matchmod._patsplit(p, None)
252 if kind is None:
252 if kind is None:
253 try:
253 try:
254 globbed = glob.glob(name)
254 globbed = glob.glob(name)
255 except re.error:
255 except re.error:
256 globbed = [name]
256 globbed = [name]
257 if globbed:
257 if globbed:
258 ret.extend(globbed)
258 ret.extend(globbed)
259 continue
259 continue
260 ret.append(p)
260 ret.append(p)
261 return ret
261 return ret
262
262
263 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
263 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
264 if pats == ("",):
264 if pats == ("",):
265 pats = []
265 pats = []
266 if not globbed and default == 'relpath':
266 if not globbed and default == 'relpath':
267 pats = expandpats(pats or [])
267 pats = expandpats(pats or [])
268 m = matchmod.match(repo.root, repo.getcwd(), pats,
268 m = matchmod.match(repo.root, repo.getcwd(), pats,
269 opts.get('include'), opts.get('exclude'), default,
269 opts.get('include'), opts.get('exclude'), default,
270 auditor=repo.auditor)
270 auditor=repo.auditor)
271 def badfn(f, msg):
271 def badfn(f, msg):
272 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
272 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
273 m.bad = badfn
273 m.bad = badfn
274 return m
274 return m
275
275
276 def matchall(repo):
276 def matchall(repo):
277 return matchmod.always(repo.root, repo.getcwd())
277 return matchmod.always(repo.root, repo.getcwd())
278
278
279 def matchfiles(repo, files):
279 def matchfiles(repo, files):
280 return matchmod.exact(repo.root, repo.getcwd(), files)
280 return matchmod.exact(repo.root, repo.getcwd(), files)
281
281
282 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
282 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
283 if dry_run is None:
283 if dry_run is None:
284 dry_run = opts.get('dry_run')
284 dry_run = opts.get('dry_run')
285 if similarity is None:
285 if similarity is None:
286 similarity = float(opts.get('similarity') or 0)
286 similarity = float(opts.get('similarity') or 0)
287 # we'd use status here, except handling of symlinks and ignore is tricky
287 # we'd use status here, except handling of symlinks and ignore is tricky
288 added, unknown, deleted, removed = [], [], [], []
288 added, unknown, deleted, removed = [], [], [], []
289 audit_path = util.path_auditor(repo.root)
289 audit_path = util.path_auditor(repo.root)
290 m = match(repo, pats, opts)
290 m = match(repo, pats, opts)
291 for abs in repo.walk(m):
291 for abs in repo.walk(m):
292 target = repo.wjoin(abs)
292 target = repo.wjoin(abs)
293 good = True
293 good = True
294 try:
294 try:
295 audit_path(abs)
295 audit_path(abs)
296 except:
296 except:
297 good = False
297 good = False
298 rel = m.rel(abs)
298 rel = m.rel(abs)
299 exact = m.exact(abs)
299 exact = m.exact(abs)
300 if good and abs not in repo.dirstate:
300 if good and abs not in repo.dirstate:
301 unknown.append(abs)
301 unknown.append(abs)
302 if repo.ui.verbose or not exact:
302 if repo.ui.verbose or not exact:
303 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
303 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
304 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
304 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
305 or (os.path.isdir(target) and not os.path.islink(target))):
305 or (os.path.isdir(target) and not os.path.islink(target))):
306 deleted.append(abs)
306 deleted.append(abs)
307 if repo.ui.verbose or not exact:
307 if repo.ui.verbose or not exact:
308 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
308 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
309 # for finding renames
309 # for finding renames
310 elif repo.dirstate[abs] == 'r':
310 elif repo.dirstate[abs] == 'r':
311 removed.append(abs)
311 removed.append(abs)
312 elif repo.dirstate[abs] == 'a':
312 elif repo.dirstate[abs] == 'a':
313 added.append(abs)
313 added.append(abs)
314 copies = {}
314 copies = {}
315 if similarity > 0:
315 if similarity > 0:
316 for old, new, score in similar.findrenames(repo,
316 for old, new, score in similar.findrenames(repo,
317 added + unknown, removed + deleted, similarity):
317 added + unknown, removed + deleted, similarity):
318 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
318 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
319 repo.ui.status(_('recording removal of %s as rename to %s '
319 repo.ui.status(_('recording removal of %s as rename to %s '
320 '(%d%% similar)\n') %
320 '(%d%% similar)\n') %
321 (m.rel(old), m.rel(new), score * 100))
321 (m.rel(old), m.rel(new), score * 100))
322 copies[new] = old
322 copies[new] = old
323
323
324 if not dry_run:
324 if not dry_run:
325 wctx = repo[None]
325 wctx = repo[None]
326 wlock = repo.wlock()
326 wlock = repo.wlock()
327 try:
327 try:
328 wctx.remove(deleted)
328 wctx.remove(deleted)
329 wctx.add(unknown)
329 wctx.add(unknown)
330 for new, old in copies.iteritems():
330 for new, old in copies.iteritems():
331 wctx.copy(old, new)
331 wctx.copy(old, new)
332 finally:
332 finally:
333 wlock.release()
333 wlock.release()
334
334
335 def updatedir(ui, repo, patches, similarity=0):
335 def updatedir(ui, repo, patches, similarity=0):
336 '''Update dirstate after patch application according to metadata'''
336 '''Update dirstate after patch application according to metadata'''
337 if not patches:
337 if not patches:
338 return
338 return
339 copies = []
339 copies = []
340 removes = set()
340 removes = set()
341 cfiles = patches.keys()
341 cfiles = patches.keys()
342 cwd = repo.getcwd()
342 cwd = repo.getcwd()
343 if cwd:
343 if cwd:
344 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
344 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
345 for f in patches:
345 for f in patches:
346 gp = patches[f]
346 gp = patches[f]
347 if not gp:
347 if not gp:
348 continue
348 continue
349 if gp.op == 'RENAME':
349 if gp.op == 'RENAME':
350 copies.append((gp.oldpath, gp.path))
350 copies.append((gp.oldpath, gp.path))
351 removes.add(gp.oldpath)
351 removes.add(gp.oldpath)
352 elif gp.op == 'COPY':
352 elif gp.op == 'COPY':
353 copies.append((gp.oldpath, gp.path))
353 copies.append((gp.oldpath, gp.path))
354 elif gp.op == 'DELETE':
354 elif gp.op == 'DELETE':
355 removes.add(gp.path)
355 removes.add(gp.path)
356
356
357 wctx = repo[None]
357 wctx = repo[None]
358 for src, dst in copies:
358 for src, dst in copies:
359 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
359 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
360 if (not similarity) and removes:
360 if (not similarity) and removes:
361 wctx.remove(sorted(removes), True)
361 wctx.remove(sorted(removes), True)
362
362
363 for f in patches:
363 for f in patches:
364 gp = patches[f]
364 gp = patches[f]
365 if gp and gp.mode:
365 if gp and gp.mode:
366 islink, isexec = gp.mode
366 islink, isexec = gp.mode
367 dst = repo.wjoin(gp.path)
367 dst = repo.wjoin(gp.path)
368 # patch won't create empty files
368 # patch won't create empty files
369 if gp.op == 'ADD' and not os.path.lexists(dst):
369 if gp.op == 'ADD' and not os.path.lexists(dst):
370 flags = (isexec and 'x' or '') + (islink and 'l' or '')
370 flags = (isexec and 'x' or '') + (islink and 'l' or '')
371 repo.wwrite(gp.path, '', flags)
371 repo.wwrite(gp.path, '', flags)
372 util.set_flags(dst, islink, isexec)
372 util.set_flags(dst, islink, isexec)
373 addremove(repo, cfiles, similarity=similarity)
373 addremove(repo, cfiles, similarity=similarity)
374 files = patches.keys()
374 files = patches.keys()
375 files.extend([r for r in removes if r not in files])
375 files.extend([r for r in removes if r not in files])
376 return sorted(files)
376 return sorted(files)
377
377
378 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
378 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
379 """Update the dirstate to reflect the intent of copying src to dst. For
379 """Update the dirstate to reflect the intent of copying src to dst. For
380 different reasons it might not end with dst being marked as copied from src.
380 different reasons it might not end with dst being marked as copied from src.
381 """
381 """
382 origsrc = repo.dirstate.copied(src) or src
382 origsrc = repo.dirstate.copied(src) or src
383 if dst == origsrc: # copying back a copy?
383 if dst == origsrc: # copying back a copy?
384 if repo.dirstate[dst] not in 'mn' and not dryrun:
384 if repo.dirstate[dst] not in 'mn' and not dryrun:
385 repo.dirstate.normallookup(dst)
385 repo.dirstate.normallookup(dst)
386 else:
386 else:
387 if repo.dirstate[origsrc] == 'a' and origsrc == src:
387 if repo.dirstate[origsrc] == 'a' and origsrc == src:
388 if not ui.quiet:
388 if not ui.quiet:
389 ui.warn(_("%s has not been committed yet, so no copy "
389 ui.warn(_("%s has not been committed yet, so no copy "
390 "data will be stored for %s.\n")
390 "data will be stored for %s.\n")
391 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
391 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
392 if repo.dirstate[dst] in '?r' and not dryrun:
392 if repo.dirstate[dst] in '?r' and not dryrun:
393 wctx.add([dst])
393 wctx.add([dst])
394 elif not dryrun:
394 elif not dryrun:
395 wctx.copy(origsrc, dst)
395 wctx.copy(origsrc, dst)
396
396
397 def copy(ui, repo, pats, opts, rename=False):
397 def copy(ui, repo, pats, opts, rename=False):
398 # called with the repo lock held
398 # called with the repo lock held
399 #
399 #
400 # hgsep => pathname that uses "/" to separate directories
400 # hgsep => pathname that uses "/" to separate directories
401 # ossep => pathname that uses os.sep to separate directories
401 # ossep => pathname that uses os.sep to separate directories
402 cwd = repo.getcwd()
402 cwd = repo.getcwd()
403 targets = {}
403 targets = {}
404 after = opts.get("after")
404 after = opts.get("after")
405 dryrun = opts.get("dry_run")
405 dryrun = opts.get("dry_run")
406 wctx = repo[None]
406 wctx = repo[None]
407
407
408 def walkpat(pat):
408 def walkpat(pat):
409 srcs = []
409 srcs = []
410 badstates = after and '?' or '?r'
410 badstates = after and '?' or '?r'
411 m = match(repo, [pat], opts, globbed=True)
411 m = match(repo, [pat], opts, globbed=True)
412 for abs in repo.walk(m):
412 for abs in repo.walk(m):
413 state = repo.dirstate[abs]
413 state = repo.dirstate[abs]
414 rel = m.rel(abs)
414 rel = m.rel(abs)
415 exact = m.exact(abs)
415 exact = m.exact(abs)
416 if state in badstates:
416 if state in badstates:
417 if exact and state == '?':
417 if exact and state == '?':
418 ui.warn(_('%s: not copying - file is not managed\n') % rel)
418 ui.warn(_('%s: not copying - file is not managed\n') % rel)
419 if exact and state == 'r':
419 if exact and state == 'r':
420 ui.warn(_('%s: not copying - file has been marked for'
420 ui.warn(_('%s: not copying - file has been marked for'
421 ' remove\n') % rel)
421 ' remove\n') % rel)
422 continue
422 continue
423 # abs: hgsep
423 # abs: hgsep
424 # rel: ossep
424 # rel: ossep
425 srcs.append((abs, rel, exact))
425 srcs.append((abs, rel, exact))
426 return srcs
426 return srcs
427
427
428 # abssrc: hgsep
428 # abssrc: hgsep
429 # relsrc: ossep
429 # relsrc: ossep
430 # otarget: ossep
430 # otarget: ossep
431 def copyfile(abssrc, relsrc, otarget, exact):
431 def copyfile(abssrc, relsrc, otarget, exact):
432 abstarget = util.canonpath(repo.root, cwd, otarget)
432 abstarget = util.canonpath(repo.root, cwd, otarget)
433 reltarget = repo.pathto(abstarget, cwd)
433 reltarget = repo.pathto(abstarget, cwd)
434 target = repo.wjoin(abstarget)
434 target = repo.wjoin(abstarget)
435 src = repo.wjoin(abssrc)
435 src = repo.wjoin(abssrc)
436 state = repo.dirstate[abstarget]
436 state = repo.dirstate[abstarget]
437
437
438 # check for collisions
438 # check for collisions
439 prevsrc = targets.get(abstarget)
439 prevsrc = targets.get(abstarget)
440 if prevsrc is not None:
440 if prevsrc is not None:
441 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
441 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
442 (reltarget, repo.pathto(abssrc, cwd),
442 (reltarget, repo.pathto(abssrc, cwd),
443 repo.pathto(prevsrc, cwd)))
443 repo.pathto(prevsrc, cwd)))
444 return
444 return
445
445
446 # check for overwrites
446 # check for overwrites
447 exists = os.path.lexists(target)
447 exists = os.path.lexists(target)
448 if not after and exists or after and state in 'mn':
448 if not after and exists or after and state in 'mn':
449 if not opts['force']:
449 if not opts['force']:
450 ui.warn(_('%s: not overwriting - file exists\n') %
450 ui.warn(_('%s: not overwriting - file exists\n') %
451 reltarget)
451 reltarget)
452 return
452 return
453
453
454 if after:
454 if after:
455 if not exists:
455 if not exists:
456 if rename:
456 if rename:
457 ui.warn(_('%s: not recording move - %s does not exist\n') %
457 ui.warn(_('%s: not recording move - %s does not exist\n') %
458 (relsrc, reltarget))
458 (relsrc, reltarget))
459 else:
459 else:
460 ui.warn(_('%s: not recording copy - %s does not exist\n') %
460 ui.warn(_('%s: not recording copy - %s does not exist\n') %
461 (relsrc, reltarget))
461 (relsrc, reltarget))
462 return
462 return
463 elif not dryrun:
463 elif not dryrun:
464 try:
464 try:
465 if exists:
465 if exists:
466 os.unlink(target)
466 os.unlink(target)
467 targetdir = os.path.dirname(target) or '.'
467 targetdir = os.path.dirname(target) or '.'
468 if not os.path.isdir(targetdir):
468 if not os.path.isdir(targetdir):
469 os.makedirs(targetdir)
469 os.makedirs(targetdir)
470 util.copyfile(src, target)
470 util.copyfile(src, target)
471 except IOError, inst:
471 except IOError, inst:
472 if inst.errno == errno.ENOENT:
472 if inst.errno == errno.ENOENT:
473 ui.warn(_('%s: deleted in working copy\n') % relsrc)
473 ui.warn(_('%s: deleted in working copy\n') % relsrc)
474 else:
474 else:
475 ui.warn(_('%s: cannot copy - %s\n') %
475 ui.warn(_('%s: cannot copy - %s\n') %
476 (relsrc, inst.strerror))
476 (relsrc, inst.strerror))
477 return True # report a failure
477 return True # report a failure
478
478
479 if ui.verbose or not exact:
479 if ui.verbose or not exact:
480 if rename:
480 if rename:
481 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
481 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
482 else:
482 else:
483 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
483 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
484
484
485 targets[abstarget] = abssrc
485 targets[abstarget] = abssrc
486
486
487 # fix up dirstate
487 # fix up dirstate
488 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
488 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
489 if rename and not dryrun:
489 if rename and not dryrun:
490 wctx.remove([abssrc], not after)
490 wctx.remove([abssrc], not after)
491
491
492 # pat: ossep
492 # pat: ossep
493 # dest ossep
493 # dest ossep
494 # srcs: list of (hgsep, hgsep, ossep, bool)
494 # srcs: list of (hgsep, hgsep, ossep, bool)
495 # return: function that takes hgsep and returns ossep
495 # return: function that takes hgsep and returns ossep
496 def targetpathfn(pat, dest, srcs):
496 def targetpathfn(pat, dest, srcs):
497 if os.path.isdir(pat):
497 if os.path.isdir(pat):
498 abspfx = util.canonpath(repo.root, cwd, pat)
498 abspfx = util.canonpath(repo.root, cwd, pat)
499 abspfx = util.localpath(abspfx)
499 abspfx = util.localpath(abspfx)
500 if destdirexists:
500 if destdirexists:
501 striplen = len(os.path.split(abspfx)[0])
501 striplen = len(os.path.split(abspfx)[0])
502 else:
502 else:
503 striplen = len(abspfx)
503 striplen = len(abspfx)
504 if striplen:
504 if striplen:
505 striplen += len(os.sep)
505 striplen += len(os.sep)
506 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
506 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
507 elif destdirexists:
507 elif destdirexists:
508 res = lambda p: os.path.join(dest,
508 res = lambda p: os.path.join(dest,
509 os.path.basename(util.localpath(p)))
509 os.path.basename(util.localpath(p)))
510 else:
510 else:
511 res = lambda p: dest
511 res = lambda p: dest
512 return res
512 return res
513
513
514 # pat: ossep
514 # pat: ossep
515 # dest ossep
515 # dest ossep
516 # srcs: list of (hgsep, hgsep, ossep, bool)
516 # srcs: list of (hgsep, hgsep, ossep, bool)
517 # return: function that takes hgsep and returns ossep
517 # return: function that takes hgsep and returns ossep
518 def targetpathafterfn(pat, dest, srcs):
518 def targetpathafterfn(pat, dest, srcs):
519 if matchmod.patkind(pat):
519 if matchmod.patkind(pat):
520 # a mercurial pattern
520 # a mercurial pattern
521 res = lambda p: os.path.join(dest,
521 res = lambda p: os.path.join(dest,
522 os.path.basename(util.localpath(p)))
522 os.path.basename(util.localpath(p)))
523 else:
523 else:
524 abspfx = util.canonpath(repo.root, cwd, pat)
524 abspfx = util.canonpath(repo.root, cwd, pat)
525 if len(abspfx) < len(srcs[0][0]):
525 if len(abspfx) < len(srcs[0][0]):
526 # A directory. Either the target path contains the last
526 # A directory. Either the target path contains the last
527 # component of the source path or it does not.
527 # component of the source path or it does not.
528 def evalpath(striplen):
528 def evalpath(striplen):
529 score = 0
529 score = 0
530 for s in srcs:
530 for s in srcs:
531 t = os.path.join(dest, util.localpath(s[0])[striplen:])
531 t = os.path.join(dest, util.localpath(s[0])[striplen:])
532 if os.path.lexists(t):
532 if os.path.lexists(t):
533 score += 1
533 score += 1
534 return score
534 return score
535
535
536 abspfx = util.localpath(abspfx)
536 abspfx = util.localpath(abspfx)
537 striplen = len(abspfx)
537 striplen = len(abspfx)
538 if striplen:
538 if striplen:
539 striplen += len(os.sep)
539 striplen += len(os.sep)
540 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
540 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
541 score = evalpath(striplen)
541 score = evalpath(striplen)
542 striplen1 = len(os.path.split(abspfx)[0])
542 striplen1 = len(os.path.split(abspfx)[0])
543 if striplen1:
543 if striplen1:
544 striplen1 += len(os.sep)
544 striplen1 += len(os.sep)
545 if evalpath(striplen1) > score:
545 if evalpath(striplen1) > score:
546 striplen = striplen1
546 striplen = striplen1
547 res = lambda p: os.path.join(dest,
547 res = lambda p: os.path.join(dest,
548 util.localpath(p)[striplen:])
548 util.localpath(p)[striplen:])
549 else:
549 else:
550 # a file
550 # a file
551 if destdirexists:
551 if destdirexists:
552 res = lambda p: os.path.join(dest,
552 res = lambda p: os.path.join(dest,
553 os.path.basename(util.localpath(p)))
553 os.path.basename(util.localpath(p)))
554 else:
554 else:
555 res = lambda p: dest
555 res = lambda p: dest
556 return res
556 return res
557
557
558
558
559 pats = expandpats(pats)
559 pats = expandpats(pats)
560 if not pats:
560 if not pats:
561 raise util.Abort(_('no source or destination specified'))
561 raise util.Abort(_('no source or destination specified'))
562 if len(pats) == 1:
562 if len(pats) == 1:
563 raise util.Abort(_('no destination specified'))
563 raise util.Abort(_('no destination specified'))
564 dest = pats.pop()
564 dest = pats.pop()
565 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
565 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
566 if not destdirexists:
566 if not destdirexists:
567 if len(pats) > 1 or matchmod.patkind(pats[0]):
567 if len(pats) > 1 or matchmod.patkind(pats[0]):
568 raise util.Abort(_('with multiple sources, destination must be an '
568 raise util.Abort(_('with multiple sources, destination must be an '
569 'existing directory'))
569 'existing directory'))
570 if util.endswithsep(dest):
570 if util.endswithsep(dest):
571 raise util.Abort(_('destination %s is not a directory') % dest)
571 raise util.Abort(_('destination %s is not a directory') % dest)
572
572
573 tfn = targetpathfn
573 tfn = targetpathfn
574 if after:
574 if after:
575 tfn = targetpathafterfn
575 tfn = targetpathafterfn
576 copylist = []
576 copylist = []
577 for pat in pats:
577 for pat in pats:
578 srcs = walkpat(pat)
578 srcs = walkpat(pat)
579 if not srcs:
579 if not srcs:
580 continue
580 continue
581 copylist.append((tfn(pat, dest, srcs), srcs))
581 copylist.append((tfn(pat, dest, srcs), srcs))
582 if not copylist:
582 if not copylist:
583 raise util.Abort(_('no files to copy'))
583 raise util.Abort(_('no files to copy'))
584
584
585 errors = 0
585 errors = 0
586 for targetpath, srcs in copylist:
586 for targetpath, srcs in copylist:
587 for abssrc, relsrc, exact in srcs:
587 for abssrc, relsrc, exact in srcs:
588 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
588 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
589 errors += 1
589 errors += 1
590
590
591 if errors:
591 if errors:
592 ui.warn(_('(consider using --after)\n'))
592 ui.warn(_('(consider using --after)\n'))
593
593
594 return errors != 0
594 return errors != 0
595
595
596 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
596 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
597 runargs=None, appendpid=False):
597 runargs=None, appendpid=False):
598 '''Run a command as a service.'''
598 '''Run a command as a service.'''
599
599
600 if opts['daemon'] and not opts['daemon_pipefds']:
600 if opts['daemon'] and not opts['daemon_pipefds']:
601 # Signal child process startup with file removal
601 # Signal child process startup with file removal
602 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
602 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
603 os.close(lockfd)
603 os.close(lockfd)
604 try:
604 try:
605 if not runargs:
605 if not runargs:
606 runargs = util.hgcmd() + sys.argv[1:]
606 runargs = util.hgcmd() + sys.argv[1:]
607 runargs.append('--daemon-pipefds=%s' % lockpath)
607 runargs.append('--daemon-pipefds=%s' % lockpath)
608 # Don't pass --cwd to the child process, because we've already
608 # Don't pass --cwd to the child process, because we've already
609 # changed directory.
609 # changed directory.
610 for i in xrange(1, len(runargs)):
610 for i in xrange(1, len(runargs)):
611 if runargs[i].startswith('--cwd='):
611 if runargs[i].startswith('--cwd='):
612 del runargs[i]
612 del runargs[i]
613 break
613 break
614 elif runargs[i].startswith('--cwd'):
614 elif runargs[i].startswith('--cwd'):
615 del runargs[i:i + 2]
615 del runargs[i:i + 2]
616 break
616 break
617 def condfn():
617 def condfn():
618 return not os.path.exists(lockpath)
618 return not os.path.exists(lockpath)
619 pid = util.rundetached(runargs, condfn)
619 pid = util.rundetached(runargs, condfn)
620 if pid < 0:
620 if pid < 0:
621 raise util.Abort(_('child process failed to start'))
621 raise util.Abort(_('child process failed to start'))
622 finally:
622 finally:
623 try:
623 try:
624 os.unlink(lockpath)
624 os.unlink(lockpath)
625 except OSError, e:
625 except OSError, e:
626 if e.errno != errno.ENOENT:
626 if e.errno != errno.ENOENT:
627 raise
627 raise
628 if parentfn:
628 if parentfn:
629 return parentfn(pid)
629 return parentfn(pid)
630 else:
630 else:
631 return
631 return
632
632
633 if initfn:
633 if initfn:
634 initfn()
634 initfn()
635
635
636 if opts['pid_file']:
636 if opts['pid_file']:
637 mode = appendpid and 'a' or 'w'
637 mode = appendpid and 'a' or 'w'
638 fp = open(opts['pid_file'], mode)
638 fp = open(opts['pid_file'], mode)
639 fp.write(str(os.getpid()) + '\n')
639 fp.write(str(os.getpid()) + '\n')
640 fp.close()
640 fp.close()
641
641
642 if opts['daemon_pipefds']:
642 if opts['daemon_pipefds']:
643 lockpath = opts['daemon_pipefds']
643 lockpath = opts['daemon_pipefds']
644 try:
644 try:
645 os.setsid()
645 os.setsid()
646 except AttributeError:
646 except AttributeError:
647 pass
647 pass
648 os.unlink(lockpath)
648 os.unlink(lockpath)
649 util.hidewindow()
649 util.hidewindow()
650 sys.stdout.flush()
650 sys.stdout.flush()
651 sys.stderr.flush()
651 sys.stderr.flush()
652
652
653 nullfd = os.open(util.nulldev, os.O_RDWR)
653 nullfd = os.open(util.nulldev, os.O_RDWR)
654 logfilefd = nullfd
654 logfilefd = nullfd
655 if logfile:
655 if logfile:
656 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
656 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
657 os.dup2(nullfd, 0)
657 os.dup2(nullfd, 0)
658 os.dup2(logfilefd, 1)
658 os.dup2(logfilefd, 1)
659 os.dup2(logfilefd, 2)
659 os.dup2(logfilefd, 2)
660 if nullfd not in (0, 1, 2):
660 if nullfd not in (0, 1, 2):
661 os.close(nullfd)
661 os.close(nullfd)
662 if logfile and logfilefd not in (0, 1, 2):
662 if logfile and logfilefd not in (0, 1, 2):
663 os.close(logfilefd)
663 os.close(logfilefd)
664
664
665 if runfn:
665 if runfn:
666 return runfn()
666 return runfn()
667
667
668 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
668 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
669 opts=None):
669 opts=None):
670 '''export changesets as hg patches.'''
670 '''export changesets as hg patches.'''
671
671
672 total = len(revs)
672 total = len(revs)
673 revwidth = max([len(str(rev)) for rev in revs])
673 revwidth = max([len(str(rev)) for rev in revs])
674
674
675 def single(rev, seqno, fp):
675 def single(rev, seqno, fp):
676 ctx = repo[rev]
676 ctx = repo[rev]
677 node = ctx.node()
677 node = ctx.node()
678 parents = [p.node() for p in ctx.parents() if p]
678 parents = [p.node() for p in ctx.parents() if p]
679 branch = ctx.branch()
679 branch = ctx.branch()
680 if switch_parent:
680 if switch_parent:
681 parents.reverse()
681 parents.reverse()
682 prev = (parents and parents[0]) or nullid
682 prev = (parents and parents[0]) or nullid
683
683
684 shouldclose = False
684 shouldclose = False
685 if not fp:
685 if not fp:
686 fp = make_file(repo, template, node, total=total, seqno=seqno,
686 fp = make_file(repo, template, node, total=total, seqno=seqno,
687 revwidth=revwidth, mode='ab')
687 revwidth=revwidth, mode='ab')
688 if fp != template:
688 if fp != template:
689 shouldclose = True
689 shouldclose = True
690 if fp != sys.stdout and hasattr(fp, 'name'):
690 if fp != sys.stdout and hasattr(fp, 'name'):
691 repo.ui.note("%s\n" % fp.name)
691 repo.ui.note("%s\n" % fp.name)
692
692
693 fp.write("# HG changeset patch\n")
693 fp.write("# HG changeset patch\n")
694 fp.write("# User %s\n" % ctx.user())
694 fp.write("# User %s\n" % ctx.user())
695 fp.write("# Date %d %d\n" % ctx.date())
695 fp.write("# Date %d %d\n" % ctx.date())
696 if branch and branch != 'default':
696 if branch and branch != 'default':
697 fp.write("# Branch %s\n" % branch)
697 fp.write("# Branch %s\n" % branch)
698 fp.write("# Node ID %s\n" % hex(node))
698 fp.write("# Node ID %s\n" % hex(node))
699 fp.write("# Parent %s\n" % hex(prev))
699 fp.write("# Parent %s\n" % hex(prev))
700 if len(parents) > 1:
700 if len(parents) > 1:
701 fp.write("# Parent %s\n" % hex(parents[1]))
701 fp.write("# Parent %s\n" % hex(parents[1]))
702 fp.write(ctx.description().rstrip())
702 fp.write(ctx.description().rstrip())
703 fp.write("\n\n")
703 fp.write("\n\n")
704
704
705 for chunk in patch.diff(repo, prev, node, opts=opts):
705 for chunk in patch.diff(repo, prev, node, opts=opts):
706 fp.write(chunk)
706 fp.write(chunk)
707
707
708 if shouldclose:
708 if shouldclose:
709 fp.close()
709 fp.close()
710
710
711 for seqno, rev in enumerate(revs):
711 for seqno, rev in enumerate(revs):
712 single(rev, seqno + 1, fp)
712 single(rev, seqno + 1, fp)
713
713
714 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
714 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
715 changes=None, stat=False, fp=None, prefix='',
715 changes=None, stat=False, fp=None, prefix='',
716 listsubrepos=False):
716 listsubrepos=False):
717 '''show diff or diffstat.'''
717 '''show diff or diffstat.'''
718 if fp is None:
718 if fp is None:
719 write = ui.write
719 write = ui.write
720 else:
720 else:
721 def write(s, **kw):
721 def write(s, **kw):
722 fp.write(s)
722 fp.write(s)
723
723
724 if stat:
724 if stat:
725 diffopts = diffopts.copy(context=0)
725 diffopts = diffopts.copy(context=0)
726 width = 80
726 width = 80
727 if not ui.plain():
727 if not ui.plain():
728 width = ui.termwidth()
728 width = ui.termwidth()
729 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
729 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
730 prefix=prefix)
730 prefix=prefix)
731 for chunk, label in patch.diffstatui(util.iterlines(chunks),
731 for chunk, label in patch.diffstatui(util.iterlines(chunks),
732 width=width,
732 width=width,
733 git=diffopts.git):
733 git=diffopts.git):
734 write(chunk, label=label)
734 write(chunk, label=label)
735 else:
735 else:
736 for chunk, label in patch.diffui(repo, node1, node2, match,
736 for chunk, label in patch.diffui(repo, node1, node2, match,
737 changes, diffopts, prefix=prefix):
737 changes, diffopts, prefix=prefix):
738 write(chunk, label=label)
738 write(chunk, label=label)
739
739
740 if listsubrepos:
740 if listsubrepos:
741 ctx1 = repo[node1]
741 ctx1 = repo[node1]
742 ctx2 = repo[node2]
742 ctx2 = repo[node2]
743 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
743 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
744 if node2 is not None:
744 if node2 is not None:
745 node2 = ctx2.substate[subpath][1]
745 node2 = ctx2.substate[subpath][1]
746 submatch = matchmod.narrowmatcher(subpath, match)
746 submatch = matchmod.narrowmatcher(subpath, match)
747 sub.diff(diffopts, node2, submatch, changes=changes,
747 sub.diff(diffopts, node2, submatch, changes=changes,
748 stat=stat, fp=fp, prefix=prefix)
748 stat=stat, fp=fp, prefix=prefix)
749
749
750 class changeset_printer(object):
750 class changeset_printer(object):
751 '''show changeset information when templating not requested.'''
751 '''show changeset information when templating not requested.'''
752
752
753 def __init__(self, ui, repo, patch, diffopts, buffered):
753 def __init__(self, ui, repo, patch, diffopts, buffered):
754 self.ui = ui
754 self.ui = ui
755 self.repo = repo
755 self.repo = repo
756 self.buffered = buffered
756 self.buffered = buffered
757 self.patch = patch
757 self.patch = patch
758 self.diffopts = diffopts
758 self.diffopts = diffopts
759 self.header = {}
759 self.header = {}
760 self.hunk = {}
760 self.hunk = {}
761 self.lastheader = None
761 self.lastheader = None
762 self.footer = None
762 self.footer = None
763
763
764 def flush(self, rev):
764 def flush(self, rev):
765 if rev in self.header:
765 if rev in self.header:
766 h = self.header[rev]
766 h = self.header[rev]
767 if h != self.lastheader:
767 if h != self.lastheader:
768 self.lastheader = h
768 self.lastheader = h
769 self.ui.write(h)
769 self.ui.write(h)
770 del self.header[rev]
770 del self.header[rev]
771 if rev in self.hunk:
771 if rev in self.hunk:
772 self.ui.write(self.hunk[rev])
772 self.ui.write(self.hunk[rev])
773 del self.hunk[rev]
773 del self.hunk[rev]
774 return 1
774 return 1
775 return 0
775 return 0
776
776
777 def close(self):
777 def close(self):
778 if self.footer:
778 if self.footer:
779 self.ui.write(self.footer)
779 self.ui.write(self.footer)
780
780
781 def show(self, ctx, copies=None, matchfn=None, **props):
781 def show(self, ctx, copies=None, matchfn=None, **props):
782 if self.buffered:
782 if self.buffered:
783 self.ui.pushbuffer()
783 self.ui.pushbuffer()
784 self._show(ctx, copies, matchfn, props)
784 self._show(ctx, copies, matchfn, props)
785 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
785 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
786 else:
786 else:
787 self._show(ctx, copies, matchfn, props)
787 self._show(ctx, copies, matchfn, props)
788
788
789 def _show(self, ctx, copies, matchfn, props):
789 def _show(self, ctx, copies, matchfn, props):
790 '''show a single changeset or file revision'''
790 '''show a single changeset or file revision'''
791 changenode = ctx.node()
791 changenode = ctx.node()
792 rev = ctx.rev()
792 rev = ctx.rev()
793
793
794 if self.ui.quiet:
794 if self.ui.quiet:
795 self.ui.write("%d:%s\n" % (rev, short(changenode)),
795 self.ui.write("%d:%s\n" % (rev, short(changenode)),
796 label='log.node')
796 label='log.node')
797 return
797 return
798
798
799 log = self.repo.changelog
799 log = self.repo.changelog
800 date = util.datestr(ctx.date())
800 date = util.datestr(ctx.date())
801
801
802 hexfunc = self.ui.debugflag and hex or short
802 hexfunc = self.ui.debugflag and hex or short
803
803
804 parents = [(p, hexfunc(log.node(p)))
804 parents = [(p, hexfunc(log.node(p)))
805 for p in self._meaningful_parentrevs(log, rev)]
805 for p in self._meaningful_parentrevs(log, rev)]
806
806
807 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
807 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
808 label='log.changeset')
808 label='log.changeset')
809
809
810 branch = ctx.branch()
810 branch = ctx.branch()
811 # don't show the default branch name
811 # don't show the default branch name
812 if branch != 'default':
812 if branch != 'default':
813 self.ui.write(_("branch: %s\n") % branch,
813 self.ui.write(_("branch: %s\n") % branch,
814 label='log.branch')
814 label='log.branch')
815 for bookmark in self.repo.nodebookmarks(changenode):
815 for bookmark in self.repo.nodebookmarks(changenode):
816 self.ui.write(_("bookmark: %s\n") % bookmark,
816 self.ui.write(_("bookmark: %s\n") % bookmark,
817 label='log.bookmark')
817 label='log.bookmark')
818 for tag in self.repo.nodetags(changenode):
818 for tag in self.repo.nodetags(changenode):
819 self.ui.write(_("tag: %s\n") % tag,
819 self.ui.write(_("tag: %s\n") % tag,
820 label='log.tag')
820 label='log.tag')
821 for parent in parents:
821 for parent in parents:
822 self.ui.write(_("parent: %d:%s\n") % parent,
822 self.ui.write(_("parent: %d:%s\n") % parent,
823 label='log.parent')
823 label='log.parent')
824
824
825 if self.ui.debugflag:
825 if self.ui.debugflag:
826 mnode = ctx.manifestnode()
826 mnode = ctx.manifestnode()
827 self.ui.write(_("manifest: %d:%s\n") %
827 self.ui.write(_("manifest: %d:%s\n") %
828 (self.repo.manifest.rev(mnode), hex(mnode)),
828 (self.repo.manifest.rev(mnode), hex(mnode)),
829 label='ui.debug log.manifest')
829 label='ui.debug log.manifest')
830 self.ui.write(_("user: %s\n") % ctx.user(),
830 self.ui.write(_("user: %s\n") % ctx.user(),
831 label='log.user')
831 label='log.user')
832 self.ui.write(_("date: %s\n") % date,
832 self.ui.write(_("date: %s\n") % date,
833 label='log.date')
833 label='log.date')
834
834
835 if self.ui.debugflag:
835 if self.ui.debugflag:
836 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
836 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
837 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
837 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
838 files):
838 files):
839 if value:
839 if value:
840 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
840 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
841 label='ui.debug log.files')
841 label='ui.debug log.files')
842 elif ctx.files() and self.ui.verbose:
842 elif ctx.files() and self.ui.verbose:
843 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
843 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
844 label='ui.note log.files')
844 label='ui.note log.files')
845 if copies and self.ui.verbose:
845 if copies and self.ui.verbose:
846 copies = ['%s (%s)' % c for c in copies]
846 copies = ['%s (%s)' % c for c in copies]
847 self.ui.write(_("copies: %s\n") % ' '.join(copies),
847 self.ui.write(_("copies: %s\n") % ' '.join(copies),
848 label='ui.note log.copies')
848 label='ui.note log.copies')
849
849
850 extra = ctx.extra()
850 extra = ctx.extra()
851 if extra and self.ui.debugflag:
851 if extra and self.ui.debugflag:
852 for key, value in sorted(extra.items()):
852 for key, value in sorted(extra.items()):
853 self.ui.write(_("extra: %s=%s\n")
853 self.ui.write(_("extra: %s=%s\n")
854 % (key, value.encode('string_escape')),
854 % (key, value.encode('string_escape')),
855 label='ui.debug log.extra')
855 label='ui.debug log.extra')
856
856
857 description = ctx.description().strip()
857 description = ctx.description().strip()
858 if description:
858 if description:
859 if self.ui.verbose:
859 if self.ui.verbose:
860 self.ui.write(_("description:\n"),
860 self.ui.write(_("description:\n"),
861 label='ui.note log.description')
861 label='ui.note log.description')
862 self.ui.write(description,
862 self.ui.write(description,
863 label='ui.note log.description')
863 label='ui.note log.description')
864 self.ui.write("\n\n")
864 self.ui.write("\n\n")
865 else:
865 else:
866 self.ui.write(_("summary: %s\n") %
866 self.ui.write(_("summary: %s\n") %
867 description.splitlines()[0],
867 description.splitlines()[0],
868 label='log.summary')
868 label='log.summary')
869 self.ui.write("\n")
869 self.ui.write("\n")
870
870
871 self.showpatch(changenode, matchfn)
871 self.showpatch(changenode, matchfn)
872
872
873 def showpatch(self, node, matchfn):
873 def showpatch(self, node, matchfn):
874 if not matchfn:
874 if not matchfn:
875 matchfn = self.patch
875 matchfn = self.patch
876 if matchfn:
876 if matchfn:
877 stat = self.diffopts.get('stat')
877 stat = self.diffopts.get('stat')
878 diff = self.diffopts.get('patch')
878 diff = self.diffopts.get('patch')
879 diffopts = patch.diffopts(self.ui, self.diffopts)
879 diffopts = patch.diffopts(self.ui, self.diffopts)
880 prev = self.repo.changelog.parents(node)[0]
880 prev = self.repo.changelog.parents(node)[0]
881 if stat:
881 if stat:
882 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
882 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
883 match=matchfn, stat=True)
883 match=matchfn, stat=True)
884 if diff:
884 if diff:
885 if stat:
885 if stat:
886 self.ui.write("\n")
886 self.ui.write("\n")
887 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
887 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
888 match=matchfn, stat=False)
888 match=matchfn, stat=False)
889 self.ui.write("\n")
889 self.ui.write("\n")
890
890
891 def _meaningful_parentrevs(self, log, rev):
891 def _meaningful_parentrevs(self, log, rev):
892 """Return list of meaningful (or all if debug) parentrevs for rev.
892 """Return list of meaningful (or all if debug) parentrevs for rev.
893
893
894 For merges (two non-nullrev revisions) both parents are meaningful.
894 For merges (two non-nullrev revisions) both parents are meaningful.
895 Otherwise the first parent revision is considered meaningful if it
895 Otherwise the first parent revision is considered meaningful if it
896 is not the preceding revision.
896 is not the preceding revision.
897 """
897 """
898 parents = log.parentrevs(rev)
898 parents = log.parentrevs(rev)
899 if not self.ui.debugflag and parents[1] == nullrev:
899 if not self.ui.debugflag and parents[1] == nullrev:
900 if parents[0] >= rev - 1:
900 if parents[0] >= rev - 1:
901 parents = []
901 parents = []
902 else:
902 else:
903 parents = [parents[0]]
903 parents = [parents[0]]
904 return parents
904 return parents
905
905
906
906
907 class changeset_templater(changeset_printer):
907 class changeset_templater(changeset_printer):
908 '''format changeset information.'''
908 '''format changeset information.'''
909
909
910 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
910 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
911 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
911 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
912 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
912 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
913 defaulttempl = {
913 defaulttempl = {
914 'parent': '{rev}:{node|formatnode} ',
914 'parent': '{rev}:{node|formatnode} ',
915 'manifest': '{rev}:{node|formatnode}',
915 'manifest': '{rev}:{node|formatnode}',
916 'file_copy': '{name} ({source})',
916 'file_copy': '{name} ({source})',
917 'extra': '{key}={value|stringescape}'
917 'extra': '{key}={value|stringescape}'
918 }
918 }
919 # filecopy is preserved for compatibility reasons
919 # filecopy is preserved for compatibility reasons
920 defaulttempl['filecopy'] = defaulttempl['file_copy']
920 defaulttempl['filecopy'] = defaulttempl['file_copy']
921 self.t = templater.templater(mapfile, {'formatnode': formatnode},
921 self.t = templater.templater(mapfile, {'formatnode': formatnode},
922 cache=defaulttempl)
922 cache=defaulttempl)
923 self.cache = {}
923 self.cache = {}
924
924
925 def use_template(self, t):
925 def use_template(self, t):
926 '''set template string to use'''
926 '''set template string to use'''
927 self.t.cache['changeset'] = t
927 self.t.cache['changeset'] = t
928
928
929 def _meaningful_parentrevs(self, ctx):
929 def _meaningful_parentrevs(self, ctx):
930 """Return list of meaningful (or all if debug) parentrevs for rev.
930 """Return list of meaningful (or all if debug) parentrevs for rev.
931 """
931 """
932 parents = ctx.parents()
932 parents = ctx.parents()
933 if len(parents) > 1:
933 if len(parents) > 1:
934 return parents
934 return parents
935 if self.ui.debugflag:
935 if self.ui.debugflag:
936 return [parents[0], self.repo['null']]
936 return [parents[0], self.repo['null']]
937 if parents[0].rev() >= ctx.rev() - 1:
937 if parents[0].rev() >= ctx.rev() - 1:
938 return []
938 return []
939 return parents
939 return parents
940
940
941 def _show(self, ctx, copies, matchfn, props):
941 def _show(self, ctx, copies, matchfn, props):
942 '''show a single changeset or file revision'''
942 '''show a single changeset or file revision'''
943
943
944 showlist = templatekw.showlist
944 showlist = templatekw.showlist
945
945
946 # showparents() behaviour depends on ui trace level which
946 # showparents() behaviour depends on ui trace level which
947 # causes unexpected behaviours at templating level and makes
947 # causes unexpected behaviours at templating level and makes
948 # it harder to extract it in a standalone function. Its
948 # it harder to extract it in a standalone function. Its
949 # behaviour cannot be changed so leave it here for now.
949 # behaviour cannot be changed so leave it here for now.
950 def showparents(**args):
950 def showparents(**args):
951 ctx = args['ctx']
951 ctx = args['ctx']
952 parents = [[('rev', p.rev()), ('node', p.hex())]
952 parents = [[('rev', p.rev()), ('node', p.hex())]
953 for p in self._meaningful_parentrevs(ctx)]
953 for p in self._meaningful_parentrevs(ctx)]
954 return showlist('parent', parents, **args)
954 return showlist('parent', parents, **args)
955
955
956 props = props.copy()
956 props = props.copy()
957 props.update(templatekw.keywords)
957 props.update(templatekw.keywords)
958 props['parents'] = showparents
958 props['parents'] = showparents
959 props['templ'] = self.t
959 props['templ'] = self.t
960 props['ctx'] = ctx
960 props['ctx'] = ctx
961 props['repo'] = self.repo
961 props['repo'] = self.repo
962 props['revcache'] = {'copies': copies}
962 props['revcache'] = {'copies': copies}
963 props['cache'] = self.cache
963 props['cache'] = self.cache
964
964
965 # find correct templates for current mode
965 # find correct templates for current mode
966
966
967 tmplmodes = [
967 tmplmodes = [
968 (True, None),
968 (True, None),
969 (self.ui.verbose, 'verbose'),
969 (self.ui.verbose, 'verbose'),
970 (self.ui.quiet, 'quiet'),
970 (self.ui.quiet, 'quiet'),
971 (self.ui.debugflag, 'debug'),
971 (self.ui.debugflag, 'debug'),
972 ]
972 ]
973
973
974 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
974 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
975 for mode, postfix in tmplmodes:
975 for mode, postfix in tmplmodes:
976 for type in types:
976 for type in types:
977 cur = postfix and ('%s_%s' % (type, postfix)) or type
977 cur = postfix and ('%s_%s' % (type, postfix)) or type
978 if mode and cur in self.t:
978 if mode and cur in self.t:
979 types[type] = cur
979 types[type] = cur
980
980
981 try:
981 try:
982
982
983 # write header
983 # write header
984 if types['header']:
984 if types['header']:
985 h = templater.stringify(self.t(types['header'], **props))
985 h = templater.stringify(self.t(types['header'], **props))
986 if self.buffered:
986 if self.buffered:
987 self.header[ctx.rev()] = h
987 self.header[ctx.rev()] = h
988 else:
988 else:
989 if self.lastheader != h:
989 if self.lastheader != h:
990 self.lastheader = h
990 self.lastheader = h
991 self.ui.write(h)
991 self.ui.write(h)
992
992
993 # write changeset metadata, then patch if requested
993 # write changeset metadata, then patch if requested
994 key = types['changeset']
994 key = types['changeset']
995 self.ui.write(templater.stringify(self.t(key, **props)))
995 self.ui.write(templater.stringify(self.t(key, **props)))
996 self.showpatch(ctx.node(), matchfn)
996 self.showpatch(ctx.node(), matchfn)
997
997
998 if types['footer']:
998 if types['footer']:
999 if not self.footer:
999 if not self.footer:
1000 self.footer = templater.stringify(self.t(types['footer'],
1000 self.footer = templater.stringify(self.t(types['footer'],
1001 **props))
1001 **props))
1002
1002
1003 except KeyError, inst:
1003 except KeyError, inst:
1004 msg = _("%s: no key named '%s'")
1004 msg = _("%s: no key named '%s'")
1005 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1005 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1006 except SyntaxError, inst:
1006 except SyntaxError, inst:
1007 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1007 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1008
1008
1009 def show_changeset(ui, repo, opts, buffered=False):
1009 def show_changeset(ui, repo, opts, buffered=False):
1010 """show one changeset using template or regular display.
1010 """show one changeset using template or regular display.
1011
1011
1012 Display format will be the first non-empty hit of:
1012 Display format will be the first non-empty hit of:
1013 1. option 'template'
1013 1. option 'template'
1014 2. option 'style'
1014 2. option 'style'
1015 3. [ui] setting 'logtemplate'
1015 3. [ui] setting 'logtemplate'
1016 4. [ui] setting 'style'
1016 4. [ui] setting 'style'
1017 If all of these values are either the unset or the empty string,
1017 If all of these values are either the unset or the empty string,
1018 regular display via changeset_printer() is done.
1018 regular display via changeset_printer() is done.
1019 """
1019 """
1020 # options
1020 # options
1021 patch = False
1021 patch = False
1022 if opts.get('patch') or opts.get('stat'):
1022 if opts.get('patch') or opts.get('stat'):
1023 patch = matchall(repo)
1023 patch = matchall(repo)
1024
1024
1025 tmpl = opts.get('template')
1025 tmpl = opts.get('template')
1026 style = None
1026 style = None
1027 if tmpl:
1027 if tmpl:
1028 tmpl = templater.parsestring(tmpl, quoted=False)
1028 tmpl = templater.parsestring(tmpl, quoted=False)
1029 else:
1029 else:
1030 style = opts.get('style')
1030 style = opts.get('style')
1031
1031
1032 # ui settings
1032 # ui settings
1033 if not (tmpl or style):
1033 if not (tmpl or style):
1034 tmpl = ui.config('ui', 'logtemplate')
1034 tmpl = ui.config('ui', 'logtemplate')
1035 if tmpl:
1035 if tmpl:
1036 tmpl = templater.parsestring(tmpl)
1036 tmpl = templater.parsestring(tmpl)
1037 else:
1037 else:
1038 style = util.expandpath(ui.config('ui', 'style', ''))
1038 style = util.expandpath(ui.config('ui', 'style', ''))
1039
1039
1040 if not (tmpl or style):
1040 if not (tmpl or style):
1041 return changeset_printer(ui, repo, patch, opts, buffered)
1041 return changeset_printer(ui, repo, patch, opts, buffered)
1042
1042
1043 mapfile = None
1043 mapfile = None
1044 if style and not tmpl:
1044 if style and not tmpl:
1045 mapfile = style
1045 mapfile = style
1046 if not os.path.split(mapfile)[0]:
1046 if not os.path.split(mapfile)[0]:
1047 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1047 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1048 or templater.templatepath(mapfile))
1048 or templater.templatepath(mapfile))
1049 if mapname:
1049 if mapname:
1050 mapfile = mapname
1050 mapfile = mapname
1051
1051
1052 try:
1052 try:
1053 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1053 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1054 except SyntaxError, inst:
1054 except SyntaxError, inst:
1055 raise util.Abort(inst.args[0])
1055 raise util.Abort(inst.args[0])
1056 if tmpl:
1056 if tmpl:
1057 t.use_template(tmpl)
1057 t.use_template(tmpl)
1058 return t
1058 return t
1059
1059
1060 def finddate(ui, repo, date):
1060 def finddate(ui, repo, date):
1061 """Find the tipmost changeset that matches the given date spec"""
1061 """Find the tipmost changeset that matches the given date spec"""
1062
1062
1063 df = util.matchdate(date)
1063 df = util.matchdate(date)
1064 m = matchall(repo)
1064 m = matchall(repo)
1065 results = {}
1065 results = {}
1066
1066
1067 def prep(ctx, fns):
1067 def prep(ctx, fns):
1068 d = ctx.date()
1068 d = ctx.date()
1069 if df(d[0]):
1069 if df(d[0]):
1070 results[ctx.rev()] = d
1070 results[ctx.rev()] = d
1071
1071
1072 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1072 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1073 rev = ctx.rev()
1073 rev = ctx.rev()
1074 if rev in results:
1074 if rev in results:
1075 ui.status(_("Found revision %s from %s\n") %
1075 ui.status(_("Found revision %s from %s\n") %
1076 (rev, util.datestr(results[rev])))
1076 (rev, util.datestr(results[rev])))
1077 return str(rev)
1077 return str(rev)
1078
1078
1079 raise util.Abort(_("revision matching date not found"))
1079 raise util.Abort(_("revision matching date not found"))
1080
1080
1081 def walkchangerevs(repo, match, opts, prepare):
1081 def walkchangerevs(repo, match, opts, prepare):
1082 '''Iterate over files and the revs in which they changed.
1082 '''Iterate over files and the revs in which they changed.
1083
1083
1084 Callers most commonly need to iterate backwards over the history
1084 Callers most commonly need to iterate backwards over the history
1085 in which they are interested. Doing so has awful (quadratic-looking)
1085 in which they are interested. Doing so has awful (quadratic-looking)
1086 performance, so we use iterators in a "windowed" way.
1086 performance, so we use iterators in a "windowed" way.
1087
1087
1088 We walk a window of revisions in the desired order. Within the
1088 We walk a window of revisions in the desired order. Within the
1089 window, we first walk forwards to gather data, then in the desired
1089 window, we first walk forwards to gather data, then in the desired
1090 order (usually backwards) to display it.
1090 order (usually backwards) to display it.
1091
1091
1092 This function returns an iterator yielding contexts. Before
1092 This function returns an iterator yielding contexts. Before
1093 yielding each context, the iterator will first call the prepare
1093 yielding each context, the iterator will first call the prepare
1094 function on each context in the window in forward order.'''
1094 function on each context in the window in forward order.'''
1095
1095
1096 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1096 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1097 if start < end:
1097 if start < end:
1098 while start < end:
1098 while start < end:
1099 yield start, min(windowsize, end - start)
1099 yield start, min(windowsize, end - start)
1100 start += windowsize
1100 start += windowsize
1101 if windowsize < sizelimit:
1101 if windowsize < sizelimit:
1102 windowsize *= 2
1102 windowsize *= 2
1103 else:
1103 else:
1104 while start > end:
1104 while start > end:
1105 yield start, min(windowsize, start - end - 1)
1105 yield start, min(windowsize, start - end - 1)
1106 start -= windowsize
1106 start -= windowsize
1107 if windowsize < sizelimit:
1107 if windowsize < sizelimit:
1108 windowsize *= 2
1108 windowsize *= 2
1109
1109
1110 follow = opts.get('follow') or opts.get('follow_first')
1110 follow = opts.get('follow') or opts.get('follow_first')
1111
1111
1112 if not len(repo):
1112 if not len(repo):
1113 return []
1113 return []
1114
1114
1115 if follow:
1115 if follow:
1116 defrange = '%s:0' % repo['.'].rev()
1116 defrange = '%s:0' % repo['.'].rev()
1117 else:
1117 else:
1118 defrange = '-1:0'
1118 defrange = '-1:0'
1119 revs = revrange(repo, opts['rev'] or [defrange])
1119 revs = revrange(repo, opts['rev'] or [defrange])
1120 if not revs:
1120 if not revs:
1121 return []
1121 return []
1122 wanted = set()
1122 wanted = set()
1123 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1123 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1124 fncache = {}
1124 fncache = {}
1125 change = util.cachefunc(repo.changectx)
1125 change = util.cachefunc(repo.changectx)
1126
1126
1127 # First step is to fill wanted, the set of revisions that we want to yield.
1127 # First step is to fill wanted, the set of revisions that we want to yield.
1128 # When it does not induce extra cost, we also fill fncache for revisions in
1128 # When it does not induce extra cost, we also fill fncache for revisions in
1129 # wanted: a cache of filenames that were changed (ctx.files()) and that
1129 # wanted: a cache of filenames that were changed (ctx.files()) and that
1130 # match the file filtering conditions.
1130 # match the file filtering conditions.
1131
1131
1132 if not slowpath and not match.files():
1132 if not slowpath and not match.files():
1133 # No files, no patterns. Display all revs.
1133 # No files, no patterns. Display all revs.
1134 wanted = set(revs)
1134 wanted = set(revs)
1135 copies = []
1135 copies = []
1136
1136
1137 if not slowpath:
1137 if not slowpath:
1138 # We only have to read through the filelog to find wanted revisions
1138 # We only have to read through the filelog to find wanted revisions
1139
1139
1140 minrev, maxrev = min(revs), max(revs)
1140 minrev, maxrev = min(revs), max(revs)
1141 def filerevgen(filelog, last):
1141 def filerevgen(filelog, last):
1142 """
1142 """
1143 Only files, no patterns. Check the history of each file.
1143 Only files, no patterns. Check the history of each file.
1144
1144
1145 Examines filelog entries within minrev, maxrev linkrev range
1145 Examines filelog entries within minrev, maxrev linkrev range
1146 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1146 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1147 tuples in backwards order
1147 tuples in backwards order
1148 """
1148 """
1149 cl_count = len(repo)
1149 cl_count = len(repo)
1150 revs = []
1150 revs = []
1151 for j in xrange(0, last + 1):
1151 for j in xrange(0, last + 1):
1152 linkrev = filelog.linkrev(j)
1152 linkrev = filelog.linkrev(j)
1153 if linkrev < minrev:
1153 if linkrev < minrev:
1154 continue
1154 continue
1155 # only yield rev for which we have the changelog, it can
1155 # only yield rev for which we have the changelog, it can
1156 # happen while doing "hg log" during a pull or commit
1156 # happen while doing "hg log" during a pull or commit
1157 if linkrev >= cl_count:
1157 if linkrev >= cl_count:
1158 break
1158 break
1159
1159
1160 parentlinkrevs = []
1160 parentlinkrevs = []
1161 for p in filelog.parentrevs(j):
1161 for p in filelog.parentrevs(j):
1162 if p != nullrev:
1162 if p != nullrev:
1163 parentlinkrevs.append(filelog.linkrev(p))
1163 parentlinkrevs.append(filelog.linkrev(p))
1164 n = filelog.node(j)
1164 n = filelog.node(j)
1165 revs.append((linkrev, parentlinkrevs,
1165 revs.append((linkrev, parentlinkrevs,
1166 follow and filelog.renamed(n)))
1166 follow and filelog.renamed(n)))
1167
1167
1168 return reversed(revs)
1168 return reversed(revs)
1169 def iterfiles():
1169 def iterfiles():
1170 for filename in match.files():
1170 for filename in match.files():
1171 yield filename, None
1171 yield filename, None
1172 for filename_node in copies:
1172 for filename_node in copies:
1173 yield filename_node
1173 yield filename_node
1174 for file_, node in iterfiles():
1174 for file_, node in iterfiles():
1175 filelog = repo.file(file_)
1175 filelog = repo.file(file_)
1176 if not len(filelog):
1176 if not len(filelog):
1177 if node is None:
1177 if node is None:
1178 # A zero count may be a directory or deleted file, so
1178 # A zero count may be a directory or deleted file, so
1179 # try to find matching entries on the slow path.
1179 # try to find matching entries on the slow path.
1180 if follow:
1180 if follow:
1181 raise util.Abort(
1181 raise util.Abort(
1182 _('cannot follow nonexistent file: "%s"') % file_)
1182 _('cannot follow nonexistent file: "%s"') % file_)
1183 slowpath = True
1183 slowpath = True
1184 break
1184 break
1185 else:
1185 else:
1186 continue
1186 continue
1187
1187
1188 if node is None:
1188 if node is None:
1189 last = len(filelog) - 1
1189 last = len(filelog) - 1
1190 else:
1190 else:
1191 last = filelog.rev(node)
1191 last = filelog.rev(node)
1192
1192
1193
1193
1194 # keep track of all ancestors of the file
1194 # keep track of all ancestors of the file
1195 ancestors = set([filelog.linkrev(last)])
1195 ancestors = set([filelog.linkrev(last)])
1196
1196
1197 # iterate from latest to oldest revision
1197 # iterate from latest to oldest revision
1198 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1198 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1199 if not follow:
1199 if not follow:
1200 if rev > maxrev:
1200 if rev > maxrev:
1201 continue
1201 continue
1202 else:
1202 else:
1203 # Note that last might not be the first interesting
1203 # Note that last might not be the first interesting
1204 # rev to us:
1204 # rev to us:
1205 # if the file has been changed after maxrev, we'll
1205 # if the file has been changed after maxrev, we'll
1206 # have linkrev(last) > maxrev, and we still need
1206 # have linkrev(last) > maxrev, and we still need
1207 # to explore the file graph
1207 # to explore the file graph
1208 if rev not in ancestors:
1208 if rev not in ancestors:
1209 continue
1209 continue
1210 # XXX insert 1327 fix here
1210 # XXX insert 1327 fix here
1211 if flparentlinkrevs:
1211 if flparentlinkrevs:
1212 ancestors.update(flparentlinkrevs)
1212 ancestors.update(flparentlinkrevs)
1213
1213
1214 fncache.setdefault(rev, []).append(file_)
1214 fncache.setdefault(rev, []).append(file_)
1215 wanted.add(rev)
1215 wanted.add(rev)
1216 if copied:
1216 if copied:
1217 copies.append(copied)
1217 copies.append(copied)
1218 if slowpath:
1218 if slowpath:
1219 # We have to read the changelog to match filenames against
1219 # We have to read the changelog to match filenames against
1220 # changed files
1220 # changed files
1221
1221
1222 if follow:
1222 if follow:
1223 raise util.Abort(_('can only follow copies/renames for explicit '
1223 raise util.Abort(_('can only follow copies/renames for explicit '
1224 'filenames'))
1224 'filenames'))
1225
1225
1226 # The slow path checks files modified in every changeset.
1226 # The slow path checks files modified in every changeset.
1227 for i in sorted(revs):
1227 for i in sorted(revs):
1228 ctx = change(i)
1228 ctx = change(i)
1229 matches = filter(match, ctx.files())
1229 matches = filter(match, ctx.files())
1230 if matches:
1230 if matches:
1231 fncache[i] = matches
1231 fncache[i] = matches
1232 wanted.add(i)
1232 wanted.add(i)
1233
1233
1234 class followfilter(object):
1234 class followfilter(object):
1235 def __init__(self, onlyfirst=False):
1235 def __init__(self, onlyfirst=False):
1236 self.startrev = nullrev
1236 self.startrev = nullrev
1237 self.roots = set()
1237 self.roots = set()
1238 self.onlyfirst = onlyfirst
1238 self.onlyfirst = onlyfirst
1239
1239
1240 def match(self, rev):
1240 def match(self, rev):
1241 def realparents(rev):
1241 def realparents(rev):
1242 if self.onlyfirst:
1242 if self.onlyfirst:
1243 return repo.changelog.parentrevs(rev)[0:1]
1243 return repo.changelog.parentrevs(rev)[0:1]
1244 else:
1244 else:
1245 return filter(lambda x: x != nullrev,
1245 return filter(lambda x: x != nullrev,
1246 repo.changelog.parentrevs(rev))
1246 repo.changelog.parentrevs(rev))
1247
1247
1248 if self.startrev == nullrev:
1248 if self.startrev == nullrev:
1249 self.startrev = rev
1249 self.startrev = rev
1250 return True
1250 return True
1251
1251
1252 if rev > self.startrev:
1252 if rev > self.startrev:
1253 # forward: all descendants
1253 # forward: all descendants
1254 if not self.roots:
1254 if not self.roots:
1255 self.roots.add(self.startrev)
1255 self.roots.add(self.startrev)
1256 for parent in realparents(rev):
1256 for parent in realparents(rev):
1257 if parent in self.roots:
1257 if parent in self.roots:
1258 self.roots.add(rev)
1258 self.roots.add(rev)
1259 return True
1259 return True
1260 else:
1260 else:
1261 # backwards: all parents
1261 # backwards: all parents
1262 if not self.roots:
1262 if not self.roots:
1263 self.roots.update(realparents(self.startrev))
1263 self.roots.update(realparents(self.startrev))
1264 if rev in self.roots:
1264 if rev in self.roots:
1265 self.roots.remove(rev)
1265 self.roots.remove(rev)
1266 self.roots.update(realparents(rev))
1266 self.roots.update(realparents(rev))
1267 return True
1267 return True
1268
1268
1269 return False
1269 return False
1270
1270
1271 # it might be worthwhile to do this in the iterator if the rev range
1271 # it might be worthwhile to do this in the iterator if the rev range
1272 # is descending and the prune args are all within that range
1272 # is descending and the prune args are all within that range
1273 for rev in opts.get('prune', ()):
1273 for rev in opts.get('prune', ()):
1274 rev = repo.changelog.rev(repo.lookup(rev))
1274 rev = repo.changelog.rev(repo.lookup(rev))
1275 ff = followfilter()
1275 ff = followfilter()
1276 stop = min(revs[0], revs[-1])
1276 stop = min(revs[0], revs[-1])
1277 for x in xrange(rev, stop - 1, -1):
1277 for x in xrange(rev, stop - 1, -1):
1278 if ff.match(x):
1278 if ff.match(x):
1279 wanted.discard(x)
1279 wanted.discard(x)
1280
1280
1281 # Now that wanted is correctly initialized, we can iterate over the
1281 # Now that wanted is correctly initialized, we can iterate over the
1282 # revision range, yielding only revisions in wanted.
1282 # revision range, yielding only revisions in wanted.
1283 def iterate():
1283 def iterate():
1284 if follow and not match.files():
1284 if follow and not match.files():
1285 ff = followfilter(onlyfirst=opts.get('follow_first'))
1285 ff = followfilter(onlyfirst=opts.get('follow_first'))
1286 def want(rev):
1286 def want(rev):
1287 return ff.match(rev) and rev in wanted
1287 return ff.match(rev) and rev in wanted
1288 else:
1288 else:
1289 def want(rev):
1289 def want(rev):
1290 return rev in wanted
1290 return rev in wanted
1291
1291
1292 for i, window in increasing_windows(0, len(revs)):
1292 for i, window in increasing_windows(0, len(revs)):
1293 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1293 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1294 for rev in sorted(nrevs):
1294 for rev in sorted(nrevs):
1295 fns = fncache.get(rev)
1295 fns = fncache.get(rev)
1296 ctx = change(rev)
1296 ctx = change(rev)
1297 if not fns:
1297 if not fns:
1298 def fns_generator():
1298 def fns_generator():
1299 for f in ctx.files():
1299 for f in ctx.files():
1300 if match(f):
1300 if match(f):
1301 yield f
1301 yield f
1302 fns = fns_generator()
1302 fns = fns_generator()
1303 prepare(ctx, fns)
1303 prepare(ctx, fns)
1304 for rev in nrevs:
1304 for rev in nrevs:
1305 yield change(rev)
1305 yield change(rev)
1306 return iterate()
1306 return iterate()
1307
1307
1308 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1308 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1309 join = lambda f: os.path.join(prefix, f)
1309 join = lambda f: os.path.join(prefix, f)
1310 bad = []
1310 bad = []
1311 oldbad = match.bad
1311 oldbad = match.bad
1312 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1312 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1313 names = []
1313 names = []
1314 wctx = repo[None]
1314 wctx = repo[None]
1315 for f in repo.walk(match):
1315 for f in repo.walk(match):
1316 exact = match.exact(f)
1316 exact = match.exact(f)
1317 if exact or f not in repo.dirstate:
1317 if exact or f not in repo.dirstate:
1318 names.append(f)
1318 names.append(f)
1319 if ui.verbose or not exact:
1319 if ui.verbose or not exact:
1320 ui.status(_('adding %s\n') % match.rel(join(f)))
1320 ui.status(_('adding %s\n') % match.rel(join(f)))
1321
1321
1322 if listsubrepos:
1322 if listsubrepos:
1323 for subpath in wctx.substate:
1323 for subpath in wctx.substate:
1324 sub = wctx.sub(subpath)
1324 sub = wctx.sub(subpath)
1325 try:
1325 try:
1326 submatch = matchmod.narrowmatcher(subpath, match)
1326 submatch = matchmod.narrowmatcher(subpath, match)
1327 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1327 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1328 except error.LookupError:
1328 except error.LookupError:
1329 ui.status(_("skipping missing subrepository: %s\n")
1329 ui.status(_("skipping missing subrepository: %s\n")
1330 % join(subpath))
1330 % join(subpath))
1331
1331
1332 if not dryrun:
1332 if not dryrun:
1333 rejected = wctx.add(names, prefix)
1333 rejected = wctx.add(names, prefix)
1334 bad.extend(f for f in rejected if f in match.files())
1334 bad.extend(f for f in rejected if f in match.files())
1335 return bad
1335 return bad
1336
1336
1337 def commit(ui, repo, commitfunc, pats, opts):
1337 def commit(ui, repo, commitfunc, pats, opts):
1338 '''commit the specified files or all outstanding changes'''
1338 '''commit the specified files or all outstanding changes'''
1339 date = opts.get('date')
1339 date = opts.get('date')
1340 if date:
1340 if date:
1341 opts['date'] = util.parsedate(date)
1341 opts['date'] = util.parsedate(date)
1342 message = logmessage(opts)
1342 message = logmessage(opts)
1343
1343
1344 # extract addremove carefully -- this function can be called from a command
1344 # extract addremove carefully -- this function can be called from a command
1345 # that doesn't support addremove
1345 # that doesn't support addremove
1346 if opts.get('addremove'):
1346 if opts.get('addremove'):
1347 addremove(repo, pats, opts)
1347 addremove(repo, pats, opts)
1348
1348
1349 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1349 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1350
1350
1351 def commiteditor(repo, ctx, subs):
1351 def commiteditor(repo, ctx, subs):
1352 if ctx.description():
1352 if ctx.description():
1353 return ctx.description()
1353 return ctx.description()
1354 return commitforceeditor(repo, ctx, subs)
1354 return commitforceeditor(repo, ctx, subs)
1355
1355
1356 def commitforceeditor(repo, ctx, subs):
1356 def commitforceeditor(repo, ctx, subs):
1357 edittext = []
1357 edittext = []
1358 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1358 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1359 if ctx.description():
1359 if ctx.description():
1360 edittext.append(ctx.description())
1360 edittext.append(ctx.description())
1361 edittext.append("")
1361 edittext.append("")
1362 edittext.append("") # Empty line between message and comments.
1362 edittext.append("") # Empty line between message and comments.
1363 edittext.append(_("HG: Enter commit message."
1363 edittext.append(_("HG: Enter commit message."
1364 " Lines beginning with 'HG:' are removed."))
1364 " Lines beginning with 'HG:' are removed."))
1365 edittext.append(_("HG: Leave message empty to abort commit."))
1365 edittext.append(_("HG: Leave message empty to abort commit."))
1366 edittext.append("HG: --")
1366 edittext.append("HG: --")
1367 edittext.append(_("HG: user: %s") % ctx.user())
1367 edittext.append(_("HG: user: %s") % ctx.user())
1368 if ctx.p2():
1368 if ctx.p2():
1369 edittext.append(_("HG: branch merge"))
1369 edittext.append(_("HG: branch merge"))
1370 if ctx.branch():
1370 if ctx.branch():
1371 edittext.append(_("HG: branch '%s'") % ctx.branch())
1371 edittext.append(_("HG: branch '%s'") % ctx.branch())
1372 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1372 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1373 edittext.extend([_("HG: added %s") % f for f in added])
1373 edittext.extend([_("HG: added %s") % f for f in added])
1374 edittext.extend([_("HG: changed %s") % f for f in modified])
1374 edittext.extend([_("HG: changed %s") % f for f in modified])
1375 edittext.extend([_("HG: removed %s") % f for f in removed])
1375 edittext.extend([_("HG: removed %s") % f for f in removed])
1376 if not added and not modified and not removed:
1376 if not added and not modified and not removed:
1377 edittext.append(_("HG: no files changed"))
1377 edittext.append(_("HG: no files changed"))
1378 edittext.append("")
1378 edittext.append("")
1379 # run editor in the repository root
1379 # run editor in the repository root
1380 olddir = os.getcwd()
1380 olddir = os.getcwd()
1381 os.chdir(repo.root)
1381 os.chdir(repo.root)
1382 text = repo.ui.edit("\n".join(edittext), ctx.user())
1382 text = repo.ui.edit("\n".join(edittext), ctx.user())
1383 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1383 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1384 os.chdir(olddir)
1384 os.chdir(olddir)
1385
1385
1386 if not text.strip():
1386 if not text.strip():
1387 raise util.Abort(_("empty commit message"))
1387 raise util.Abort(_("empty commit message"))
1388
1388
1389 return text
1389 return text
@@ -1,4877 +1,4877 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, sys, difflib, time, tempfile
11 import os, re, sys, difflib, time, tempfile
12 import hg, util, revlog, extensions, copies, error, bookmarks
12 import hg, util, revlog, extensions, copies, error, bookmarks
13 import patch, help, mdiff, url, encoding, templatekw, discovery
13 import patch, help, mdiff, url, encoding, templatekw, discovery
14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15 import merge as mergemod
15 import merge as mergemod
16 import minirst, revset, templatefilters
16 import minirst, revset, templatefilters
17 import dagparser
17 import dagparser
18
18
19 # Commands start here, listed alphabetically
19 # Commands start here, listed alphabetically
20
20
21 def add(ui, repo, *pats, **opts):
21 def add(ui, repo, *pats, **opts):
22 """add the specified files on the next commit
22 """add the specified files on the next commit
23
23
24 Schedule files to be version controlled and added to the
24 Schedule files to be version controlled and added to the
25 repository.
25 repository.
26
26
27 The files will be added to the repository at the next commit. To
27 The files will be added to the repository at the next commit. To
28 undo an add before that, see :hg:`forget`.
28 undo an add before that, see :hg:`forget`.
29
29
30 If no names are given, add all files to the repository.
30 If no names are given, add all files to the repository.
31
31
32 .. container:: verbose
32 .. container:: verbose
33
33
34 An example showing how new (unknown) files are added
34 An example showing how new (unknown) files are added
35 automatically by :hg:`add`::
35 automatically by :hg:`add`::
36
36
37 $ ls
37 $ ls
38 foo.c
38 foo.c
39 $ hg status
39 $ hg status
40 ? foo.c
40 ? foo.c
41 $ hg add
41 $ hg add
42 adding foo.c
42 adding foo.c
43 $ hg status
43 $ hg status
44 A foo.c
44 A foo.c
45
45
46 Returns 0 if all files are successfully added.
46 Returns 0 if all files are successfully added.
47 """
47 """
48
48
49 m = cmdutil.match(repo, pats, opts)
49 m = cmdutil.match(repo, pats, opts)
50 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
50 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
51 opts.get('subrepos'), prefix="")
51 opts.get('subrepos'), prefix="")
52 return rejected and 1 or 0
52 return rejected and 1 or 0
53
53
54 def addremove(ui, repo, *pats, **opts):
54 def addremove(ui, repo, *pats, **opts):
55 """add all new files, delete all missing files
55 """add all new files, delete all missing files
56
56
57 Add all new files and remove all missing files from the
57 Add all new files and remove all missing files from the
58 repository.
58 repository.
59
59
60 New files are ignored if they match any of the patterns in
60 New files are ignored if they match any of the patterns in
61 ``.hgignore``. As with add, these changes take effect at the next
61 ``.hgignore``. As with add, these changes take effect at the next
62 commit.
62 commit.
63
63
64 Use the -s/--similarity option to detect renamed files. With a
64 Use the -s/--similarity option to detect renamed files. With a
65 parameter greater than 0, this compares every removed file with
65 parameter greater than 0, this compares every removed file with
66 every added file and records those similar enough as renames. This
66 every added file and records those similar enough as renames. This
67 option takes a percentage between 0 (disabled) and 100 (files must
67 option takes a percentage between 0 (disabled) and 100 (files must
68 be identical) as its parameter. Detecting renamed files this way
68 be identical) as its parameter. Detecting renamed files this way
69 can be expensive. After using this option, :hg:`status -C` can be
69 can be expensive. After using this option, :hg:`status -C` can be
70 used to check which files were identified as moved or renamed.
70 used to check which files were identified as moved or renamed.
71
71
72 Returns 0 if all files are successfully added.
72 Returns 0 if all files are successfully added.
73 """
73 """
74 try:
74 try:
75 sim = float(opts.get('similarity') or 100)
75 sim = float(opts.get('similarity') or 100)
76 except ValueError:
76 except ValueError:
77 raise util.Abort(_('similarity must be a number'))
77 raise util.Abort(_('similarity must be a number'))
78 if sim < 0 or sim > 100:
78 if sim < 0 or sim > 100:
79 raise util.Abort(_('similarity must be between 0 and 100'))
79 raise util.Abort(_('similarity must be between 0 and 100'))
80 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
80 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
81
81
82 def annotate(ui, repo, *pats, **opts):
82 def annotate(ui, repo, *pats, **opts):
83 """show changeset information by line for each file
83 """show changeset information by line for each file
84
84
85 List changes in files, showing the revision id responsible for
85 List changes in files, showing the revision id responsible for
86 each line
86 each line
87
87
88 This command is useful for discovering when a change was made and
88 This command is useful for discovering when a change was made and
89 by whom.
89 by whom.
90
90
91 Without the -a/--text option, annotate will avoid processing files
91 Without the -a/--text option, annotate will avoid processing files
92 it detects as binary. With -a, annotate will annotate the file
92 it detects as binary. With -a, annotate will annotate the file
93 anyway, although the results will probably be neither useful
93 anyway, although the results will probably be neither useful
94 nor desirable.
94 nor desirable.
95
95
96 Returns 0 on success.
96 Returns 0 on success.
97 """
97 """
98 if opts.get('follow'):
98 if opts.get('follow'):
99 # --follow is deprecated and now just an alias for -f/--file
99 # --follow is deprecated and now just an alias for -f/--file
100 # to mimic the behavior of Mercurial before version 1.5
100 # to mimic the behavior of Mercurial before version 1.5
101 opts['file'] = 1
101 opts['file'] = 1
102
102
103 datefunc = ui.quiet and util.shortdate or util.datestr
103 datefunc = ui.quiet and util.shortdate or util.datestr
104 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
104 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
105
105
106 if not pats:
106 if not pats:
107 raise util.Abort(_('at least one filename or pattern is required'))
107 raise util.Abort(_('at least one filename or pattern is required'))
108
108
109 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
109 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
110 ('number', lambda x: str(x[0].rev())),
110 ('number', lambda x: str(x[0].rev())),
111 ('changeset', lambda x: short(x[0].node())),
111 ('changeset', lambda x: short(x[0].node())),
112 ('date', getdate),
112 ('date', getdate),
113 ('file', lambda x: x[0].path()),
113 ('file', lambda x: x[0].path()),
114 ]
114 ]
115
115
116 if (not opts.get('user') and not opts.get('changeset')
116 if (not opts.get('user') and not opts.get('changeset')
117 and not opts.get('date') and not opts.get('file')):
117 and not opts.get('date') and not opts.get('file')):
118 opts['number'] = 1
118 opts['number'] = 1
119
119
120 linenumber = opts.get('line_number') is not None
120 linenumber = opts.get('line_number') is not None
121 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
121 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
122 raise util.Abort(_('at least one of -n/-c is required for -l'))
122 raise util.Abort(_('at least one of -n/-c is required for -l'))
123
123
124 funcmap = [func for op, func in opmap if opts.get(op)]
124 funcmap = [func for op, func in opmap if opts.get(op)]
125 if linenumber:
125 if linenumber:
126 lastfunc = funcmap[-1]
126 lastfunc = funcmap[-1]
127 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
127 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
128
128
129 def bad(x, y):
129 def bad(x, y):
130 raise util.Abort("%s: %s" % (x, y))
130 raise util.Abort("%s: %s" % (x, y))
131
131
132 ctx = cmdutil.revsingle(repo, opts.get('rev'))
132 ctx = cmdutil.revsingle(repo, opts.get('rev'))
133 m = cmdutil.match(repo, pats, opts)
133 m = cmdutil.match(repo, pats, opts)
134 m.bad = bad
134 m.bad = bad
135 follow = not opts.get('no_follow')
135 follow = not opts.get('no_follow')
136 for abs in ctx.walk(m):
136 for abs in ctx.walk(m):
137 fctx = ctx[abs]
137 fctx = ctx[abs]
138 if not opts.get('text') and util.binary(fctx.data()):
138 if not opts.get('text') and util.binary(fctx.data()):
139 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
139 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
140 continue
140 continue
141
141
142 lines = fctx.annotate(follow=follow, linenumber=linenumber)
142 lines = fctx.annotate(follow=follow, linenumber=linenumber)
143 pieces = []
143 pieces = []
144
144
145 for f in funcmap:
145 for f in funcmap:
146 l = [f(n) for n, dummy in lines]
146 l = [f(n) for n, dummy in lines]
147 if l:
147 if l:
148 sized = [(x, encoding.colwidth(x)) for x in l]
148 sized = [(x, encoding.colwidth(x)) for x in l]
149 ml = max([w for x, w in sized])
149 ml = max([w for x, w in sized])
150 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
150 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
151
151
152 if pieces:
152 if pieces:
153 for p, l in zip(zip(*pieces), lines):
153 for p, l in zip(zip(*pieces), lines):
154 ui.write("%s: %s" % (" ".join(p), l[1]))
154 ui.write("%s: %s" % (" ".join(p), l[1]))
155
155
156 def archive(ui, repo, dest, **opts):
156 def archive(ui, repo, dest, **opts):
157 '''create an unversioned archive of a repository revision
157 '''create an unversioned archive of a repository revision
158
158
159 By default, the revision used is the parent of the working
159 By default, the revision used is the parent of the working
160 directory; use -r/--rev to specify a different revision.
160 directory; use -r/--rev to specify a different revision.
161
161
162 The archive type is automatically detected based on file
162 The archive type is automatically detected based on file
163 extension (or override using -t/--type).
163 extension (or override using -t/--type).
164
164
165 Valid types are:
165 Valid types are:
166
166
167 :``files``: a directory full of files (default)
167 :``files``: a directory full of files (default)
168 :``tar``: tar archive, uncompressed
168 :``tar``: tar archive, uncompressed
169 :``tbz2``: tar archive, compressed using bzip2
169 :``tbz2``: tar archive, compressed using bzip2
170 :``tgz``: tar archive, compressed using gzip
170 :``tgz``: tar archive, compressed using gzip
171 :``uzip``: zip archive, uncompressed
171 :``uzip``: zip archive, uncompressed
172 :``zip``: zip archive, compressed using deflate
172 :``zip``: zip archive, compressed using deflate
173
173
174 The exact name of the destination archive or directory is given
174 The exact name of the destination archive or directory is given
175 using a format string; see :hg:`help export` for details.
175 using a format string; see :hg:`help export` for details.
176
176
177 Each member added to an archive file has a directory prefix
177 Each member added to an archive file has a directory prefix
178 prepended. Use -p/--prefix to specify a format string for the
178 prepended. Use -p/--prefix to specify a format string for the
179 prefix. The default is the basename of the archive, with suffixes
179 prefix. The default is the basename of the archive, with suffixes
180 removed.
180 removed.
181
181
182 Returns 0 on success.
182 Returns 0 on success.
183 '''
183 '''
184
184
185 ctx = cmdutil.revsingle(repo, opts.get('rev'))
185 ctx = cmdutil.revsingle(repo, opts.get('rev'))
186 if not ctx:
186 if not ctx:
187 raise util.Abort(_('no working directory: please specify a revision'))
187 raise util.Abort(_('no working directory: please specify a revision'))
188 node = ctx.node()
188 node = ctx.node()
189 dest = cmdutil.make_filename(repo, dest, node)
189 dest = cmdutil.make_filename(repo, dest, node)
190 if os.path.realpath(dest) == repo.root:
190 if os.path.realpath(dest) == repo.root:
191 raise util.Abort(_('repository root cannot be destination'))
191 raise util.Abort(_('repository root cannot be destination'))
192
192
193 kind = opts.get('type') or archival.guesskind(dest) or 'files'
193 kind = opts.get('type') or archival.guesskind(dest) or 'files'
194 prefix = opts.get('prefix')
194 prefix = opts.get('prefix')
195
195
196 if dest == '-':
196 if dest == '-':
197 if kind == 'files':
197 if kind == 'files':
198 raise util.Abort(_('cannot archive plain files to stdout'))
198 raise util.Abort(_('cannot archive plain files to stdout'))
199 dest = sys.stdout
199 dest = sys.stdout
200 if not prefix:
200 if not prefix:
201 prefix = os.path.basename(repo.root) + '-%h'
201 prefix = os.path.basename(repo.root) + '-%h'
202
202
203 prefix = cmdutil.make_filename(repo, prefix, node)
203 prefix = cmdutil.make_filename(repo, prefix, node)
204 matchfn = cmdutil.match(repo, [], opts)
204 matchfn = cmdutil.match(repo, [], opts)
205 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
205 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
206 matchfn, prefix, subrepos=opts.get('subrepos'))
206 matchfn, prefix, subrepos=opts.get('subrepos'))
207
207
208 def backout(ui, repo, node=None, rev=None, **opts):
208 def backout(ui, repo, node=None, rev=None, **opts):
209 '''reverse effect of earlier changeset
209 '''reverse effect of earlier changeset
210
210
211 Prepare a new changeset with the effect of REV undone in the
211 Prepare a new changeset with the effect of REV undone in the
212 current working directory.
212 current working directory.
213
213
214 If REV is the parent of the working directory, then this new changeset
214 If REV is the parent of the working directory, then this new changeset
215 is committed automatically. Otherwise, hg needs to merge the
215 is committed automatically. Otherwise, hg needs to merge the
216 changes and the merged result is left uncommitted.
216 changes and the merged result is left uncommitted.
217
217
218 By default, the pending changeset will have one parent,
218 By default, the pending changeset will have one parent,
219 maintaining a linear history. With --merge, the pending changeset
219 maintaining a linear history. With --merge, the pending changeset
220 will instead have two parents: the old parent of the working
220 will instead have two parents: the old parent of the working
221 directory and a new child of REV that simply undoes REV.
221 directory and a new child of REV that simply undoes REV.
222
222
223 Before version 1.7, the behavior without --merge was equivalent to
223 Before version 1.7, the behavior without --merge was equivalent to
224 specifying --merge followed by :hg:`update --clean .` to cancel
224 specifying --merge followed by :hg:`update --clean .` to cancel
225 the merge and leave the child of REV as a head to be merged
225 the merge and leave the child of REV as a head to be merged
226 separately.
226 separately.
227
227
228 See :hg:`help dates` for a list of formats valid for -d/--date.
228 See :hg:`help dates` for a list of formats valid for -d/--date.
229
229
230 Returns 0 on success.
230 Returns 0 on success.
231 '''
231 '''
232 if rev and node:
232 if rev and node:
233 raise util.Abort(_("please specify just one revision"))
233 raise util.Abort(_("please specify just one revision"))
234
234
235 if not rev:
235 if not rev:
236 rev = node
236 rev = node
237
237
238 if not rev:
238 if not rev:
239 raise util.Abort(_("please specify a revision to backout"))
239 raise util.Abort(_("please specify a revision to backout"))
240
240
241 date = opts.get('date')
241 date = opts.get('date')
242 if date:
242 if date:
243 opts['date'] = util.parsedate(date)
243 opts['date'] = util.parsedate(date)
244
244
245 cmdutil.bail_if_changed(repo)
245 cmdutil.bail_if_changed(repo)
246 node = cmdutil.revsingle(repo, rev).node()
246 node = cmdutil.revsingle(repo, rev).node()
247
247
248 op1, op2 = repo.dirstate.parents()
248 op1, op2 = repo.dirstate.parents()
249 a = repo.changelog.ancestor(op1, node)
249 a = repo.changelog.ancestor(op1, node)
250 if a != node:
250 if a != node:
251 raise util.Abort(_('cannot backout change on a different branch'))
251 raise util.Abort(_('cannot backout change on a different branch'))
252
252
253 p1, p2 = repo.changelog.parents(node)
253 p1, p2 = repo.changelog.parents(node)
254 if p1 == nullid:
254 if p1 == nullid:
255 raise util.Abort(_('cannot backout a change with no parents'))
255 raise util.Abort(_('cannot backout a change with no parents'))
256 if p2 != nullid:
256 if p2 != nullid:
257 if not opts.get('parent'):
257 if not opts.get('parent'):
258 raise util.Abort(_('cannot backout a merge changeset without '
258 raise util.Abort(_('cannot backout a merge changeset without '
259 '--parent'))
259 '--parent'))
260 p = repo.lookup(opts['parent'])
260 p = repo.lookup(opts['parent'])
261 if p not in (p1, p2):
261 if p not in (p1, p2):
262 raise util.Abort(_('%s is not a parent of %s') %
262 raise util.Abort(_('%s is not a parent of %s') %
263 (short(p), short(node)))
263 (short(p), short(node)))
264 parent = p
264 parent = p
265 else:
265 else:
266 if opts.get('parent'):
266 if opts.get('parent'):
267 raise util.Abort(_('cannot use --parent on non-merge changeset'))
267 raise util.Abort(_('cannot use --parent on non-merge changeset'))
268 parent = p1
268 parent = p1
269
269
270 # the backout should appear on the same branch
270 # the backout should appear on the same branch
271 branch = repo.dirstate.branch()
271 branch = repo.dirstate.branch()
272 hg.clean(repo, node, show_stats=False)
272 hg.clean(repo, node, show_stats=False)
273 repo.dirstate.setbranch(branch)
273 repo.dirstate.setbranch(branch)
274 revert_opts = opts.copy()
274 revert_opts = opts.copy()
275 revert_opts['date'] = None
275 revert_opts['date'] = None
276 revert_opts['all'] = True
276 revert_opts['all'] = True
277 revert_opts['rev'] = hex(parent)
277 revert_opts['rev'] = hex(parent)
278 revert_opts['no_backup'] = None
278 revert_opts['no_backup'] = None
279 revert(ui, repo, **revert_opts)
279 revert(ui, repo, **revert_opts)
280 if not opts.get('merge') and op1 != node:
280 if not opts.get('merge') and op1 != node:
281 try:
281 try:
282 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
282 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
283 return hg.update(repo, op1)
283 return hg.update(repo, op1)
284 finally:
284 finally:
285 ui.setconfig('ui', 'forcemerge', '')
285 ui.setconfig('ui', 'forcemerge', '')
286
286
287 commit_opts = opts.copy()
287 commit_opts = opts.copy()
288 commit_opts['addremove'] = False
288 commit_opts['addremove'] = False
289 if not commit_opts['message'] and not commit_opts['logfile']:
289 if not commit_opts['message'] and not commit_opts['logfile']:
290 # we don't translate commit messages
290 # we don't translate commit messages
291 commit_opts['message'] = "Backed out changeset %s" % short(node)
291 commit_opts['message'] = "Backed out changeset %s" % short(node)
292 commit_opts['force_editor'] = True
292 commit_opts['force_editor'] = True
293 commit(ui, repo, **commit_opts)
293 commit(ui, repo, **commit_opts)
294 def nice(node):
294 def nice(node):
295 return '%d:%s' % (repo.changelog.rev(node), short(node))
295 return '%d:%s' % (repo.changelog.rev(node), short(node))
296 ui.status(_('changeset %s backs out changeset %s\n') %
296 ui.status(_('changeset %s backs out changeset %s\n') %
297 (nice(repo.changelog.tip()), nice(node)))
297 (nice(repo.changelog.tip()), nice(node)))
298 if opts.get('merge') and op1 != node:
298 if opts.get('merge') and op1 != node:
299 hg.clean(repo, op1, show_stats=False)
299 hg.clean(repo, op1, show_stats=False)
300 ui.status(_('merging with changeset %s\n')
300 ui.status(_('merging with changeset %s\n')
301 % nice(repo.changelog.tip()))
301 % nice(repo.changelog.tip()))
302 try:
302 try:
303 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
303 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
304 return hg.merge(repo, hex(repo.changelog.tip()))
304 return hg.merge(repo, hex(repo.changelog.tip()))
305 finally:
305 finally:
306 ui.setconfig('ui', 'forcemerge', '')
306 ui.setconfig('ui', 'forcemerge', '')
307 return 0
307 return 0
308
308
309 def bisect(ui, repo, rev=None, extra=None, command=None,
309 def bisect(ui, repo, rev=None, extra=None, command=None,
310 reset=None, good=None, bad=None, skip=None, extend=None,
310 reset=None, good=None, bad=None, skip=None, extend=None,
311 noupdate=None):
311 noupdate=None):
312 """subdivision search of changesets
312 """subdivision search of changesets
313
313
314 This command helps to find changesets which introduce problems. To
314 This command helps to find changesets which introduce problems. To
315 use, mark the earliest changeset you know exhibits the problem as
315 use, mark the earliest changeset you know exhibits the problem as
316 bad, then mark the latest changeset which is free from the problem
316 bad, then mark the latest changeset which is free from the problem
317 as good. Bisect will update your working directory to a revision
317 as good. Bisect will update your working directory to a revision
318 for testing (unless the -U/--noupdate option is specified). Once
318 for testing (unless the -U/--noupdate option is specified). Once
319 you have performed tests, mark the working directory as good or
319 you have performed tests, mark the working directory as good or
320 bad, and bisect will either update to another candidate changeset
320 bad, and bisect will either update to another candidate changeset
321 or announce that it has found the bad revision.
321 or announce that it has found the bad revision.
322
322
323 As a shortcut, you can also use the revision argument to mark a
323 As a shortcut, you can also use the revision argument to mark a
324 revision as good or bad without checking it out first.
324 revision as good or bad without checking it out first.
325
325
326 If you supply a command, it will be used for automatic bisection.
326 If you supply a command, it will be used for automatic bisection.
327 Its exit status will be used to mark revisions as good or bad:
327 Its exit status will be used to mark revisions as good or bad:
328 status 0 means good, 125 means to skip the revision, 127
328 status 0 means good, 125 means to skip the revision, 127
329 (command not found) will abort the bisection, and any other
329 (command not found) will abort the bisection, and any other
330 non-zero exit status means the revision is bad.
330 non-zero exit status means the revision is bad.
331
331
332 Returns 0 on success.
332 Returns 0 on success.
333 """
333 """
334 def extendbisectrange(nodes, good):
334 def extendbisectrange(nodes, good):
335 # bisect is incomplete when it ends on a merge node and
335 # bisect is incomplete when it ends on a merge node and
336 # one of the parent was not checked.
336 # one of the parent was not checked.
337 parents = repo[nodes[0]].parents()
337 parents = repo[nodes[0]].parents()
338 if len(parents) > 1:
338 if len(parents) > 1:
339 side = good and state['bad'] or state['good']
339 side = good and state['bad'] or state['good']
340 num = len(set(i.node() for i in parents) & set(side))
340 num = len(set(i.node() for i in parents) & set(side))
341 if num == 1:
341 if num == 1:
342 return parents[0].ancestor(parents[1])
342 return parents[0].ancestor(parents[1])
343 return None
343 return None
344
344
345 def print_result(nodes, good):
345 def print_result(nodes, good):
346 displayer = cmdutil.show_changeset(ui, repo, {})
346 displayer = cmdutil.show_changeset(ui, repo, {})
347 if len(nodes) == 1:
347 if len(nodes) == 1:
348 # narrowed it down to a single revision
348 # narrowed it down to a single revision
349 if good:
349 if good:
350 ui.write(_("The first good revision is:\n"))
350 ui.write(_("The first good revision is:\n"))
351 else:
351 else:
352 ui.write(_("The first bad revision is:\n"))
352 ui.write(_("The first bad revision is:\n"))
353 displayer.show(repo[nodes[0]])
353 displayer.show(repo[nodes[0]])
354 parents = repo[nodes[0]].parents()
354 parents = repo[nodes[0]].parents()
355 extendnode = extendbisectrange(nodes, good)
355 extendnode = extendbisectrange(nodes, good)
356 if extendnode is not None:
356 if extendnode is not None:
357 ui.write(_('Not all ancestors of this changeset have been'
357 ui.write(_('Not all ancestors of this changeset have been'
358 ' checked.\nUse bisect --extend to continue the '
358 ' checked.\nUse bisect --extend to continue the '
359 'bisection from\nthe common ancestor, %s.\n')
359 'bisection from\nthe common ancestor, %s.\n')
360 % short(extendnode.node()))
360 % short(extendnode.node()))
361 else:
361 else:
362 # multiple possible revisions
362 # multiple possible revisions
363 if good:
363 if good:
364 ui.write(_("Due to skipped revisions, the first "
364 ui.write(_("Due to skipped revisions, the first "
365 "good revision could be any of:\n"))
365 "good revision could be any of:\n"))
366 else:
366 else:
367 ui.write(_("Due to skipped revisions, the first "
367 ui.write(_("Due to skipped revisions, the first "
368 "bad revision could be any of:\n"))
368 "bad revision could be any of:\n"))
369 for n in nodes:
369 for n in nodes:
370 displayer.show(repo[n])
370 displayer.show(repo[n])
371 displayer.close()
371 displayer.close()
372
372
373 def check_state(state, interactive=True):
373 def check_state(state, interactive=True):
374 if not state['good'] or not state['bad']:
374 if not state['good'] or not state['bad']:
375 if (good or bad or skip or reset) and interactive:
375 if (good or bad or skip or reset) and interactive:
376 return
376 return
377 if not state['good']:
377 if not state['good']:
378 raise util.Abort(_('cannot bisect (no known good revisions)'))
378 raise util.Abort(_('cannot bisect (no known good revisions)'))
379 else:
379 else:
380 raise util.Abort(_('cannot bisect (no known bad revisions)'))
380 raise util.Abort(_('cannot bisect (no known bad revisions)'))
381 return True
381 return True
382
382
383 # backward compatibility
383 # backward compatibility
384 if rev in "good bad reset init".split():
384 if rev in "good bad reset init".split():
385 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
385 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
386 cmd, rev, extra = rev, extra, None
386 cmd, rev, extra = rev, extra, None
387 if cmd == "good":
387 if cmd == "good":
388 good = True
388 good = True
389 elif cmd == "bad":
389 elif cmd == "bad":
390 bad = True
390 bad = True
391 else:
391 else:
392 reset = True
392 reset = True
393 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
393 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
394 raise util.Abort(_('incompatible arguments'))
394 raise util.Abort(_('incompatible arguments'))
395
395
396 if reset:
396 if reset:
397 p = repo.join("bisect.state")
397 p = repo.join("bisect.state")
398 if os.path.exists(p):
398 if os.path.exists(p):
399 os.unlink(p)
399 os.unlink(p)
400 return
400 return
401
401
402 state = hbisect.load_state(repo)
402 state = hbisect.load_state(repo)
403
403
404 if command:
404 if command:
405 changesets = 1
405 changesets = 1
406 try:
406 try:
407 while changesets:
407 while changesets:
408 # update state
408 # update state
409 status = util.system(command)
409 status = util.system(command)
410 if status == 125:
410 if status == 125:
411 transition = "skip"
411 transition = "skip"
412 elif status == 0:
412 elif status == 0:
413 transition = "good"
413 transition = "good"
414 # status < 0 means process was killed
414 # status < 0 means process was killed
415 elif status == 127:
415 elif status == 127:
416 raise util.Abort(_("failed to execute %s") % command)
416 raise util.Abort(_("failed to execute %s") % command)
417 elif status < 0:
417 elif status < 0:
418 raise util.Abort(_("%s killed") % command)
418 raise util.Abort(_("%s killed") % command)
419 else:
419 else:
420 transition = "bad"
420 transition = "bad"
421 ctx = cmdutil.revsingle(repo, rev)
421 ctx = cmdutil.revsingle(repo, rev)
422 rev = None # clear for future iterations
422 rev = None # clear for future iterations
423 state[transition].append(ctx.node())
423 state[transition].append(ctx.node())
424 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
424 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
425 check_state(state, interactive=False)
425 check_state(state, interactive=False)
426 # bisect
426 # bisect
427 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
427 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
428 # update to next check
428 # update to next check
429 cmdutil.bail_if_changed(repo)
429 cmdutil.bail_if_changed(repo)
430 hg.clean(repo, nodes[0], show_stats=False)
430 hg.clean(repo, nodes[0], show_stats=False)
431 finally:
431 finally:
432 hbisect.save_state(repo, state)
432 hbisect.save_state(repo, state)
433 print_result(nodes, good)
433 print_result(nodes, good)
434 return
434 return
435
435
436 # update state
436 # update state
437
437
438 if rev:
438 if rev:
439 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
439 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
440 else:
440 else:
441 nodes = [repo.lookup('.')]
441 nodes = [repo.lookup('.')]
442
442
443 if good or bad or skip:
443 if good or bad or skip:
444 if good:
444 if good:
445 state['good'] += nodes
445 state['good'] += nodes
446 elif bad:
446 elif bad:
447 state['bad'] += nodes
447 state['bad'] += nodes
448 elif skip:
448 elif skip:
449 state['skip'] += nodes
449 state['skip'] += nodes
450 hbisect.save_state(repo, state)
450 hbisect.save_state(repo, state)
451
451
452 if not check_state(state):
452 if not check_state(state):
453 return
453 return
454
454
455 # actually bisect
455 # actually bisect
456 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
456 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
457 if extend:
457 if extend:
458 if not changesets:
458 if not changesets:
459 extendnode = extendbisectrange(nodes, good)
459 extendnode = extendbisectrange(nodes, good)
460 if extendnode is not None:
460 if extendnode is not None:
461 ui.write(_("Extending search to changeset %d:%s\n"
461 ui.write(_("Extending search to changeset %d:%s\n"
462 % (extendnode.rev(), short(extendnode.node()))))
462 % (extendnode.rev(), short(extendnode.node()))))
463 if noupdate:
463 if noupdate:
464 return
464 return
465 cmdutil.bail_if_changed(repo)
465 cmdutil.bail_if_changed(repo)
466 return hg.clean(repo, extendnode.node())
466 return hg.clean(repo, extendnode.node())
467 raise util.Abort(_("nothing to extend"))
467 raise util.Abort(_("nothing to extend"))
468
468
469 if changesets == 0:
469 if changesets == 0:
470 print_result(nodes, good)
470 print_result(nodes, good)
471 else:
471 else:
472 assert len(nodes) == 1 # only a single node can be tested next
472 assert len(nodes) == 1 # only a single node can be tested next
473 node = nodes[0]
473 node = nodes[0]
474 # compute the approximate number of remaining tests
474 # compute the approximate number of remaining tests
475 tests, size = 0, 2
475 tests, size = 0, 2
476 while size <= changesets:
476 while size <= changesets:
477 tests, size = tests + 1, size * 2
477 tests, size = tests + 1, size * 2
478 rev = repo.changelog.rev(node)
478 rev = repo.changelog.rev(node)
479 ui.write(_("Testing changeset %d:%s "
479 ui.write(_("Testing changeset %d:%s "
480 "(%d changesets remaining, ~%d tests)\n")
480 "(%d changesets remaining, ~%d tests)\n")
481 % (rev, short(node), changesets, tests))
481 % (rev, short(node), changesets, tests))
482 if not noupdate:
482 if not noupdate:
483 cmdutil.bail_if_changed(repo)
483 cmdutil.bail_if_changed(repo)
484 return hg.clean(repo, node)
484 return hg.clean(repo, node)
485
485
486 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
486 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
487 '''track a line of development with movable markers
487 '''track a line of development with movable markers
488
488
489 Bookmarks are pointers to certain commits that move when
489 Bookmarks are pointers to certain commits that move when
490 committing. Bookmarks are local. They can be renamed, copied and
490 committing. Bookmarks are local. They can be renamed, copied and
491 deleted. It is possible to use bookmark names in :hg:`merge` and
491 deleted. It is possible to use bookmark names in :hg:`merge` and
492 :hg:`update` to merge and update respectively to a given bookmark.
492 :hg:`update` to merge and update respectively to a given bookmark.
493
493
494 You can use :hg:`bookmark NAME` to set a bookmark on the working
494 You can use :hg:`bookmark NAME` to set a bookmark on the working
495 directory's parent revision with the given name. If you specify
495 directory's parent revision with the given name. If you specify
496 a revision using -r REV (where REV may be an existing bookmark),
496 a revision using -r REV (where REV may be an existing bookmark),
497 the bookmark is assigned to that revision.
497 the bookmark is assigned to that revision.
498
498
499 Bookmarks can be pushed and pulled between repositories (see :hg:`help
499 Bookmarks can be pushed and pulled between repositories (see :hg:`help
500 push` and :hg:`help pull`). This requires both the local and remote
500 push` and :hg:`help pull`). This requires both the local and remote
501 repositories to support bookmarks. For versions prior to 1.8, this means
501 repositories to support bookmarks. For versions prior to 1.8, this means
502 the bookmarks extension must be enabled.
502 the bookmarks extension must be enabled.
503 '''
503 '''
504 hexfn = ui.debugflag and hex or short
504 hexfn = ui.debugflag and hex or short
505 marks = repo._bookmarks
505 marks = repo._bookmarks
506 cur = repo.changectx('.').node()
506 cur = repo.changectx('.').node()
507
507
508 if rename:
508 if rename:
509 if rename not in marks:
509 if rename not in marks:
510 raise util.Abort(_("a bookmark of this name does not exist"))
510 raise util.Abort(_("a bookmark of this name does not exist"))
511 if mark in marks and not force:
511 if mark in marks and not force:
512 raise util.Abort(_("a bookmark of the same name already exists"))
512 raise util.Abort(_("a bookmark of the same name already exists"))
513 if mark is None:
513 if mark is None:
514 raise util.Abort(_("new bookmark name required"))
514 raise util.Abort(_("new bookmark name required"))
515 marks[mark] = marks[rename]
515 marks[mark] = marks[rename]
516 if repo._bookmarkcurrent == rename:
516 if repo._bookmarkcurrent == rename:
517 bookmarks.setcurrent(repo, mark)
517 bookmarks.setcurrent(repo, mark)
518 del marks[rename]
518 del marks[rename]
519 bookmarks.write(repo)
519 bookmarks.write(repo)
520 return
520 return
521
521
522 if delete:
522 if delete:
523 if mark is None:
523 if mark is None:
524 raise util.Abort(_("bookmark name required"))
524 raise util.Abort(_("bookmark name required"))
525 if mark not in marks:
525 if mark not in marks:
526 raise util.Abort(_("a bookmark of this name does not exist"))
526 raise util.Abort(_("a bookmark of this name does not exist"))
527 if mark == repo._bookmarkcurrent:
527 if mark == repo._bookmarkcurrent:
528 bookmarks.setcurrent(repo, None)
528 bookmarks.setcurrent(repo, None)
529 del marks[mark]
529 del marks[mark]
530 bookmarks.write(repo)
530 bookmarks.write(repo)
531 return
531 return
532
532
533 if mark is not None:
533 if mark is not None:
534 if "\n" in mark:
534 if "\n" in mark:
535 raise util.Abort(_("bookmark name cannot contain newlines"))
535 raise util.Abort(_("bookmark name cannot contain newlines"))
536 mark = mark.strip()
536 mark = mark.strip()
537 if not mark:
537 if not mark:
538 raise util.Abort(_("bookmark names cannot consist entirely of "
538 raise util.Abort(_("bookmark names cannot consist entirely of "
539 "whitespace"))
539 "whitespace"))
540 if mark in marks and not force:
540 if mark in marks and not force:
541 raise util.Abort(_("a bookmark of the same name already exists"))
541 raise util.Abort(_("a bookmark of the same name already exists"))
542 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
542 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
543 and not force):
543 and not force):
544 raise util.Abort(
544 raise util.Abort(
545 _("a bookmark cannot have the name of an existing branch"))
545 _("a bookmark cannot have the name of an existing branch"))
546 if rev:
546 if rev:
547 marks[mark] = repo.lookup(rev)
547 marks[mark] = repo.lookup(rev)
548 else:
548 else:
549 marks[mark] = repo.changectx('.').node()
549 marks[mark] = repo.changectx('.').node()
550 if repo.changectx('.').node() == marks[mark]:
550 if repo.changectx('.').node() == marks[mark]:
551 bookmarks.setcurrent(repo, mark)
551 bookmarks.setcurrent(repo, mark)
552 bookmarks.write(repo)
552 bookmarks.write(repo)
553 return
553 return
554
554
555 if mark is None:
555 if mark is None:
556 if rev:
556 if rev:
557 raise util.Abort(_("bookmark name required"))
557 raise util.Abort(_("bookmark name required"))
558 if len(marks) == 0:
558 if len(marks) == 0:
559 ui.status(_("no bookmarks set\n"))
559 ui.status(_("no bookmarks set\n"))
560 else:
560 else:
561 for bmark, n in sorted(marks.iteritems()):
561 for bmark, n in sorted(marks.iteritems()):
562 current = repo._bookmarkcurrent
562 current = repo._bookmarkcurrent
563 if bmark == current and n == cur:
563 if bmark == current and n == cur:
564 prefix, label = '*', 'bookmarks.current'
564 prefix, label = '*', 'bookmarks.current'
565 else:
565 else:
566 prefix, label = ' ', ''
566 prefix, label = ' ', ''
567
567
568 if ui.quiet:
568 if ui.quiet:
569 ui.write("%s\n" % bmark, label=label)
569 ui.write("%s\n" % bmark, label=label)
570 else:
570 else:
571 ui.write(" %s %-25s %d:%s\n" % (
571 ui.write(" %s %-25s %d:%s\n" % (
572 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
572 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
573 label=label)
573 label=label)
574 return
574 return
575
575
576 def branch(ui, repo, label=None, **opts):
576 def branch(ui, repo, label=None, **opts):
577 """set or show the current branch name
577 """set or show the current branch name
578
578
579 With no argument, show the current branch name. With one argument,
579 With no argument, show the current branch name. With one argument,
580 set the working directory branch name (the branch will not exist
580 set the working directory branch name (the branch will not exist
581 in the repository until the next commit). Standard practice
581 in the repository until the next commit). Standard practice
582 recommends that primary development take place on the 'default'
582 recommends that primary development take place on the 'default'
583 branch.
583 branch.
584
584
585 Unless -f/--force is specified, branch will not let you set a
585 Unless -f/--force is specified, branch will not let you set a
586 branch name that already exists, even if it's inactive.
586 branch name that already exists, even if it's inactive.
587
587
588 Use -C/--clean to reset the working directory branch to that of
588 Use -C/--clean to reset the working directory branch to that of
589 the parent of the working directory, negating a previous branch
589 the parent of the working directory, negating a previous branch
590 change.
590 change.
591
591
592 Use the command :hg:`update` to switch to an existing branch. Use
592 Use the command :hg:`update` to switch to an existing branch. Use
593 :hg:`commit --close-branch` to mark this branch as closed.
593 :hg:`commit --close-branch` to mark this branch as closed.
594
594
595 Returns 0 on success.
595 Returns 0 on success.
596 """
596 """
597
597
598 if opts.get('clean'):
598 if opts.get('clean'):
599 label = repo[None].parents()[0].branch()
599 label = repo[None].p1().branch()
600 repo.dirstate.setbranch(label)
600 repo.dirstate.setbranch(label)
601 ui.status(_('reset working directory to branch %s\n') % label)
601 ui.status(_('reset working directory to branch %s\n') % label)
602 elif label:
602 elif label:
603 if not opts.get('force') and label in repo.branchtags():
603 if not opts.get('force') and label in repo.branchtags():
604 if label not in [p.branch() for p in repo.parents()]:
604 if label not in [p.branch() for p in repo.parents()]:
605 raise util.Abort(_('a branch of the same name already exists'
605 raise util.Abort(_('a branch of the same name already exists'
606 " (use 'hg update' to switch to it)"))
606 " (use 'hg update' to switch to it)"))
607 repo.dirstate.setbranch(label)
607 repo.dirstate.setbranch(label)
608 ui.status(_('marked working directory as branch %s\n') % label)
608 ui.status(_('marked working directory as branch %s\n') % label)
609 else:
609 else:
610 ui.write("%s\n" % repo.dirstate.branch())
610 ui.write("%s\n" % repo.dirstate.branch())
611
611
612 def branches(ui, repo, active=False, closed=False):
612 def branches(ui, repo, active=False, closed=False):
613 """list repository named branches
613 """list repository named branches
614
614
615 List the repository's named branches, indicating which ones are
615 List the repository's named branches, indicating which ones are
616 inactive. If -c/--closed is specified, also list branches which have
616 inactive. If -c/--closed is specified, also list branches which have
617 been marked closed (see :hg:`commit --close-branch`).
617 been marked closed (see :hg:`commit --close-branch`).
618
618
619 If -a/--active is specified, only show active branches. A branch
619 If -a/--active is specified, only show active branches. A branch
620 is considered active if it contains repository heads.
620 is considered active if it contains repository heads.
621
621
622 Use the command :hg:`update` to switch to an existing branch.
622 Use the command :hg:`update` to switch to an existing branch.
623
623
624 Returns 0.
624 Returns 0.
625 """
625 """
626
626
627 hexfunc = ui.debugflag and hex or short
627 hexfunc = ui.debugflag and hex or short
628 activebranches = [repo[n].branch() for n in repo.heads()]
628 activebranches = [repo[n].branch() for n in repo.heads()]
629 def testactive(tag, node):
629 def testactive(tag, node):
630 realhead = tag in activebranches
630 realhead = tag in activebranches
631 open = node in repo.branchheads(tag, closed=False)
631 open = node in repo.branchheads(tag, closed=False)
632 return realhead and open
632 return realhead and open
633 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
633 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
634 for tag, node in repo.branchtags().items()],
634 for tag, node in repo.branchtags().items()],
635 reverse=True)
635 reverse=True)
636
636
637 for isactive, node, tag in branches:
637 for isactive, node, tag in branches:
638 if (not active) or isactive:
638 if (not active) or isactive:
639 if ui.quiet:
639 if ui.quiet:
640 ui.write("%s\n" % tag)
640 ui.write("%s\n" % tag)
641 else:
641 else:
642 hn = repo.lookup(node)
642 hn = repo.lookup(node)
643 if isactive:
643 if isactive:
644 label = 'branches.active'
644 label = 'branches.active'
645 notice = ''
645 notice = ''
646 elif hn not in repo.branchheads(tag, closed=False):
646 elif hn not in repo.branchheads(tag, closed=False):
647 if not closed:
647 if not closed:
648 continue
648 continue
649 label = 'branches.closed'
649 label = 'branches.closed'
650 notice = _(' (closed)')
650 notice = _(' (closed)')
651 else:
651 else:
652 label = 'branches.inactive'
652 label = 'branches.inactive'
653 notice = _(' (inactive)')
653 notice = _(' (inactive)')
654 if tag == repo.dirstate.branch():
654 if tag == repo.dirstate.branch():
655 label = 'branches.current'
655 label = 'branches.current'
656 rev = str(node).rjust(31 - encoding.colwidth(tag))
656 rev = str(node).rjust(31 - encoding.colwidth(tag))
657 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
657 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
658 tag = ui.label(tag, label)
658 tag = ui.label(tag, label)
659 ui.write("%s %s%s\n" % (tag, rev, notice))
659 ui.write("%s %s%s\n" % (tag, rev, notice))
660
660
661 def bundle(ui, repo, fname, dest=None, **opts):
661 def bundle(ui, repo, fname, dest=None, **opts):
662 """create a changegroup file
662 """create a changegroup file
663
663
664 Generate a compressed changegroup file collecting changesets not
664 Generate a compressed changegroup file collecting changesets not
665 known to be in another repository.
665 known to be in another repository.
666
666
667 If you omit the destination repository, then hg assumes the
667 If you omit the destination repository, then hg assumes the
668 destination will have all the nodes you specify with --base
668 destination will have all the nodes you specify with --base
669 parameters. To create a bundle containing all changesets, use
669 parameters. To create a bundle containing all changesets, use
670 -a/--all (or --base null).
670 -a/--all (or --base null).
671
671
672 You can change compression method with the -t/--type option.
672 You can change compression method with the -t/--type option.
673 The available compression methods are: none, bzip2, and
673 The available compression methods are: none, bzip2, and
674 gzip (by default, bundles are compressed using bzip2).
674 gzip (by default, bundles are compressed using bzip2).
675
675
676 The bundle file can then be transferred using conventional means
676 The bundle file can then be transferred using conventional means
677 and applied to another repository with the unbundle or pull
677 and applied to another repository with the unbundle or pull
678 command. This is useful when direct push and pull are not
678 command. This is useful when direct push and pull are not
679 available or when exporting an entire repository is undesirable.
679 available or when exporting an entire repository is undesirable.
680
680
681 Applying bundles preserves all changeset contents including
681 Applying bundles preserves all changeset contents including
682 permissions, copy/rename information, and revision history.
682 permissions, copy/rename information, and revision history.
683
683
684 Returns 0 on success, 1 if no changes found.
684 Returns 0 on success, 1 if no changes found.
685 """
685 """
686 revs = None
686 revs = None
687 if 'rev' in opts:
687 if 'rev' in opts:
688 revs = cmdutil.revrange(repo, opts['rev'])
688 revs = cmdutil.revrange(repo, opts['rev'])
689
689
690 if opts.get('all'):
690 if opts.get('all'):
691 base = ['null']
691 base = ['null']
692 else:
692 else:
693 base = cmdutil.revrange(repo, opts.get('base'))
693 base = cmdutil.revrange(repo, opts.get('base'))
694 if base:
694 if base:
695 if dest:
695 if dest:
696 raise util.Abort(_("--base is incompatible with specifying "
696 raise util.Abort(_("--base is incompatible with specifying "
697 "a destination"))
697 "a destination"))
698 base = [repo.lookup(rev) for rev in base]
698 base = [repo.lookup(rev) for rev in base]
699 # create the right base
699 # create the right base
700 # XXX: nodesbetween / changegroup* should be "fixed" instead
700 # XXX: nodesbetween / changegroup* should be "fixed" instead
701 o = []
701 o = []
702 has = set((nullid,))
702 has = set((nullid,))
703 for n in base:
703 for n in base:
704 has.update(repo.changelog.reachable(n))
704 has.update(repo.changelog.reachable(n))
705 if revs:
705 if revs:
706 revs = [repo.lookup(rev) for rev in revs]
706 revs = [repo.lookup(rev) for rev in revs]
707 visit = revs[:]
707 visit = revs[:]
708 has.difference_update(visit)
708 has.difference_update(visit)
709 else:
709 else:
710 visit = repo.changelog.heads()
710 visit = repo.changelog.heads()
711 seen = {}
711 seen = {}
712 while visit:
712 while visit:
713 n = visit.pop(0)
713 n = visit.pop(0)
714 parents = [p for p in repo.changelog.parents(n) if p not in has]
714 parents = [p for p in repo.changelog.parents(n) if p not in has]
715 if len(parents) == 0:
715 if len(parents) == 0:
716 if n not in has:
716 if n not in has:
717 o.append(n)
717 o.append(n)
718 else:
718 else:
719 for p in parents:
719 for p in parents:
720 if p not in seen:
720 if p not in seen:
721 seen[p] = 1
721 seen[p] = 1
722 visit.append(p)
722 visit.append(p)
723 else:
723 else:
724 dest = ui.expandpath(dest or 'default-push', dest or 'default')
724 dest = ui.expandpath(dest or 'default-push', dest or 'default')
725 dest, branches = hg.parseurl(dest, opts.get('branch'))
725 dest, branches = hg.parseurl(dest, opts.get('branch'))
726 other = hg.repository(hg.remoteui(repo, opts), dest)
726 other = hg.repository(hg.remoteui(repo, opts), dest)
727 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
727 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
728 if revs:
728 if revs:
729 revs = [repo.lookup(rev) for rev in revs]
729 revs = [repo.lookup(rev) for rev in revs]
730 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
730 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
731
731
732 if not o:
732 if not o:
733 ui.status(_("no changes found\n"))
733 ui.status(_("no changes found\n"))
734 return 1
734 return 1
735
735
736 if revs:
736 if revs:
737 cg = repo.changegroupsubset(o, revs, 'bundle')
737 cg = repo.changegroupsubset(o, revs, 'bundle')
738 else:
738 else:
739 cg = repo.changegroup(o, 'bundle')
739 cg = repo.changegroup(o, 'bundle')
740
740
741 bundletype = opts.get('type', 'bzip2').lower()
741 bundletype = opts.get('type', 'bzip2').lower()
742 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
742 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
743 bundletype = btypes.get(bundletype)
743 bundletype = btypes.get(bundletype)
744 if bundletype not in changegroup.bundletypes:
744 if bundletype not in changegroup.bundletypes:
745 raise util.Abort(_('unknown bundle type specified with --type'))
745 raise util.Abort(_('unknown bundle type specified with --type'))
746
746
747 changegroup.writebundle(cg, fname, bundletype)
747 changegroup.writebundle(cg, fname, bundletype)
748
748
749 def cat(ui, repo, file1, *pats, **opts):
749 def cat(ui, repo, file1, *pats, **opts):
750 """output the current or given revision of files
750 """output the current or given revision of files
751
751
752 Print the specified files as they were at the given revision. If
752 Print the specified files as they were at the given revision. If
753 no revision is given, the parent of the working directory is used,
753 no revision is given, the parent of the working directory is used,
754 or tip if no revision is checked out.
754 or tip if no revision is checked out.
755
755
756 Output may be to a file, in which case the name of the file is
756 Output may be to a file, in which case the name of the file is
757 given using a format string. The formatting rules are the same as
757 given using a format string. The formatting rules are the same as
758 for the export command, with the following additions:
758 for the export command, with the following additions:
759
759
760 :``%s``: basename of file being printed
760 :``%s``: basename of file being printed
761 :``%d``: dirname of file being printed, or '.' if in repository root
761 :``%d``: dirname of file being printed, or '.' if in repository root
762 :``%p``: root-relative path name of file being printed
762 :``%p``: root-relative path name of file being printed
763
763
764 Returns 0 on success.
764 Returns 0 on success.
765 """
765 """
766 ctx = cmdutil.revsingle(repo, opts.get('rev'))
766 ctx = cmdutil.revsingle(repo, opts.get('rev'))
767 err = 1
767 err = 1
768 m = cmdutil.match(repo, (file1,) + pats, opts)
768 m = cmdutil.match(repo, (file1,) + pats, opts)
769 for abs in ctx.walk(m):
769 for abs in ctx.walk(m):
770 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
770 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
771 data = ctx[abs].data()
771 data = ctx[abs].data()
772 if opts.get('decode'):
772 if opts.get('decode'):
773 data = repo.wwritedata(abs, data)
773 data = repo.wwritedata(abs, data)
774 fp.write(data)
774 fp.write(data)
775 fp.close()
775 fp.close()
776 err = 0
776 err = 0
777 return err
777 return err
778
778
779 def clone(ui, source, dest=None, **opts):
779 def clone(ui, source, dest=None, **opts):
780 """make a copy of an existing repository
780 """make a copy of an existing repository
781
781
782 Create a copy of an existing repository in a new directory.
782 Create a copy of an existing repository in a new directory.
783
783
784 If no destination directory name is specified, it defaults to the
784 If no destination directory name is specified, it defaults to the
785 basename of the source.
785 basename of the source.
786
786
787 The location of the source is added to the new repository's
787 The location of the source is added to the new repository's
788 ``.hg/hgrc`` file, as the default to be used for future pulls.
788 ``.hg/hgrc`` file, as the default to be used for future pulls.
789
789
790 See :hg:`help urls` for valid source format details.
790 See :hg:`help urls` for valid source format details.
791
791
792 It is possible to specify an ``ssh://`` URL as the destination, but no
792 It is possible to specify an ``ssh://`` URL as the destination, but no
793 ``.hg/hgrc`` and working directory will be created on the remote side.
793 ``.hg/hgrc`` and working directory will be created on the remote side.
794 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
794 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
795
795
796 A set of changesets (tags, or branch names) to pull may be specified
796 A set of changesets (tags, or branch names) to pull may be specified
797 by listing each changeset (tag, or branch name) with -r/--rev.
797 by listing each changeset (tag, or branch name) with -r/--rev.
798 If -r/--rev is used, the cloned repository will contain only a subset
798 If -r/--rev is used, the cloned repository will contain only a subset
799 of the changesets of the source repository. Only the set of changesets
799 of the changesets of the source repository. Only the set of changesets
800 defined by all -r/--rev options (including all their ancestors)
800 defined by all -r/--rev options (including all their ancestors)
801 will be pulled into the destination repository.
801 will be pulled into the destination repository.
802 No subsequent changesets (including subsequent tags) will be present
802 No subsequent changesets (including subsequent tags) will be present
803 in the destination.
803 in the destination.
804
804
805 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
805 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
806 local source repositories.
806 local source repositories.
807
807
808 For efficiency, hardlinks are used for cloning whenever the source
808 For efficiency, hardlinks are used for cloning whenever the source
809 and destination are on the same filesystem (note this applies only
809 and destination are on the same filesystem (note this applies only
810 to the repository data, not to the working directory). Some
810 to the repository data, not to the working directory). Some
811 filesystems, such as AFS, implement hardlinking incorrectly, but
811 filesystems, such as AFS, implement hardlinking incorrectly, but
812 do not report errors. In these cases, use the --pull option to
812 do not report errors. In these cases, use the --pull option to
813 avoid hardlinking.
813 avoid hardlinking.
814
814
815 In some cases, you can clone repositories and the working directory
815 In some cases, you can clone repositories and the working directory
816 using full hardlinks with ::
816 using full hardlinks with ::
817
817
818 $ cp -al REPO REPOCLONE
818 $ cp -al REPO REPOCLONE
819
819
820 This is the fastest way to clone, but it is not always safe. The
820 This is the fastest way to clone, but it is not always safe. The
821 operation is not atomic (making sure REPO is not modified during
821 operation is not atomic (making sure REPO is not modified during
822 the operation is up to you) and you have to make sure your editor
822 the operation is up to you) and you have to make sure your editor
823 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
823 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
824 this is not compatible with certain extensions that place their
824 this is not compatible with certain extensions that place their
825 metadata under the .hg directory, such as mq.
825 metadata under the .hg directory, such as mq.
826
826
827 Mercurial will update the working directory to the first applicable
827 Mercurial will update the working directory to the first applicable
828 revision from this list:
828 revision from this list:
829
829
830 a) null if -U or the source repository has no changesets
830 a) null if -U or the source repository has no changesets
831 b) if -u . and the source repository is local, the first parent of
831 b) if -u . and the source repository is local, the first parent of
832 the source repository's working directory
832 the source repository's working directory
833 c) the changeset specified with -u (if a branch name, this means the
833 c) the changeset specified with -u (if a branch name, this means the
834 latest head of that branch)
834 latest head of that branch)
835 d) the changeset specified with -r
835 d) the changeset specified with -r
836 e) the tipmost head specified with -b
836 e) the tipmost head specified with -b
837 f) the tipmost head specified with the url#branch source syntax
837 f) the tipmost head specified with the url#branch source syntax
838 g) the tipmost head of the default branch
838 g) the tipmost head of the default branch
839 h) tip
839 h) tip
840
840
841 Returns 0 on success.
841 Returns 0 on success.
842 """
842 """
843 if opts.get('noupdate') and opts.get('updaterev'):
843 if opts.get('noupdate') and opts.get('updaterev'):
844 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
844 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
845
845
846 r = hg.clone(hg.remoteui(ui, opts), source, dest,
846 r = hg.clone(hg.remoteui(ui, opts), source, dest,
847 pull=opts.get('pull'),
847 pull=opts.get('pull'),
848 stream=opts.get('uncompressed'),
848 stream=opts.get('uncompressed'),
849 rev=opts.get('rev'),
849 rev=opts.get('rev'),
850 update=opts.get('updaterev') or not opts.get('noupdate'),
850 update=opts.get('updaterev') or not opts.get('noupdate'),
851 branch=opts.get('branch'))
851 branch=opts.get('branch'))
852
852
853 return r is None
853 return r is None
854
854
855 def commit(ui, repo, *pats, **opts):
855 def commit(ui, repo, *pats, **opts):
856 """commit the specified files or all outstanding changes
856 """commit the specified files or all outstanding changes
857
857
858 Commit changes to the given files into the repository. Unlike a
858 Commit changes to the given files into the repository. Unlike a
859 centralized SCM, this operation is a local operation. See
859 centralized SCM, this operation is a local operation. See
860 :hg:`push` for a way to actively distribute your changes.
860 :hg:`push` for a way to actively distribute your changes.
861
861
862 If a list of files is omitted, all changes reported by :hg:`status`
862 If a list of files is omitted, all changes reported by :hg:`status`
863 will be committed.
863 will be committed.
864
864
865 If you are committing the result of a merge, do not provide any
865 If you are committing the result of a merge, do not provide any
866 filenames or -I/-X filters.
866 filenames or -I/-X filters.
867
867
868 If no commit message is specified, Mercurial starts your
868 If no commit message is specified, Mercurial starts your
869 configured editor where you can enter a message. In case your
869 configured editor where you can enter a message. In case your
870 commit fails, you will find a backup of your message in
870 commit fails, you will find a backup of your message in
871 ``.hg/last-message.txt``.
871 ``.hg/last-message.txt``.
872
872
873 See :hg:`help dates` for a list of formats valid for -d/--date.
873 See :hg:`help dates` for a list of formats valid for -d/--date.
874
874
875 Returns 0 on success, 1 if nothing changed.
875 Returns 0 on success, 1 if nothing changed.
876 """
876 """
877 extra = {}
877 extra = {}
878 if opts.get('close_branch'):
878 if opts.get('close_branch'):
879 if repo['.'].node() not in repo.branchheads():
879 if repo['.'].node() not in repo.branchheads():
880 # The topo heads set is included in the branch heads set of the
880 # The topo heads set is included in the branch heads set of the
881 # current branch, so it's sufficient to test branchheads
881 # current branch, so it's sufficient to test branchheads
882 raise util.Abort(_('can only close branch heads'))
882 raise util.Abort(_('can only close branch heads'))
883 extra['close'] = 1
883 extra['close'] = 1
884 e = cmdutil.commiteditor
884 e = cmdutil.commiteditor
885 if opts.get('force_editor'):
885 if opts.get('force_editor'):
886 e = cmdutil.commitforceeditor
886 e = cmdutil.commitforceeditor
887
887
888 def commitfunc(ui, repo, message, match, opts):
888 def commitfunc(ui, repo, message, match, opts):
889 return repo.commit(message, opts.get('user'), opts.get('date'), match,
889 return repo.commit(message, opts.get('user'), opts.get('date'), match,
890 editor=e, extra=extra)
890 editor=e, extra=extra)
891
891
892 branch = repo[None].branch()
892 branch = repo[None].branch()
893 bheads = repo.branchheads(branch)
893 bheads = repo.branchheads(branch)
894
894
895 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
895 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
896 if not node:
896 if not node:
897 ui.status(_("nothing changed\n"))
897 ui.status(_("nothing changed\n"))
898 return 1
898 return 1
899
899
900 ctx = repo[node]
900 ctx = repo[node]
901 parents = ctx.parents()
901 parents = ctx.parents()
902
902
903 if bheads and not [x for x in parents
903 if bheads and not [x for x in parents
904 if x.node() in bheads and x.branch() == branch]:
904 if x.node() in bheads and x.branch() == branch]:
905 ui.status(_('created new head\n'))
905 ui.status(_('created new head\n'))
906 # The message is not printed for initial roots. For the other
906 # The message is not printed for initial roots. For the other
907 # changesets, it is printed in the following situations:
907 # changesets, it is printed in the following situations:
908 #
908 #
909 # Par column: for the 2 parents with ...
909 # Par column: for the 2 parents with ...
910 # N: null or no parent
910 # N: null or no parent
911 # B: parent is on another named branch
911 # B: parent is on another named branch
912 # C: parent is a regular non head changeset
912 # C: parent is a regular non head changeset
913 # H: parent was a branch head of the current branch
913 # H: parent was a branch head of the current branch
914 # Msg column: whether we print "created new head" message
914 # Msg column: whether we print "created new head" message
915 # In the following, it is assumed that there already exists some
915 # In the following, it is assumed that there already exists some
916 # initial branch heads of the current branch, otherwise nothing is
916 # initial branch heads of the current branch, otherwise nothing is
917 # printed anyway.
917 # printed anyway.
918 #
918 #
919 # Par Msg Comment
919 # Par Msg Comment
920 # NN y additional topo root
920 # NN y additional topo root
921 #
921 #
922 # BN y additional branch root
922 # BN y additional branch root
923 # CN y additional topo head
923 # CN y additional topo head
924 # HN n usual case
924 # HN n usual case
925 #
925 #
926 # BB y weird additional branch root
926 # BB y weird additional branch root
927 # CB y branch merge
927 # CB y branch merge
928 # HB n merge with named branch
928 # HB n merge with named branch
929 #
929 #
930 # CC y additional head from merge
930 # CC y additional head from merge
931 # CH n merge with a head
931 # CH n merge with a head
932 #
932 #
933 # HH n head merge: head count decreases
933 # HH n head merge: head count decreases
934
934
935 if not opts.get('close_branch'):
935 if not opts.get('close_branch'):
936 for r in parents:
936 for r in parents:
937 if r.extra().get('close') and r.branch() == branch:
937 if r.extra().get('close') and r.branch() == branch:
938 ui.status(_('reopening closed branch head %d\n') % r)
938 ui.status(_('reopening closed branch head %d\n') % r)
939
939
940 if ui.debugflag:
940 if ui.debugflag:
941 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
941 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
942 elif ui.verbose:
942 elif ui.verbose:
943 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
943 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
944
944
945 def copy(ui, repo, *pats, **opts):
945 def copy(ui, repo, *pats, **opts):
946 """mark files as copied for the next commit
946 """mark files as copied for the next commit
947
947
948 Mark dest as having copies of source files. If dest is a
948 Mark dest as having copies of source files. If dest is a
949 directory, copies are put in that directory. If dest is a file,
949 directory, copies are put in that directory. If dest is a file,
950 the source must be a single file.
950 the source must be a single file.
951
951
952 By default, this command copies the contents of files as they
952 By default, this command copies the contents of files as they
953 exist in the working directory. If invoked with -A/--after, the
953 exist in the working directory. If invoked with -A/--after, the
954 operation is recorded, but no copying is performed.
954 operation is recorded, but no copying is performed.
955
955
956 This command takes effect with the next commit. To undo a copy
956 This command takes effect with the next commit. To undo a copy
957 before that, see :hg:`revert`.
957 before that, see :hg:`revert`.
958
958
959 Returns 0 on success, 1 if errors are encountered.
959 Returns 0 on success, 1 if errors are encountered.
960 """
960 """
961 wlock = repo.wlock(False)
961 wlock = repo.wlock(False)
962 try:
962 try:
963 return cmdutil.copy(ui, repo, pats, opts)
963 return cmdutil.copy(ui, repo, pats, opts)
964 finally:
964 finally:
965 wlock.release()
965 wlock.release()
966
966
967 def debugancestor(ui, repo, *args):
967 def debugancestor(ui, repo, *args):
968 """find the ancestor revision of two revisions in a given index"""
968 """find the ancestor revision of two revisions in a given index"""
969 if len(args) == 3:
969 if len(args) == 3:
970 index, rev1, rev2 = args
970 index, rev1, rev2 = args
971 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
971 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
972 lookup = r.lookup
972 lookup = r.lookup
973 elif len(args) == 2:
973 elif len(args) == 2:
974 if not repo:
974 if not repo:
975 raise util.Abort(_("there is no Mercurial repository here "
975 raise util.Abort(_("there is no Mercurial repository here "
976 "(.hg not found)"))
976 "(.hg not found)"))
977 rev1, rev2 = args
977 rev1, rev2 = args
978 r = repo.changelog
978 r = repo.changelog
979 lookup = repo.lookup
979 lookup = repo.lookup
980 else:
980 else:
981 raise util.Abort(_('either two or three arguments required'))
981 raise util.Abort(_('either two or three arguments required'))
982 a = r.ancestor(lookup(rev1), lookup(rev2))
982 a = r.ancestor(lookup(rev1), lookup(rev2))
983 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
983 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
984
984
985 def debugbuilddag(ui, repo, text,
985 def debugbuilddag(ui, repo, text,
986 mergeable_file=False,
986 mergeable_file=False,
987 appended_file=False,
987 appended_file=False,
988 overwritten_file=False,
988 overwritten_file=False,
989 new_file=False):
989 new_file=False):
990 """builds a repo with a given dag from scratch in the current empty repo
990 """builds a repo with a given dag from scratch in the current empty repo
991
991
992 Elements:
992 Elements:
993
993
994 - "+n" is a linear run of n nodes based on the current default parent
994 - "+n" is a linear run of n nodes based on the current default parent
995 - "." is a single node based on the current default parent
995 - "." is a single node based on the current default parent
996 - "$" resets the default parent to null (implied at the start);
996 - "$" resets the default parent to null (implied at the start);
997 otherwise the default parent is always the last node created
997 otherwise the default parent is always the last node created
998 - "<p" sets the default parent to the backref p
998 - "<p" sets the default parent to the backref p
999 - "*p" is a fork at parent p, which is a backref
999 - "*p" is a fork at parent p, which is a backref
1000 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1000 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1001 - "/p2" is a merge of the preceding node and p2
1001 - "/p2" is a merge of the preceding node and p2
1002 - ":tag" defines a local tag for the preceding node
1002 - ":tag" defines a local tag for the preceding node
1003 - "@branch" sets the named branch for subsequent nodes
1003 - "@branch" sets the named branch for subsequent nodes
1004 - "!command" runs the command using your shell
1004 - "!command" runs the command using your shell
1005 - "!!my command\\n" is like "!", but to the end of the line
1005 - "!!my command\\n" is like "!", but to the end of the line
1006 - "#...\\n" is a comment up to the end of the line
1006 - "#...\\n" is a comment up to the end of the line
1007
1007
1008 Whitespace between the above elements is ignored.
1008 Whitespace between the above elements is ignored.
1009
1009
1010 A backref is either
1010 A backref is either
1011
1011
1012 - a number n, which references the node curr-n, where curr is the current
1012 - a number n, which references the node curr-n, where curr is the current
1013 node, or
1013 node, or
1014 - the name of a local tag you placed earlier using ":tag", or
1014 - the name of a local tag you placed earlier using ":tag", or
1015 - empty to denote the default parent.
1015 - empty to denote the default parent.
1016
1016
1017 All string valued-elements are either strictly alphanumeric, or must
1017 All string valued-elements are either strictly alphanumeric, or must
1018 be enclosed in double quotes ("..."), with "\\" as escape character.
1018 be enclosed in double quotes ("..."), with "\\" as escape character.
1019
1019
1020 Note that the --overwritten-file and --appended-file options imply the
1020 Note that the --overwritten-file and --appended-file options imply the
1021 use of "HGMERGE=internal:local" during DAG buildup.
1021 use of "HGMERGE=internal:local" during DAG buildup.
1022 """
1022 """
1023
1023
1024 if not (mergeable_file or appended_file or overwritten_file or new_file):
1024 if not (mergeable_file or appended_file or overwritten_file or new_file):
1025 raise util.Abort(_('need at least one of -m, -a, -o, -n'))
1025 raise util.Abort(_('need at least one of -m, -a, -o, -n'))
1026
1026
1027 if len(repo.changelog) > 0:
1027 if len(repo.changelog) > 0:
1028 raise util.Abort(_('repository is not empty'))
1028 raise util.Abort(_('repository is not empty'))
1029
1029
1030 if overwritten_file or appended_file:
1030 if overwritten_file or appended_file:
1031 # we don't want to fail in merges during buildup
1031 # we don't want to fail in merges during buildup
1032 os.environ['HGMERGE'] = 'internal:local'
1032 os.environ['HGMERGE'] = 'internal:local'
1033
1033
1034 def writefile(fname, text, fmode="wb"):
1034 def writefile(fname, text, fmode="wb"):
1035 f = open(fname, fmode)
1035 f = open(fname, fmode)
1036 try:
1036 try:
1037 f.write(text)
1037 f.write(text)
1038 finally:
1038 finally:
1039 f.close()
1039 f.close()
1040
1040
1041 if mergeable_file:
1041 if mergeable_file:
1042 linesperrev = 2
1042 linesperrev = 2
1043 # determine number of revs in DAG
1043 # determine number of revs in DAG
1044 n = 0
1044 n = 0
1045 for type, data in dagparser.parsedag(text):
1045 for type, data in dagparser.parsedag(text):
1046 if type == 'n':
1046 if type == 'n':
1047 n += 1
1047 n += 1
1048 # make a file with k lines per rev
1048 # make a file with k lines per rev
1049 writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev))
1049 writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev))
1050 + "\n")
1050 + "\n")
1051
1051
1052 at = -1
1052 at = -1
1053 atbranch = 'default'
1053 atbranch = 'default'
1054 for type, data in dagparser.parsedag(text):
1054 for type, data in dagparser.parsedag(text):
1055 if type == 'n':
1055 if type == 'n':
1056 ui.status('node %s\n' % str(data))
1056 ui.status('node %s\n' % str(data))
1057 id, ps = data
1057 id, ps = data
1058 p1 = ps[0]
1058 p1 = ps[0]
1059 if p1 != at:
1059 if p1 != at:
1060 update(ui, repo, node=str(p1), clean=True)
1060 update(ui, repo, node=str(p1), clean=True)
1061 at = p1
1061 at = p1
1062 if repo.dirstate.branch() != atbranch:
1062 if repo.dirstate.branch() != atbranch:
1063 branch(ui, repo, atbranch, force=True)
1063 branch(ui, repo, atbranch, force=True)
1064 if len(ps) > 1:
1064 if len(ps) > 1:
1065 p2 = ps[1]
1065 p2 = ps[1]
1066 merge(ui, repo, node=p2)
1066 merge(ui, repo, node=p2)
1067
1067
1068 if mergeable_file:
1068 if mergeable_file:
1069 f = open("mf", "rb+")
1069 f = open("mf", "rb+")
1070 try:
1070 try:
1071 lines = f.read().split("\n")
1071 lines = f.read().split("\n")
1072 lines[id * linesperrev] += " r%i" % id
1072 lines[id * linesperrev] += " r%i" % id
1073 f.seek(0)
1073 f.seek(0)
1074 f.write("\n".join(lines))
1074 f.write("\n".join(lines))
1075 finally:
1075 finally:
1076 f.close()
1076 f.close()
1077
1077
1078 if appended_file:
1078 if appended_file:
1079 writefile("af", "r%i\n" % id, "ab")
1079 writefile("af", "r%i\n" % id, "ab")
1080
1080
1081 if overwritten_file:
1081 if overwritten_file:
1082 writefile("of", "r%i\n" % id)
1082 writefile("of", "r%i\n" % id)
1083
1083
1084 if new_file:
1084 if new_file:
1085 writefile("nf%i" % id, "r%i\n" % id)
1085 writefile("nf%i" % id, "r%i\n" % id)
1086
1086
1087 commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0))
1087 commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0))
1088 at = id
1088 at = id
1089 elif type == 'l':
1089 elif type == 'l':
1090 id, name = data
1090 id, name = data
1091 ui.status('tag %s\n' % name)
1091 ui.status('tag %s\n' % name)
1092 tag(ui, repo, name, local=True)
1092 tag(ui, repo, name, local=True)
1093 elif type == 'a':
1093 elif type == 'a':
1094 ui.status('branch %s\n' % data)
1094 ui.status('branch %s\n' % data)
1095 atbranch = data
1095 atbranch = data
1096 elif type in 'cC':
1096 elif type in 'cC':
1097 r = util.system(data, cwd=repo.root)
1097 r = util.system(data, cwd=repo.root)
1098 if r:
1098 if r:
1099 desc, r = util.explain_exit(r)
1099 desc, r = util.explain_exit(r)
1100 raise util.Abort(_('%s command %s') % (data, desc))
1100 raise util.Abort(_('%s command %s') % (data, desc))
1101
1101
1102 def debugcommands(ui, cmd='', *args):
1102 def debugcommands(ui, cmd='', *args):
1103 """list all available commands and options"""
1103 """list all available commands and options"""
1104 for cmd, vals in sorted(table.iteritems()):
1104 for cmd, vals in sorted(table.iteritems()):
1105 cmd = cmd.split('|')[0].strip('^')
1105 cmd = cmd.split('|')[0].strip('^')
1106 opts = ', '.join([i[1] for i in vals[1]])
1106 opts = ', '.join([i[1] for i in vals[1]])
1107 ui.write('%s: %s\n' % (cmd, opts))
1107 ui.write('%s: %s\n' % (cmd, opts))
1108
1108
1109 def debugcomplete(ui, cmd='', **opts):
1109 def debugcomplete(ui, cmd='', **opts):
1110 """returns the completion list associated with the given command"""
1110 """returns the completion list associated with the given command"""
1111
1111
1112 if opts.get('options'):
1112 if opts.get('options'):
1113 options = []
1113 options = []
1114 otables = [globalopts]
1114 otables = [globalopts]
1115 if cmd:
1115 if cmd:
1116 aliases, entry = cmdutil.findcmd(cmd, table, False)
1116 aliases, entry = cmdutil.findcmd(cmd, table, False)
1117 otables.append(entry[1])
1117 otables.append(entry[1])
1118 for t in otables:
1118 for t in otables:
1119 for o in t:
1119 for o in t:
1120 if "(DEPRECATED)" in o[3]:
1120 if "(DEPRECATED)" in o[3]:
1121 continue
1121 continue
1122 if o[0]:
1122 if o[0]:
1123 options.append('-%s' % o[0])
1123 options.append('-%s' % o[0])
1124 options.append('--%s' % o[1])
1124 options.append('--%s' % o[1])
1125 ui.write("%s\n" % "\n".join(options))
1125 ui.write("%s\n" % "\n".join(options))
1126 return
1126 return
1127
1127
1128 cmdlist = cmdutil.findpossible(cmd, table)
1128 cmdlist = cmdutil.findpossible(cmd, table)
1129 if ui.verbose:
1129 if ui.verbose:
1130 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1130 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1131 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1131 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1132
1132
1133 def debugfsinfo(ui, path = "."):
1133 def debugfsinfo(ui, path = "."):
1134 """show information detected about current filesystem"""
1134 """show information detected about current filesystem"""
1135 open('.debugfsinfo', 'w').write('')
1135 open('.debugfsinfo', 'w').write('')
1136 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1136 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1137 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1137 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1138 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1138 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1139 and 'yes' or 'no'))
1139 and 'yes' or 'no'))
1140 os.unlink('.debugfsinfo')
1140 os.unlink('.debugfsinfo')
1141
1141
1142 def debugrebuildstate(ui, repo, rev="tip"):
1142 def debugrebuildstate(ui, repo, rev="tip"):
1143 """rebuild the dirstate as it would look like for the given revision"""
1143 """rebuild the dirstate as it would look like for the given revision"""
1144 ctx = cmdutil.revsingle(repo, rev)
1144 ctx = cmdutil.revsingle(repo, rev)
1145 wlock = repo.wlock()
1145 wlock = repo.wlock()
1146 try:
1146 try:
1147 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1147 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1148 finally:
1148 finally:
1149 wlock.release()
1149 wlock.release()
1150
1150
1151 def debugcheckstate(ui, repo):
1151 def debugcheckstate(ui, repo):
1152 """validate the correctness of the current dirstate"""
1152 """validate the correctness of the current dirstate"""
1153 parent1, parent2 = repo.dirstate.parents()
1153 parent1, parent2 = repo.dirstate.parents()
1154 m1 = repo[parent1].manifest()
1154 m1 = repo[parent1].manifest()
1155 m2 = repo[parent2].manifest()
1155 m2 = repo[parent2].manifest()
1156 errors = 0
1156 errors = 0
1157 for f in repo.dirstate:
1157 for f in repo.dirstate:
1158 state = repo.dirstate[f]
1158 state = repo.dirstate[f]
1159 if state in "nr" and f not in m1:
1159 if state in "nr" and f not in m1:
1160 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1160 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1161 errors += 1
1161 errors += 1
1162 if state in "a" and f in m1:
1162 if state in "a" and f in m1:
1163 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1163 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1164 errors += 1
1164 errors += 1
1165 if state in "m" and f not in m1 and f not in m2:
1165 if state in "m" and f not in m1 and f not in m2:
1166 ui.warn(_("%s in state %s, but not in either manifest\n") %
1166 ui.warn(_("%s in state %s, but not in either manifest\n") %
1167 (f, state))
1167 (f, state))
1168 errors += 1
1168 errors += 1
1169 for f in m1:
1169 for f in m1:
1170 state = repo.dirstate[f]
1170 state = repo.dirstate[f]
1171 if state not in "nrm":
1171 if state not in "nrm":
1172 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1172 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1173 errors += 1
1173 errors += 1
1174 if errors:
1174 if errors:
1175 error = _(".hg/dirstate inconsistent with current parent's manifest")
1175 error = _(".hg/dirstate inconsistent with current parent's manifest")
1176 raise util.Abort(error)
1176 raise util.Abort(error)
1177
1177
1178 def showconfig(ui, repo, *values, **opts):
1178 def showconfig(ui, repo, *values, **opts):
1179 """show combined config settings from all hgrc files
1179 """show combined config settings from all hgrc files
1180
1180
1181 With no arguments, print names and values of all config items.
1181 With no arguments, print names and values of all config items.
1182
1182
1183 With one argument of the form section.name, print just the value
1183 With one argument of the form section.name, print just the value
1184 of that config item.
1184 of that config item.
1185
1185
1186 With multiple arguments, print names and values of all config
1186 With multiple arguments, print names and values of all config
1187 items with matching section names.
1187 items with matching section names.
1188
1188
1189 With --debug, the source (filename and line number) is printed
1189 With --debug, the source (filename and line number) is printed
1190 for each config item.
1190 for each config item.
1191
1191
1192 Returns 0 on success.
1192 Returns 0 on success.
1193 """
1193 """
1194
1194
1195 for f in util.rcpath():
1195 for f in util.rcpath():
1196 ui.debug(_('read config from: %s\n') % f)
1196 ui.debug(_('read config from: %s\n') % f)
1197 untrusted = bool(opts.get('untrusted'))
1197 untrusted = bool(opts.get('untrusted'))
1198 if values:
1198 if values:
1199 sections = [v for v in values if '.' not in v]
1199 sections = [v for v in values if '.' not in v]
1200 items = [v for v in values if '.' in v]
1200 items = [v for v in values if '.' in v]
1201 if len(items) > 1 or items and sections:
1201 if len(items) > 1 or items and sections:
1202 raise util.Abort(_('only one config item permitted'))
1202 raise util.Abort(_('only one config item permitted'))
1203 for section, name, value in ui.walkconfig(untrusted=untrusted):
1203 for section, name, value in ui.walkconfig(untrusted=untrusted):
1204 value = str(value).replace('\n', '\\n')
1204 value = str(value).replace('\n', '\\n')
1205 sectname = section + '.' + name
1205 sectname = section + '.' + name
1206 if values:
1206 if values:
1207 for v in values:
1207 for v in values:
1208 if v == section:
1208 if v == section:
1209 ui.debug('%s: ' %
1209 ui.debug('%s: ' %
1210 ui.configsource(section, name, untrusted))
1210 ui.configsource(section, name, untrusted))
1211 ui.write('%s=%s\n' % (sectname, value))
1211 ui.write('%s=%s\n' % (sectname, value))
1212 elif v == sectname:
1212 elif v == sectname:
1213 ui.debug('%s: ' %
1213 ui.debug('%s: ' %
1214 ui.configsource(section, name, untrusted))
1214 ui.configsource(section, name, untrusted))
1215 ui.write(value, '\n')
1215 ui.write(value, '\n')
1216 else:
1216 else:
1217 ui.debug('%s: ' %
1217 ui.debug('%s: ' %
1218 ui.configsource(section, name, untrusted))
1218 ui.configsource(section, name, untrusted))
1219 ui.write('%s=%s\n' % (sectname, value))
1219 ui.write('%s=%s\n' % (sectname, value))
1220
1220
1221 def debugknown(ui, repopath, *ids, **opts):
1221 def debugknown(ui, repopath, *ids, **opts):
1222 """test whether node ids are known to a repo
1222 """test whether node ids are known to a repo
1223
1223
1224 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
1224 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
1225 indicating unknown/known.
1225 indicating unknown/known.
1226 """
1226 """
1227 repo = hg.repository(ui, repopath)
1227 repo = hg.repository(ui, repopath)
1228 if not repo.capable('known'):
1228 if not repo.capable('known'):
1229 raise util.Abort("known() not supported by target repository")
1229 raise util.Abort("known() not supported by target repository")
1230 flags = repo.known([bin(s) for s in ids])
1230 flags = repo.known([bin(s) for s in ids])
1231 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1231 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1232
1232
1233 def debugbundle(ui, bundlepath, all=None, **opts):
1233 def debugbundle(ui, bundlepath, all=None, **opts):
1234 """lists the contents of a bundle"""
1234 """lists the contents of a bundle"""
1235 f = url.open(ui, bundlepath)
1235 f = url.open(ui, bundlepath)
1236 try:
1236 try:
1237 gen = changegroup.readbundle(f, bundlepath)
1237 gen = changegroup.readbundle(f, bundlepath)
1238 if all:
1238 if all:
1239 ui.write("format: id, p1, p2, cset, len(delta)\n")
1239 ui.write("format: id, p1, p2, cset, len(delta)\n")
1240
1240
1241 def showchunks(named):
1241 def showchunks(named):
1242 ui.write("\n%s\n" % named)
1242 ui.write("\n%s\n" % named)
1243 while 1:
1243 while 1:
1244 chunkdata = gen.parsechunk()
1244 chunkdata = gen.parsechunk()
1245 if not chunkdata:
1245 if not chunkdata:
1246 break
1246 break
1247 node = chunkdata['node']
1247 node = chunkdata['node']
1248 p1 = chunkdata['p1']
1248 p1 = chunkdata['p1']
1249 p2 = chunkdata['p2']
1249 p2 = chunkdata['p2']
1250 cs = chunkdata['cs']
1250 cs = chunkdata['cs']
1251 delta = chunkdata['data']
1251 delta = chunkdata['data']
1252 ui.write("%s %s %s %s %s\n" %
1252 ui.write("%s %s %s %s %s\n" %
1253 (hex(node), hex(p1), hex(p2),
1253 (hex(node), hex(p1), hex(p2),
1254 hex(cs), len(delta)))
1254 hex(cs), len(delta)))
1255
1255
1256 showchunks("changelog")
1256 showchunks("changelog")
1257 showchunks("manifest")
1257 showchunks("manifest")
1258 while 1:
1258 while 1:
1259 fname = gen.chunk()
1259 fname = gen.chunk()
1260 if not fname:
1260 if not fname:
1261 break
1261 break
1262 showchunks(fname)
1262 showchunks(fname)
1263 else:
1263 else:
1264 while 1:
1264 while 1:
1265 chunkdata = gen.parsechunk()
1265 chunkdata = gen.parsechunk()
1266 if not chunkdata:
1266 if not chunkdata:
1267 break
1267 break
1268 node = chunkdata['node']
1268 node = chunkdata['node']
1269 ui.write("%s\n" % hex(node))
1269 ui.write("%s\n" % hex(node))
1270 finally:
1270 finally:
1271 f.close()
1271 f.close()
1272
1272
1273 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1273 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1274 """retrieves a bundle from a repo
1274 """retrieves a bundle from a repo
1275
1275
1276 Every ID must be a full-length hex node id string. Saves the bundle to the
1276 Every ID must be a full-length hex node id string. Saves the bundle to the
1277 given file.
1277 given file.
1278 """
1278 """
1279 repo = hg.repository(ui, repopath)
1279 repo = hg.repository(ui, repopath)
1280 if not repo.capable('getbundle'):
1280 if not repo.capable('getbundle'):
1281 raise util.Abort("getbundle() not supported by target repository")
1281 raise util.Abort("getbundle() not supported by target repository")
1282 args = {}
1282 args = {}
1283 if common:
1283 if common:
1284 args['common'] = [bin(s) for s in common]
1284 args['common'] = [bin(s) for s in common]
1285 if head:
1285 if head:
1286 args['heads'] = [bin(s) for s in head]
1286 args['heads'] = [bin(s) for s in head]
1287 bundle = repo.getbundle('debug', **args)
1287 bundle = repo.getbundle('debug', **args)
1288
1288
1289 bundletype = opts.get('type', 'bzip2').lower()
1289 bundletype = opts.get('type', 'bzip2').lower()
1290 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1290 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1291 bundletype = btypes.get(bundletype)
1291 bundletype = btypes.get(bundletype)
1292 if bundletype not in changegroup.bundletypes:
1292 if bundletype not in changegroup.bundletypes:
1293 raise util.Abort(_('unknown bundle type specified with --type'))
1293 raise util.Abort(_('unknown bundle type specified with --type'))
1294 changegroup.writebundle(bundle, bundlepath, bundletype)
1294 changegroup.writebundle(bundle, bundlepath, bundletype)
1295
1295
1296 def debugpushkey(ui, repopath, namespace, *keyinfo):
1296 def debugpushkey(ui, repopath, namespace, *keyinfo):
1297 '''access the pushkey key/value protocol
1297 '''access the pushkey key/value protocol
1298
1298
1299 With two args, list the keys in the given namespace.
1299 With two args, list the keys in the given namespace.
1300
1300
1301 With five args, set a key to new if it currently is set to old.
1301 With five args, set a key to new if it currently is set to old.
1302 Reports success or failure.
1302 Reports success or failure.
1303 '''
1303 '''
1304
1304
1305 target = hg.repository(ui, repopath)
1305 target = hg.repository(ui, repopath)
1306 if keyinfo:
1306 if keyinfo:
1307 key, old, new = keyinfo
1307 key, old, new = keyinfo
1308 r = target.pushkey(namespace, key, old, new)
1308 r = target.pushkey(namespace, key, old, new)
1309 ui.status(str(r) + '\n')
1309 ui.status(str(r) + '\n')
1310 return not r
1310 return not r
1311 else:
1311 else:
1312 for k, v in target.listkeys(namespace).iteritems():
1312 for k, v in target.listkeys(namespace).iteritems():
1313 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1313 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1314 v.encode('string-escape')))
1314 v.encode('string-escape')))
1315
1315
1316 def debugrevspec(ui, repo, expr):
1316 def debugrevspec(ui, repo, expr):
1317 '''parse and apply a revision specification'''
1317 '''parse and apply a revision specification'''
1318 if ui.verbose:
1318 if ui.verbose:
1319 tree = revset.parse(expr)[0]
1319 tree = revset.parse(expr)[0]
1320 ui.note(tree, "\n")
1320 ui.note(tree, "\n")
1321 func = revset.match(expr)
1321 func = revset.match(expr)
1322 for c in func(repo, range(len(repo))):
1322 for c in func(repo, range(len(repo))):
1323 ui.write("%s\n" % c)
1323 ui.write("%s\n" % c)
1324
1324
1325 def debugsetparents(ui, repo, rev1, rev2=None):
1325 def debugsetparents(ui, repo, rev1, rev2=None):
1326 """manually set the parents of the current working directory
1326 """manually set the parents of the current working directory
1327
1327
1328 This is useful for writing repository conversion tools, but should
1328 This is useful for writing repository conversion tools, but should
1329 be used with care.
1329 be used with care.
1330
1330
1331 Returns 0 on success.
1331 Returns 0 on success.
1332 """
1332 """
1333
1333
1334 r1 = cmdutil.revsingle(repo, rev1).node()
1334 r1 = cmdutil.revsingle(repo, rev1).node()
1335 r2 = cmdutil.revsingle(repo, rev2, 'null').node()
1335 r2 = cmdutil.revsingle(repo, rev2, 'null').node()
1336
1336
1337 wlock = repo.wlock()
1337 wlock = repo.wlock()
1338 try:
1338 try:
1339 repo.dirstate.setparents(r1, r2)
1339 repo.dirstate.setparents(r1, r2)
1340 finally:
1340 finally:
1341 wlock.release()
1341 wlock.release()
1342
1342
1343 def debugstate(ui, repo, nodates=None, datesort=None):
1343 def debugstate(ui, repo, nodates=None, datesort=None):
1344 """show the contents of the current dirstate"""
1344 """show the contents of the current dirstate"""
1345 timestr = ""
1345 timestr = ""
1346 showdate = not nodates
1346 showdate = not nodates
1347 if datesort:
1347 if datesort:
1348 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
1348 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
1349 else:
1349 else:
1350 keyfunc = None # sort by filename
1350 keyfunc = None # sort by filename
1351 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
1351 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
1352 if showdate:
1352 if showdate:
1353 if ent[3] == -1:
1353 if ent[3] == -1:
1354 # Pad or slice to locale representation
1354 # Pad or slice to locale representation
1355 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1355 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1356 time.localtime(0)))
1356 time.localtime(0)))
1357 timestr = 'unset'
1357 timestr = 'unset'
1358 timestr = (timestr[:locale_len] +
1358 timestr = (timestr[:locale_len] +
1359 ' ' * (locale_len - len(timestr)))
1359 ' ' * (locale_len - len(timestr)))
1360 else:
1360 else:
1361 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1361 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1362 time.localtime(ent[3]))
1362 time.localtime(ent[3]))
1363 if ent[1] & 020000:
1363 if ent[1] & 020000:
1364 mode = 'lnk'
1364 mode = 'lnk'
1365 else:
1365 else:
1366 mode = '%3o' % (ent[1] & 0777)
1366 mode = '%3o' % (ent[1] & 0777)
1367 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1367 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1368 for f in repo.dirstate.copies():
1368 for f in repo.dirstate.copies():
1369 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1369 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1370
1370
1371 def debugsub(ui, repo, rev=None):
1371 def debugsub(ui, repo, rev=None):
1372 ctx = cmdutil.revsingle(repo, rev, None)
1372 ctx = cmdutil.revsingle(repo, rev, None)
1373 for k, v in sorted(ctx.substate.items()):
1373 for k, v in sorted(ctx.substate.items()):
1374 ui.write('path %s\n' % k)
1374 ui.write('path %s\n' % k)
1375 ui.write(' source %s\n' % v[0])
1375 ui.write(' source %s\n' % v[0])
1376 ui.write(' revision %s\n' % v[1])
1376 ui.write(' revision %s\n' % v[1])
1377
1377
1378 def debugdag(ui, repo, file_=None, *revs, **opts):
1378 def debugdag(ui, repo, file_=None, *revs, **opts):
1379 """format the changelog or an index DAG as a concise textual description
1379 """format the changelog or an index DAG as a concise textual description
1380
1380
1381 If you pass a revlog index, the revlog's DAG is emitted. If you list
1381 If you pass a revlog index, the revlog's DAG is emitted. If you list
1382 revision numbers, they get labelled in the output as rN.
1382 revision numbers, they get labelled in the output as rN.
1383
1383
1384 Otherwise, the changelog DAG of the current repo is emitted.
1384 Otherwise, the changelog DAG of the current repo is emitted.
1385 """
1385 """
1386 spaces = opts.get('spaces')
1386 spaces = opts.get('spaces')
1387 dots = opts.get('dots')
1387 dots = opts.get('dots')
1388 if file_:
1388 if file_:
1389 rlog = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1389 rlog = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1390 revs = set((int(r) for r in revs))
1390 revs = set((int(r) for r in revs))
1391 def events():
1391 def events():
1392 for r in rlog:
1392 for r in rlog:
1393 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1393 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1394 if r in revs:
1394 if r in revs:
1395 yield 'l', (r, "r%i" % r)
1395 yield 'l', (r, "r%i" % r)
1396 elif repo:
1396 elif repo:
1397 cl = repo.changelog
1397 cl = repo.changelog
1398 tags = opts.get('tags')
1398 tags = opts.get('tags')
1399 branches = opts.get('branches')
1399 branches = opts.get('branches')
1400 if tags:
1400 if tags:
1401 labels = {}
1401 labels = {}
1402 for l, n in repo.tags().items():
1402 for l, n in repo.tags().items():
1403 labels.setdefault(cl.rev(n), []).append(l)
1403 labels.setdefault(cl.rev(n), []).append(l)
1404 def events():
1404 def events():
1405 b = "default"
1405 b = "default"
1406 for r in cl:
1406 for r in cl:
1407 if branches:
1407 if branches:
1408 newb = cl.read(cl.node(r))[5]['branch']
1408 newb = cl.read(cl.node(r))[5]['branch']
1409 if newb != b:
1409 if newb != b:
1410 yield 'a', newb
1410 yield 'a', newb
1411 b = newb
1411 b = newb
1412 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1412 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1413 if tags:
1413 if tags:
1414 ls = labels.get(r)
1414 ls = labels.get(r)
1415 if ls:
1415 if ls:
1416 for l in ls:
1416 for l in ls:
1417 yield 'l', (r, l)
1417 yield 'l', (r, l)
1418 else:
1418 else:
1419 raise util.Abort(_('need repo for changelog dag'))
1419 raise util.Abort(_('need repo for changelog dag'))
1420
1420
1421 for line in dagparser.dagtextlines(events(),
1421 for line in dagparser.dagtextlines(events(),
1422 addspaces=spaces,
1422 addspaces=spaces,
1423 wraplabels=True,
1423 wraplabels=True,
1424 wrapannotations=True,
1424 wrapannotations=True,
1425 wrapnonlinear=dots,
1425 wrapnonlinear=dots,
1426 usedots=dots,
1426 usedots=dots,
1427 maxlinewidth=70):
1427 maxlinewidth=70):
1428 ui.write(line)
1428 ui.write(line)
1429 ui.write("\n")
1429 ui.write("\n")
1430
1430
1431 def debugdata(ui, repo, file_, rev):
1431 def debugdata(ui, repo, file_, rev):
1432 """dump the contents of a data file revision"""
1432 """dump the contents of a data file revision"""
1433 r = None
1433 r = None
1434 if repo:
1434 if repo:
1435 filelog = repo.file(file_)
1435 filelog = repo.file(file_)
1436 if len(filelog):
1436 if len(filelog):
1437 r = filelog
1437 r = filelog
1438 if not r:
1438 if not r:
1439 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1439 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1440 try:
1440 try:
1441 ui.write(r.revision(r.lookup(rev)))
1441 ui.write(r.revision(r.lookup(rev)))
1442 except KeyError:
1442 except KeyError:
1443 raise util.Abort(_('invalid revision identifier %s') % rev)
1443 raise util.Abort(_('invalid revision identifier %s') % rev)
1444
1444
1445 def debugdate(ui, date, range=None, **opts):
1445 def debugdate(ui, date, range=None, **opts):
1446 """parse and display a date"""
1446 """parse and display a date"""
1447 if opts["extended"]:
1447 if opts["extended"]:
1448 d = util.parsedate(date, util.extendeddateformats)
1448 d = util.parsedate(date, util.extendeddateformats)
1449 else:
1449 else:
1450 d = util.parsedate(date)
1450 d = util.parsedate(date)
1451 ui.write("internal: %s %s\n" % d)
1451 ui.write("internal: %s %s\n" % d)
1452 ui.write("standard: %s\n" % util.datestr(d))
1452 ui.write("standard: %s\n" % util.datestr(d))
1453 if range:
1453 if range:
1454 m = util.matchdate(range)
1454 m = util.matchdate(range)
1455 ui.write("match: %s\n" % m(d[0]))
1455 ui.write("match: %s\n" % m(d[0]))
1456
1456
1457 def debugignore(ui, repo, *values, **opts):
1457 def debugignore(ui, repo, *values, **opts):
1458 """display the combined ignore pattern"""
1458 """display the combined ignore pattern"""
1459 ignore = repo.dirstate._ignore
1459 ignore = repo.dirstate._ignore
1460 if hasattr(ignore, 'includepat'):
1460 if hasattr(ignore, 'includepat'):
1461 ui.write("%s\n" % ignore.includepat)
1461 ui.write("%s\n" % ignore.includepat)
1462 else:
1462 else:
1463 raise util.Abort(_("no ignore patterns found"))
1463 raise util.Abort(_("no ignore patterns found"))
1464
1464
1465 def debugindex(ui, repo, file_, **opts):
1465 def debugindex(ui, repo, file_, **opts):
1466 """dump the contents of an index file"""
1466 """dump the contents of an index file"""
1467 r = None
1467 r = None
1468 if repo:
1468 if repo:
1469 filelog = repo.file(file_)
1469 filelog = repo.file(file_)
1470 if len(filelog):
1470 if len(filelog):
1471 r = filelog
1471 r = filelog
1472
1472
1473 format = opts.get('format', 0)
1473 format = opts.get('format', 0)
1474 if format not in (0, 1):
1474 if format not in (0, 1):
1475 raise util.Abort(_("unknown format %d") % format)
1475 raise util.Abort(_("unknown format %d") % format)
1476
1476
1477 if not r:
1477 if not r:
1478 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1478 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1479
1479
1480 if format == 0:
1480 if format == 0:
1481 ui.write(" rev offset length base linkrev"
1481 ui.write(" rev offset length base linkrev"
1482 " nodeid p1 p2\n")
1482 " nodeid p1 p2\n")
1483 elif format == 1:
1483 elif format == 1:
1484 ui.write(" rev flag offset length"
1484 ui.write(" rev flag offset length"
1485 " size base link p1 p2 nodeid\n")
1485 " size base link p1 p2 nodeid\n")
1486
1486
1487 for i in r:
1487 for i in r:
1488 node = r.node(i)
1488 node = r.node(i)
1489 if format == 0:
1489 if format == 0:
1490 try:
1490 try:
1491 pp = r.parents(node)
1491 pp = r.parents(node)
1492 except:
1492 except:
1493 pp = [nullid, nullid]
1493 pp = [nullid, nullid]
1494 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1494 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1495 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1495 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1496 short(node), short(pp[0]), short(pp[1])))
1496 short(node), short(pp[0]), short(pp[1])))
1497 elif format == 1:
1497 elif format == 1:
1498 pr = r.parentrevs(i)
1498 pr = r.parentrevs(i)
1499 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1499 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1500 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1500 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1501 r.base(i), r.linkrev(i), pr[0], pr[1], short(node)))
1501 r.base(i), r.linkrev(i), pr[0], pr[1], short(node)))
1502
1502
1503 def debugindexdot(ui, repo, file_):
1503 def debugindexdot(ui, repo, file_):
1504 """dump an index DAG as a graphviz dot file"""
1504 """dump an index DAG as a graphviz dot file"""
1505 r = None
1505 r = None
1506 if repo:
1506 if repo:
1507 filelog = repo.file(file_)
1507 filelog = repo.file(file_)
1508 if len(filelog):
1508 if len(filelog):
1509 r = filelog
1509 r = filelog
1510 if not r:
1510 if not r:
1511 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1511 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1512 ui.write("digraph G {\n")
1512 ui.write("digraph G {\n")
1513 for i in r:
1513 for i in r:
1514 node = r.node(i)
1514 node = r.node(i)
1515 pp = r.parents(node)
1515 pp = r.parents(node)
1516 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1516 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1517 if pp[1] != nullid:
1517 if pp[1] != nullid:
1518 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1518 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1519 ui.write("}\n")
1519 ui.write("}\n")
1520
1520
1521 def debuginstall(ui):
1521 def debuginstall(ui):
1522 '''test Mercurial installation
1522 '''test Mercurial installation
1523
1523
1524 Returns 0 on success.
1524 Returns 0 on success.
1525 '''
1525 '''
1526
1526
1527 def writetemp(contents):
1527 def writetemp(contents):
1528 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1528 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1529 f = os.fdopen(fd, "wb")
1529 f = os.fdopen(fd, "wb")
1530 f.write(contents)
1530 f.write(contents)
1531 f.close()
1531 f.close()
1532 return name
1532 return name
1533
1533
1534 problems = 0
1534 problems = 0
1535
1535
1536 # encoding
1536 # encoding
1537 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1537 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1538 try:
1538 try:
1539 encoding.fromlocal("test")
1539 encoding.fromlocal("test")
1540 except util.Abort, inst:
1540 except util.Abort, inst:
1541 ui.write(" %s\n" % inst)
1541 ui.write(" %s\n" % inst)
1542 ui.write(_(" (check that your locale is properly set)\n"))
1542 ui.write(_(" (check that your locale is properly set)\n"))
1543 problems += 1
1543 problems += 1
1544
1544
1545 # compiled modules
1545 # compiled modules
1546 ui.status(_("Checking installed modules (%s)...\n")
1546 ui.status(_("Checking installed modules (%s)...\n")
1547 % os.path.dirname(__file__))
1547 % os.path.dirname(__file__))
1548 try:
1548 try:
1549 import bdiff, mpatch, base85, osutil
1549 import bdiff, mpatch, base85, osutil
1550 except Exception, inst:
1550 except Exception, inst:
1551 ui.write(" %s\n" % inst)
1551 ui.write(" %s\n" % inst)
1552 ui.write(_(" One or more extensions could not be found"))
1552 ui.write(_(" One or more extensions could not be found"))
1553 ui.write(_(" (check that you compiled the extensions)\n"))
1553 ui.write(_(" (check that you compiled the extensions)\n"))
1554 problems += 1
1554 problems += 1
1555
1555
1556 # templates
1556 # templates
1557 ui.status(_("Checking templates...\n"))
1557 ui.status(_("Checking templates...\n"))
1558 try:
1558 try:
1559 import templater
1559 import templater
1560 templater.templater(templater.templatepath("map-cmdline.default"))
1560 templater.templater(templater.templatepath("map-cmdline.default"))
1561 except Exception, inst:
1561 except Exception, inst:
1562 ui.write(" %s\n" % inst)
1562 ui.write(" %s\n" % inst)
1563 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1563 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1564 problems += 1
1564 problems += 1
1565
1565
1566 # editor
1566 # editor
1567 ui.status(_("Checking commit editor...\n"))
1567 ui.status(_("Checking commit editor...\n"))
1568 editor = ui.geteditor()
1568 editor = ui.geteditor()
1569 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1569 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1570 if not cmdpath:
1570 if not cmdpath:
1571 if editor == 'vi':
1571 if editor == 'vi':
1572 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1572 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1573 ui.write(_(" (specify a commit editor in your configuration"
1573 ui.write(_(" (specify a commit editor in your configuration"
1574 " file)\n"))
1574 " file)\n"))
1575 else:
1575 else:
1576 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1576 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1577 ui.write(_(" (specify a commit editor in your configuration"
1577 ui.write(_(" (specify a commit editor in your configuration"
1578 " file)\n"))
1578 " file)\n"))
1579 problems += 1
1579 problems += 1
1580
1580
1581 # check username
1581 # check username
1582 ui.status(_("Checking username...\n"))
1582 ui.status(_("Checking username...\n"))
1583 try:
1583 try:
1584 ui.username()
1584 ui.username()
1585 except util.Abort, e:
1585 except util.Abort, e:
1586 ui.write(" %s\n" % e)
1586 ui.write(" %s\n" % e)
1587 ui.write(_(" (specify a username in your configuration file)\n"))
1587 ui.write(_(" (specify a username in your configuration file)\n"))
1588 problems += 1
1588 problems += 1
1589
1589
1590 if not problems:
1590 if not problems:
1591 ui.status(_("No problems detected\n"))
1591 ui.status(_("No problems detected\n"))
1592 else:
1592 else:
1593 ui.write(_("%s problems detected,"
1593 ui.write(_("%s problems detected,"
1594 " please check your install!\n") % problems)
1594 " please check your install!\n") % problems)
1595
1595
1596 return problems
1596 return problems
1597
1597
1598 def debugrename(ui, repo, file1, *pats, **opts):
1598 def debugrename(ui, repo, file1, *pats, **opts):
1599 """dump rename information"""
1599 """dump rename information"""
1600
1600
1601 ctx = cmdutil.revsingle(repo, opts.get('rev'))
1601 ctx = cmdutil.revsingle(repo, opts.get('rev'))
1602 m = cmdutil.match(repo, (file1,) + pats, opts)
1602 m = cmdutil.match(repo, (file1,) + pats, opts)
1603 for abs in ctx.walk(m):
1603 for abs in ctx.walk(m):
1604 fctx = ctx[abs]
1604 fctx = ctx[abs]
1605 o = fctx.filelog().renamed(fctx.filenode())
1605 o = fctx.filelog().renamed(fctx.filenode())
1606 rel = m.rel(abs)
1606 rel = m.rel(abs)
1607 if o:
1607 if o:
1608 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1608 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1609 else:
1609 else:
1610 ui.write(_("%s not renamed\n") % rel)
1610 ui.write(_("%s not renamed\n") % rel)
1611
1611
1612 def debugwalk(ui, repo, *pats, **opts):
1612 def debugwalk(ui, repo, *pats, **opts):
1613 """show how files match on given patterns"""
1613 """show how files match on given patterns"""
1614 m = cmdutil.match(repo, pats, opts)
1614 m = cmdutil.match(repo, pats, opts)
1615 items = list(repo.walk(m))
1615 items = list(repo.walk(m))
1616 if not items:
1616 if not items:
1617 return
1617 return
1618 fmt = 'f %%-%ds %%-%ds %%s' % (
1618 fmt = 'f %%-%ds %%-%ds %%s' % (
1619 max([len(abs) for abs in items]),
1619 max([len(abs) for abs in items]),
1620 max([len(m.rel(abs)) for abs in items]))
1620 max([len(m.rel(abs)) for abs in items]))
1621 for abs in items:
1621 for abs in items:
1622 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1622 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1623 ui.write("%s\n" % line.rstrip())
1623 ui.write("%s\n" % line.rstrip())
1624
1624
1625 def debugwireargs(ui, repopath, *vals, **opts):
1625 def debugwireargs(ui, repopath, *vals, **opts):
1626 repo = hg.repository(hg.remoteui(ui, opts), repopath)
1626 repo = hg.repository(hg.remoteui(ui, opts), repopath)
1627 for opt in remoteopts:
1627 for opt in remoteopts:
1628 del opts[opt[1]]
1628 del opts[opt[1]]
1629 args = {}
1629 args = {}
1630 for k, v in opts.iteritems():
1630 for k, v in opts.iteritems():
1631 if v:
1631 if v:
1632 args[k] = v
1632 args[k] = v
1633 # run twice to check that we don't mess up the stream for the next command
1633 # run twice to check that we don't mess up the stream for the next command
1634 res1 = repo.debugwireargs(*vals, **args)
1634 res1 = repo.debugwireargs(*vals, **args)
1635 res2 = repo.debugwireargs(*vals, **args)
1635 res2 = repo.debugwireargs(*vals, **args)
1636 ui.write("%s\n" % res1)
1636 ui.write("%s\n" % res1)
1637 if res1 != res2:
1637 if res1 != res2:
1638 ui.warn("%s\n" % res2)
1638 ui.warn("%s\n" % res2)
1639
1639
1640 def diff(ui, repo, *pats, **opts):
1640 def diff(ui, repo, *pats, **opts):
1641 """diff repository (or selected files)
1641 """diff repository (or selected files)
1642
1642
1643 Show differences between revisions for the specified files.
1643 Show differences between revisions for the specified files.
1644
1644
1645 Differences between files are shown using the unified diff format.
1645 Differences between files are shown using the unified diff format.
1646
1646
1647 .. note::
1647 .. note::
1648 diff may generate unexpected results for merges, as it will
1648 diff may generate unexpected results for merges, as it will
1649 default to comparing against the working directory's first
1649 default to comparing against the working directory's first
1650 parent changeset if no revisions are specified.
1650 parent changeset if no revisions are specified.
1651
1651
1652 When two revision arguments are given, then changes are shown
1652 When two revision arguments are given, then changes are shown
1653 between those revisions. If only one revision is specified then
1653 between those revisions. If only one revision is specified then
1654 that revision is compared to the working directory, and, when no
1654 that revision is compared to the working directory, and, when no
1655 revisions are specified, the working directory files are compared
1655 revisions are specified, the working directory files are compared
1656 to its parent.
1656 to its parent.
1657
1657
1658 Alternatively you can specify -c/--change with a revision to see
1658 Alternatively you can specify -c/--change with a revision to see
1659 the changes in that changeset relative to its first parent.
1659 the changes in that changeset relative to its first parent.
1660
1660
1661 Without the -a/--text option, diff will avoid generating diffs of
1661 Without the -a/--text option, diff will avoid generating diffs of
1662 files it detects as binary. With -a, diff will generate a diff
1662 files it detects as binary. With -a, diff will generate a diff
1663 anyway, probably with undesirable results.
1663 anyway, probably with undesirable results.
1664
1664
1665 Use the -g/--git option to generate diffs in the git extended diff
1665 Use the -g/--git option to generate diffs in the git extended diff
1666 format. For more information, read :hg:`help diffs`.
1666 format. For more information, read :hg:`help diffs`.
1667
1667
1668 Returns 0 on success.
1668 Returns 0 on success.
1669 """
1669 """
1670
1670
1671 revs = opts.get('rev')
1671 revs = opts.get('rev')
1672 change = opts.get('change')
1672 change = opts.get('change')
1673 stat = opts.get('stat')
1673 stat = opts.get('stat')
1674 reverse = opts.get('reverse')
1674 reverse = opts.get('reverse')
1675
1675
1676 if revs and change:
1676 if revs and change:
1677 msg = _('cannot specify --rev and --change at the same time')
1677 msg = _('cannot specify --rev and --change at the same time')
1678 raise util.Abort(msg)
1678 raise util.Abort(msg)
1679 elif change:
1679 elif change:
1680 node2 = cmdutil.revsingle(repo, change, None).node()
1680 node2 = cmdutil.revsingle(repo, change, None).node()
1681 node1 = repo[node2].parents()[0].node()
1681 node1 = repo[node2].p1().node()
1682 else:
1682 else:
1683 node1, node2 = cmdutil.revpair(repo, revs)
1683 node1, node2 = cmdutil.revpair(repo, revs)
1684
1684
1685 if reverse:
1685 if reverse:
1686 node1, node2 = node2, node1
1686 node1, node2 = node2, node1
1687
1687
1688 diffopts = patch.diffopts(ui, opts)
1688 diffopts = patch.diffopts(ui, opts)
1689 m = cmdutil.match(repo, pats, opts)
1689 m = cmdutil.match(repo, pats, opts)
1690 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1690 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1691 listsubrepos=opts.get('subrepos'))
1691 listsubrepos=opts.get('subrepos'))
1692
1692
1693 def export(ui, repo, *changesets, **opts):
1693 def export(ui, repo, *changesets, **opts):
1694 """dump the header and diffs for one or more changesets
1694 """dump the header and diffs for one or more changesets
1695
1695
1696 Print the changeset header and diffs for one or more revisions.
1696 Print the changeset header and diffs for one or more revisions.
1697
1697
1698 The information shown in the changeset header is: author, date,
1698 The information shown in the changeset header is: author, date,
1699 branch name (if non-default), changeset hash, parent(s) and commit
1699 branch name (if non-default), changeset hash, parent(s) and commit
1700 comment.
1700 comment.
1701
1701
1702 .. note::
1702 .. note::
1703 export may generate unexpected diff output for merge
1703 export may generate unexpected diff output for merge
1704 changesets, as it will compare the merge changeset against its
1704 changesets, as it will compare the merge changeset against its
1705 first parent only.
1705 first parent only.
1706
1706
1707 Output may be to a file, in which case the name of the file is
1707 Output may be to a file, in which case the name of the file is
1708 given using a format string. The formatting rules are as follows:
1708 given using a format string. The formatting rules are as follows:
1709
1709
1710 :``%%``: literal "%" character
1710 :``%%``: literal "%" character
1711 :``%H``: changeset hash (40 hexadecimal digits)
1711 :``%H``: changeset hash (40 hexadecimal digits)
1712 :``%N``: number of patches being generated
1712 :``%N``: number of patches being generated
1713 :``%R``: changeset revision number
1713 :``%R``: changeset revision number
1714 :``%b``: basename of the exporting repository
1714 :``%b``: basename of the exporting repository
1715 :``%h``: short-form changeset hash (12 hexadecimal digits)
1715 :``%h``: short-form changeset hash (12 hexadecimal digits)
1716 :``%n``: zero-padded sequence number, starting at 1
1716 :``%n``: zero-padded sequence number, starting at 1
1717 :``%r``: zero-padded changeset revision number
1717 :``%r``: zero-padded changeset revision number
1718
1718
1719 Without the -a/--text option, export will avoid generating diffs
1719 Without the -a/--text option, export will avoid generating diffs
1720 of files it detects as binary. With -a, export will generate a
1720 of files it detects as binary. With -a, export will generate a
1721 diff anyway, probably with undesirable results.
1721 diff anyway, probably with undesirable results.
1722
1722
1723 Use the -g/--git option to generate diffs in the git extended diff
1723 Use the -g/--git option to generate diffs in the git extended diff
1724 format. See :hg:`help diffs` for more information.
1724 format. See :hg:`help diffs` for more information.
1725
1725
1726 With the --switch-parent option, the diff will be against the
1726 With the --switch-parent option, the diff will be against the
1727 second parent. It can be useful to review a merge.
1727 second parent. It can be useful to review a merge.
1728
1728
1729 Returns 0 on success.
1729 Returns 0 on success.
1730 """
1730 """
1731 changesets += tuple(opts.get('rev', []))
1731 changesets += tuple(opts.get('rev', []))
1732 if not changesets:
1732 if not changesets:
1733 raise util.Abort(_("export requires at least one changeset"))
1733 raise util.Abort(_("export requires at least one changeset"))
1734 revs = cmdutil.revrange(repo, changesets)
1734 revs = cmdutil.revrange(repo, changesets)
1735 if len(revs) > 1:
1735 if len(revs) > 1:
1736 ui.note(_('exporting patches:\n'))
1736 ui.note(_('exporting patches:\n'))
1737 else:
1737 else:
1738 ui.note(_('exporting patch:\n'))
1738 ui.note(_('exporting patch:\n'))
1739 cmdutil.export(repo, revs, template=opts.get('output'),
1739 cmdutil.export(repo, revs, template=opts.get('output'),
1740 switch_parent=opts.get('switch_parent'),
1740 switch_parent=opts.get('switch_parent'),
1741 opts=patch.diffopts(ui, opts))
1741 opts=patch.diffopts(ui, opts))
1742
1742
1743 def forget(ui, repo, *pats, **opts):
1743 def forget(ui, repo, *pats, **opts):
1744 """forget the specified files on the next commit
1744 """forget the specified files on the next commit
1745
1745
1746 Mark the specified files so they will no longer be tracked
1746 Mark the specified files so they will no longer be tracked
1747 after the next commit.
1747 after the next commit.
1748
1748
1749 This only removes files from the current branch, not from the
1749 This only removes files from the current branch, not from the
1750 entire project history, and it does not delete them from the
1750 entire project history, and it does not delete them from the
1751 working directory.
1751 working directory.
1752
1752
1753 To undo a forget before the next commit, see :hg:`add`.
1753 To undo a forget before the next commit, see :hg:`add`.
1754
1754
1755 Returns 0 on success.
1755 Returns 0 on success.
1756 """
1756 """
1757
1757
1758 if not pats:
1758 if not pats:
1759 raise util.Abort(_('no files specified'))
1759 raise util.Abort(_('no files specified'))
1760
1760
1761 m = cmdutil.match(repo, pats, opts)
1761 m = cmdutil.match(repo, pats, opts)
1762 s = repo.status(match=m, clean=True)
1762 s = repo.status(match=m, clean=True)
1763 forget = sorted(s[0] + s[1] + s[3] + s[6])
1763 forget = sorted(s[0] + s[1] + s[3] + s[6])
1764 errs = 0
1764 errs = 0
1765
1765
1766 for f in m.files():
1766 for f in m.files():
1767 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1767 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1768 ui.warn(_('not removing %s: file is already untracked\n')
1768 ui.warn(_('not removing %s: file is already untracked\n')
1769 % m.rel(f))
1769 % m.rel(f))
1770 errs = 1
1770 errs = 1
1771
1771
1772 for f in forget:
1772 for f in forget:
1773 if ui.verbose or not m.exact(f):
1773 if ui.verbose or not m.exact(f):
1774 ui.status(_('removing %s\n') % m.rel(f))
1774 ui.status(_('removing %s\n') % m.rel(f))
1775
1775
1776 repo[None].remove(forget, unlink=False)
1776 repo[None].remove(forget, unlink=False)
1777 return errs
1777 return errs
1778
1778
1779 def grep(ui, repo, pattern, *pats, **opts):
1779 def grep(ui, repo, pattern, *pats, **opts):
1780 """search for a pattern in specified files and revisions
1780 """search for a pattern in specified files and revisions
1781
1781
1782 Search revisions of files for a regular expression.
1782 Search revisions of files for a regular expression.
1783
1783
1784 This command behaves differently than Unix grep. It only accepts
1784 This command behaves differently than Unix grep. It only accepts
1785 Python/Perl regexps. It searches repository history, not the
1785 Python/Perl regexps. It searches repository history, not the
1786 working directory. It always prints the revision number in which a
1786 working directory. It always prints the revision number in which a
1787 match appears.
1787 match appears.
1788
1788
1789 By default, grep only prints output for the first revision of a
1789 By default, grep only prints output for the first revision of a
1790 file in which it finds a match. To get it to print every revision
1790 file in which it finds a match. To get it to print every revision
1791 that contains a change in match status ("-" for a match that
1791 that contains a change in match status ("-" for a match that
1792 becomes a non-match, or "+" for a non-match that becomes a match),
1792 becomes a non-match, or "+" for a non-match that becomes a match),
1793 use the --all flag.
1793 use the --all flag.
1794
1794
1795 Returns 0 if a match is found, 1 otherwise.
1795 Returns 0 if a match is found, 1 otherwise.
1796 """
1796 """
1797 reflags = 0
1797 reflags = 0
1798 if opts.get('ignore_case'):
1798 if opts.get('ignore_case'):
1799 reflags |= re.I
1799 reflags |= re.I
1800 try:
1800 try:
1801 regexp = re.compile(pattern, reflags)
1801 regexp = re.compile(pattern, reflags)
1802 except re.error, inst:
1802 except re.error, inst:
1803 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1803 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1804 return 1
1804 return 1
1805 sep, eol = ':', '\n'
1805 sep, eol = ':', '\n'
1806 if opts.get('print0'):
1806 if opts.get('print0'):
1807 sep = eol = '\0'
1807 sep = eol = '\0'
1808
1808
1809 getfile = util.lrucachefunc(repo.file)
1809 getfile = util.lrucachefunc(repo.file)
1810
1810
1811 def matchlines(body):
1811 def matchlines(body):
1812 begin = 0
1812 begin = 0
1813 linenum = 0
1813 linenum = 0
1814 while True:
1814 while True:
1815 match = regexp.search(body, begin)
1815 match = regexp.search(body, begin)
1816 if not match:
1816 if not match:
1817 break
1817 break
1818 mstart, mend = match.span()
1818 mstart, mend = match.span()
1819 linenum += body.count('\n', begin, mstart) + 1
1819 linenum += body.count('\n', begin, mstart) + 1
1820 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1820 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1821 begin = body.find('\n', mend) + 1 or len(body)
1821 begin = body.find('\n', mend) + 1 or len(body)
1822 lend = begin - 1
1822 lend = begin - 1
1823 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1823 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1824
1824
1825 class linestate(object):
1825 class linestate(object):
1826 def __init__(self, line, linenum, colstart, colend):
1826 def __init__(self, line, linenum, colstart, colend):
1827 self.line = line
1827 self.line = line
1828 self.linenum = linenum
1828 self.linenum = linenum
1829 self.colstart = colstart
1829 self.colstart = colstart
1830 self.colend = colend
1830 self.colend = colend
1831
1831
1832 def __hash__(self):
1832 def __hash__(self):
1833 return hash((self.linenum, self.line))
1833 return hash((self.linenum, self.line))
1834
1834
1835 def __eq__(self, other):
1835 def __eq__(self, other):
1836 return self.line == other.line
1836 return self.line == other.line
1837
1837
1838 matches = {}
1838 matches = {}
1839 copies = {}
1839 copies = {}
1840 def grepbody(fn, rev, body):
1840 def grepbody(fn, rev, body):
1841 matches[rev].setdefault(fn, [])
1841 matches[rev].setdefault(fn, [])
1842 m = matches[rev][fn]
1842 m = matches[rev][fn]
1843 for lnum, cstart, cend, line in matchlines(body):
1843 for lnum, cstart, cend, line in matchlines(body):
1844 s = linestate(line, lnum, cstart, cend)
1844 s = linestate(line, lnum, cstart, cend)
1845 m.append(s)
1845 m.append(s)
1846
1846
1847 def difflinestates(a, b):
1847 def difflinestates(a, b):
1848 sm = difflib.SequenceMatcher(None, a, b)
1848 sm = difflib.SequenceMatcher(None, a, b)
1849 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1849 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1850 if tag == 'insert':
1850 if tag == 'insert':
1851 for i in xrange(blo, bhi):
1851 for i in xrange(blo, bhi):
1852 yield ('+', b[i])
1852 yield ('+', b[i])
1853 elif tag == 'delete':
1853 elif tag == 'delete':
1854 for i in xrange(alo, ahi):
1854 for i in xrange(alo, ahi):
1855 yield ('-', a[i])
1855 yield ('-', a[i])
1856 elif tag == 'replace':
1856 elif tag == 'replace':
1857 for i in xrange(alo, ahi):
1857 for i in xrange(alo, ahi):
1858 yield ('-', a[i])
1858 yield ('-', a[i])
1859 for i in xrange(blo, bhi):
1859 for i in xrange(blo, bhi):
1860 yield ('+', b[i])
1860 yield ('+', b[i])
1861
1861
1862 def display(fn, ctx, pstates, states):
1862 def display(fn, ctx, pstates, states):
1863 rev = ctx.rev()
1863 rev = ctx.rev()
1864 datefunc = ui.quiet and util.shortdate or util.datestr
1864 datefunc = ui.quiet and util.shortdate or util.datestr
1865 found = False
1865 found = False
1866 filerevmatches = {}
1866 filerevmatches = {}
1867 if opts.get('all'):
1867 if opts.get('all'):
1868 iter = difflinestates(pstates, states)
1868 iter = difflinestates(pstates, states)
1869 else:
1869 else:
1870 iter = [('', l) for l in states]
1870 iter = [('', l) for l in states]
1871 for change, l in iter:
1871 for change, l in iter:
1872 cols = [fn, str(rev)]
1872 cols = [fn, str(rev)]
1873 before, match, after = None, None, None
1873 before, match, after = None, None, None
1874 if opts.get('line_number'):
1874 if opts.get('line_number'):
1875 cols.append(str(l.linenum))
1875 cols.append(str(l.linenum))
1876 if opts.get('all'):
1876 if opts.get('all'):
1877 cols.append(change)
1877 cols.append(change)
1878 if opts.get('user'):
1878 if opts.get('user'):
1879 cols.append(ui.shortuser(ctx.user()))
1879 cols.append(ui.shortuser(ctx.user()))
1880 if opts.get('date'):
1880 if opts.get('date'):
1881 cols.append(datefunc(ctx.date()))
1881 cols.append(datefunc(ctx.date()))
1882 if opts.get('files_with_matches'):
1882 if opts.get('files_with_matches'):
1883 c = (fn, rev)
1883 c = (fn, rev)
1884 if c in filerevmatches:
1884 if c in filerevmatches:
1885 continue
1885 continue
1886 filerevmatches[c] = 1
1886 filerevmatches[c] = 1
1887 else:
1887 else:
1888 before = l.line[:l.colstart]
1888 before = l.line[:l.colstart]
1889 match = l.line[l.colstart:l.colend]
1889 match = l.line[l.colstart:l.colend]
1890 after = l.line[l.colend:]
1890 after = l.line[l.colend:]
1891 ui.write(sep.join(cols))
1891 ui.write(sep.join(cols))
1892 if before is not None:
1892 if before is not None:
1893 ui.write(sep + before)
1893 ui.write(sep + before)
1894 ui.write(match, label='grep.match')
1894 ui.write(match, label='grep.match')
1895 ui.write(after)
1895 ui.write(after)
1896 ui.write(eol)
1896 ui.write(eol)
1897 found = True
1897 found = True
1898 return found
1898 return found
1899
1899
1900 skip = {}
1900 skip = {}
1901 revfiles = {}
1901 revfiles = {}
1902 matchfn = cmdutil.match(repo, pats, opts)
1902 matchfn = cmdutil.match(repo, pats, opts)
1903 found = False
1903 found = False
1904 follow = opts.get('follow')
1904 follow = opts.get('follow')
1905
1905
1906 def prep(ctx, fns):
1906 def prep(ctx, fns):
1907 rev = ctx.rev()
1907 rev = ctx.rev()
1908 pctx = ctx.parents()[0]
1908 pctx = ctx.p1()
1909 parent = pctx.rev()
1909 parent = pctx.rev()
1910 matches.setdefault(rev, {})
1910 matches.setdefault(rev, {})
1911 matches.setdefault(parent, {})
1911 matches.setdefault(parent, {})
1912 files = revfiles.setdefault(rev, [])
1912 files = revfiles.setdefault(rev, [])
1913 for fn in fns:
1913 for fn in fns:
1914 flog = getfile(fn)
1914 flog = getfile(fn)
1915 try:
1915 try:
1916 fnode = ctx.filenode(fn)
1916 fnode = ctx.filenode(fn)
1917 except error.LookupError:
1917 except error.LookupError:
1918 continue
1918 continue
1919
1919
1920 copied = flog.renamed(fnode)
1920 copied = flog.renamed(fnode)
1921 copy = follow and copied and copied[0]
1921 copy = follow and copied and copied[0]
1922 if copy:
1922 if copy:
1923 copies.setdefault(rev, {})[fn] = copy
1923 copies.setdefault(rev, {})[fn] = copy
1924 if fn in skip:
1924 if fn in skip:
1925 if copy:
1925 if copy:
1926 skip[copy] = True
1926 skip[copy] = True
1927 continue
1927 continue
1928 files.append(fn)
1928 files.append(fn)
1929
1929
1930 if fn not in matches[rev]:
1930 if fn not in matches[rev]:
1931 grepbody(fn, rev, flog.read(fnode))
1931 grepbody(fn, rev, flog.read(fnode))
1932
1932
1933 pfn = copy or fn
1933 pfn = copy or fn
1934 if pfn not in matches[parent]:
1934 if pfn not in matches[parent]:
1935 try:
1935 try:
1936 fnode = pctx.filenode(pfn)
1936 fnode = pctx.filenode(pfn)
1937 grepbody(pfn, parent, flog.read(fnode))
1937 grepbody(pfn, parent, flog.read(fnode))
1938 except error.LookupError:
1938 except error.LookupError:
1939 pass
1939 pass
1940
1940
1941 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1941 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1942 rev = ctx.rev()
1942 rev = ctx.rev()
1943 parent = ctx.parents()[0].rev()
1943 parent = ctx.p1().rev()
1944 for fn in sorted(revfiles.get(rev, [])):
1944 for fn in sorted(revfiles.get(rev, [])):
1945 states = matches[rev][fn]
1945 states = matches[rev][fn]
1946 copy = copies.get(rev, {}).get(fn)
1946 copy = copies.get(rev, {}).get(fn)
1947 if fn in skip:
1947 if fn in skip:
1948 if copy:
1948 if copy:
1949 skip[copy] = True
1949 skip[copy] = True
1950 continue
1950 continue
1951 pstates = matches.get(parent, {}).get(copy or fn, [])
1951 pstates = matches.get(parent, {}).get(copy or fn, [])
1952 if pstates or states:
1952 if pstates or states:
1953 r = display(fn, ctx, pstates, states)
1953 r = display(fn, ctx, pstates, states)
1954 found = found or r
1954 found = found or r
1955 if r and not opts.get('all'):
1955 if r and not opts.get('all'):
1956 skip[fn] = True
1956 skip[fn] = True
1957 if copy:
1957 if copy:
1958 skip[copy] = True
1958 skip[copy] = True
1959 del matches[rev]
1959 del matches[rev]
1960 del revfiles[rev]
1960 del revfiles[rev]
1961
1961
1962 return not found
1962 return not found
1963
1963
1964 def heads(ui, repo, *branchrevs, **opts):
1964 def heads(ui, repo, *branchrevs, **opts):
1965 """show current repository heads or show branch heads
1965 """show current repository heads or show branch heads
1966
1966
1967 With no arguments, show all repository branch heads.
1967 With no arguments, show all repository branch heads.
1968
1968
1969 Repository "heads" are changesets with no child changesets. They are
1969 Repository "heads" are changesets with no child changesets. They are
1970 where development generally takes place and are the usual targets
1970 where development generally takes place and are the usual targets
1971 for update and merge operations. Branch heads are changesets that have
1971 for update and merge operations. Branch heads are changesets that have
1972 no child changeset on the same branch.
1972 no child changeset on the same branch.
1973
1973
1974 If one or more REVs are given, only branch heads on the branches
1974 If one or more REVs are given, only branch heads on the branches
1975 associated with the specified changesets are shown.
1975 associated with the specified changesets are shown.
1976
1976
1977 If -c/--closed is specified, also show branch heads marked closed
1977 If -c/--closed is specified, also show branch heads marked closed
1978 (see :hg:`commit --close-branch`).
1978 (see :hg:`commit --close-branch`).
1979
1979
1980 If STARTREV is specified, only those heads that are descendants of
1980 If STARTREV is specified, only those heads that are descendants of
1981 STARTREV will be displayed.
1981 STARTREV will be displayed.
1982
1982
1983 If -t/--topo is specified, named branch mechanics will be ignored and only
1983 If -t/--topo is specified, named branch mechanics will be ignored and only
1984 changesets without children will be shown.
1984 changesets without children will be shown.
1985
1985
1986 Returns 0 if matching heads are found, 1 if not.
1986 Returns 0 if matching heads are found, 1 if not.
1987 """
1987 """
1988
1988
1989 start = None
1989 start = None
1990 if 'rev' in opts:
1990 if 'rev' in opts:
1991 start = cmdutil.revsingle(repo, opts['rev'], None).node()
1991 start = cmdutil.revsingle(repo, opts['rev'], None).node()
1992
1992
1993 if opts.get('topo'):
1993 if opts.get('topo'):
1994 heads = [repo[h] for h in repo.heads(start)]
1994 heads = [repo[h] for h in repo.heads(start)]
1995 else:
1995 else:
1996 heads = []
1996 heads = []
1997 for b, ls in repo.branchmap().iteritems():
1997 for b, ls in repo.branchmap().iteritems():
1998 if start is None:
1998 if start is None:
1999 heads += [repo[h] for h in ls]
1999 heads += [repo[h] for h in ls]
2000 continue
2000 continue
2001 startrev = repo.changelog.rev(start)
2001 startrev = repo.changelog.rev(start)
2002 descendants = set(repo.changelog.descendants(startrev))
2002 descendants = set(repo.changelog.descendants(startrev))
2003 descendants.add(startrev)
2003 descendants.add(startrev)
2004 rev = repo.changelog.rev
2004 rev = repo.changelog.rev
2005 heads += [repo[h] for h in ls if rev(h) in descendants]
2005 heads += [repo[h] for h in ls if rev(h) in descendants]
2006
2006
2007 if branchrevs:
2007 if branchrevs:
2008 branches = set(repo[br].branch() for br in branchrevs)
2008 branches = set(repo[br].branch() for br in branchrevs)
2009 heads = [h for h in heads if h.branch() in branches]
2009 heads = [h for h in heads if h.branch() in branches]
2010
2010
2011 if not opts.get('closed'):
2011 if not opts.get('closed'):
2012 heads = [h for h in heads if not h.extra().get('close')]
2012 heads = [h for h in heads if not h.extra().get('close')]
2013
2013
2014 if opts.get('active') and branchrevs:
2014 if opts.get('active') and branchrevs:
2015 dagheads = repo.heads(start)
2015 dagheads = repo.heads(start)
2016 heads = [h for h in heads if h.node() in dagheads]
2016 heads = [h for h in heads if h.node() in dagheads]
2017
2017
2018 if branchrevs:
2018 if branchrevs:
2019 haveheads = set(h.branch() for h in heads)
2019 haveheads = set(h.branch() for h in heads)
2020 if branches - haveheads:
2020 if branches - haveheads:
2021 headless = ', '.join(b for b in branches - haveheads)
2021 headless = ', '.join(b for b in branches - haveheads)
2022 msg = _('no open branch heads found on branches %s')
2022 msg = _('no open branch heads found on branches %s')
2023 if opts.get('rev'):
2023 if opts.get('rev'):
2024 msg += _(' (started at %s)' % opts['rev'])
2024 msg += _(' (started at %s)' % opts['rev'])
2025 ui.warn((msg + '\n') % headless)
2025 ui.warn((msg + '\n') % headless)
2026
2026
2027 if not heads:
2027 if not heads:
2028 return 1
2028 return 1
2029
2029
2030 heads = sorted(heads, key=lambda x: -x.rev())
2030 heads = sorted(heads, key=lambda x: -x.rev())
2031 displayer = cmdutil.show_changeset(ui, repo, opts)
2031 displayer = cmdutil.show_changeset(ui, repo, opts)
2032 for ctx in heads:
2032 for ctx in heads:
2033 displayer.show(ctx)
2033 displayer.show(ctx)
2034 displayer.close()
2034 displayer.close()
2035
2035
2036 def help_(ui, name=None, with_version=False, unknowncmd=False):
2036 def help_(ui, name=None, with_version=False, unknowncmd=False):
2037 """show help for a given topic or a help overview
2037 """show help for a given topic or a help overview
2038
2038
2039 With no arguments, print a list of commands with short help messages.
2039 With no arguments, print a list of commands with short help messages.
2040
2040
2041 Given a topic, extension, or command name, print help for that
2041 Given a topic, extension, or command name, print help for that
2042 topic.
2042 topic.
2043
2043
2044 Returns 0 if successful.
2044 Returns 0 if successful.
2045 """
2045 """
2046 option_lists = []
2046 option_lists = []
2047 textwidth = min(ui.termwidth(), 80) - 2
2047 textwidth = min(ui.termwidth(), 80) - 2
2048
2048
2049 def addglobalopts(aliases):
2049 def addglobalopts(aliases):
2050 if ui.verbose:
2050 if ui.verbose:
2051 option_lists.append((_("global options:"), globalopts))
2051 option_lists.append((_("global options:"), globalopts))
2052 if name == 'shortlist':
2052 if name == 'shortlist':
2053 option_lists.append((_('use "hg help" for the full list '
2053 option_lists.append((_('use "hg help" for the full list '
2054 'of commands'), ()))
2054 'of commands'), ()))
2055 else:
2055 else:
2056 if name == 'shortlist':
2056 if name == 'shortlist':
2057 msg = _('use "hg help" for the full list of commands '
2057 msg = _('use "hg help" for the full list of commands '
2058 'or "hg -v" for details')
2058 'or "hg -v" for details')
2059 elif aliases:
2059 elif aliases:
2060 msg = _('use "hg -v help%s" to show builtin aliases and '
2060 msg = _('use "hg -v help%s" to show builtin aliases and '
2061 'global options') % (name and " " + name or "")
2061 'global options') % (name and " " + name or "")
2062 else:
2062 else:
2063 msg = _('use "hg -v help %s" to show global options') % name
2063 msg = _('use "hg -v help %s" to show global options') % name
2064 option_lists.append((msg, ()))
2064 option_lists.append((msg, ()))
2065
2065
2066 def helpcmd(name):
2066 def helpcmd(name):
2067 if with_version:
2067 if with_version:
2068 version_(ui)
2068 version_(ui)
2069 ui.write('\n')
2069 ui.write('\n')
2070
2070
2071 try:
2071 try:
2072 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
2072 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
2073 except error.AmbiguousCommand, inst:
2073 except error.AmbiguousCommand, inst:
2074 # py3k fix: except vars can't be used outside the scope of the
2074 # py3k fix: except vars can't be used outside the scope of the
2075 # except block, nor can be used inside a lambda. python issue4617
2075 # except block, nor can be used inside a lambda. python issue4617
2076 prefix = inst.args[0]
2076 prefix = inst.args[0]
2077 select = lambda c: c.lstrip('^').startswith(prefix)
2077 select = lambda c: c.lstrip('^').startswith(prefix)
2078 helplist(_('list of commands:\n\n'), select)
2078 helplist(_('list of commands:\n\n'), select)
2079 return
2079 return
2080
2080
2081 # check if it's an invalid alias and display its error if it is
2081 # check if it's an invalid alias and display its error if it is
2082 if getattr(entry[0], 'badalias', False):
2082 if getattr(entry[0], 'badalias', False):
2083 if not unknowncmd:
2083 if not unknowncmd:
2084 entry[0](ui)
2084 entry[0](ui)
2085 return
2085 return
2086
2086
2087 # synopsis
2087 # synopsis
2088 if len(entry) > 2:
2088 if len(entry) > 2:
2089 if entry[2].startswith('hg'):
2089 if entry[2].startswith('hg'):
2090 ui.write("%s\n" % entry[2])
2090 ui.write("%s\n" % entry[2])
2091 else:
2091 else:
2092 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2092 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2093 else:
2093 else:
2094 ui.write('hg %s\n' % aliases[0])
2094 ui.write('hg %s\n' % aliases[0])
2095
2095
2096 # aliases
2096 # aliases
2097 if not ui.quiet and len(aliases) > 1:
2097 if not ui.quiet and len(aliases) > 1:
2098 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2098 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2099
2099
2100 # description
2100 # description
2101 doc = gettext(entry[0].__doc__)
2101 doc = gettext(entry[0].__doc__)
2102 if not doc:
2102 if not doc:
2103 doc = _("(no help text available)")
2103 doc = _("(no help text available)")
2104 if hasattr(entry[0], 'definition'): # aliased command
2104 if hasattr(entry[0], 'definition'): # aliased command
2105 if entry[0].definition.startswith('!'): # shell alias
2105 if entry[0].definition.startswith('!'): # shell alias
2106 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2106 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2107 else:
2107 else:
2108 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2108 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2109 if ui.quiet:
2109 if ui.quiet:
2110 doc = doc.splitlines()[0]
2110 doc = doc.splitlines()[0]
2111 keep = ui.verbose and ['verbose'] or []
2111 keep = ui.verbose and ['verbose'] or []
2112 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2112 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2113 ui.write("\n%s\n" % formatted)
2113 ui.write("\n%s\n" % formatted)
2114 if pruned:
2114 if pruned:
2115 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2115 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2116
2116
2117 if not ui.quiet:
2117 if not ui.quiet:
2118 # options
2118 # options
2119 if entry[1]:
2119 if entry[1]:
2120 option_lists.append((_("options:\n"), entry[1]))
2120 option_lists.append((_("options:\n"), entry[1]))
2121
2121
2122 addglobalopts(False)
2122 addglobalopts(False)
2123
2123
2124 def helplist(header, select=None):
2124 def helplist(header, select=None):
2125 h = {}
2125 h = {}
2126 cmds = {}
2126 cmds = {}
2127 for c, e in table.iteritems():
2127 for c, e in table.iteritems():
2128 f = c.split("|", 1)[0]
2128 f = c.split("|", 1)[0]
2129 if select and not select(f):
2129 if select and not select(f):
2130 continue
2130 continue
2131 if (not select and name != 'shortlist' and
2131 if (not select and name != 'shortlist' and
2132 e[0].__module__ != __name__):
2132 e[0].__module__ != __name__):
2133 continue
2133 continue
2134 if name == "shortlist" and not f.startswith("^"):
2134 if name == "shortlist" and not f.startswith("^"):
2135 continue
2135 continue
2136 f = f.lstrip("^")
2136 f = f.lstrip("^")
2137 if not ui.debugflag and f.startswith("debug"):
2137 if not ui.debugflag and f.startswith("debug"):
2138 continue
2138 continue
2139 doc = e[0].__doc__
2139 doc = e[0].__doc__
2140 if doc and 'DEPRECATED' in doc and not ui.verbose:
2140 if doc and 'DEPRECATED' in doc and not ui.verbose:
2141 continue
2141 continue
2142 doc = gettext(doc)
2142 doc = gettext(doc)
2143 if not doc:
2143 if not doc:
2144 doc = _("(no help text available)")
2144 doc = _("(no help text available)")
2145 h[f] = doc.splitlines()[0].rstrip()
2145 h[f] = doc.splitlines()[0].rstrip()
2146 cmds[f] = c.lstrip("^")
2146 cmds[f] = c.lstrip("^")
2147
2147
2148 if not h:
2148 if not h:
2149 ui.status(_('no commands defined\n'))
2149 ui.status(_('no commands defined\n'))
2150 return
2150 return
2151
2151
2152 ui.status(header)
2152 ui.status(header)
2153 fns = sorted(h)
2153 fns = sorted(h)
2154 m = max(map(len, fns))
2154 m = max(map(len, fns))
2155 for f in fns:
2155 for f in fns:
2156 if ui.verbose:
2156 if ui.verbose:
2157 commands = cmds[f].replace("|",", ")
2157 commands = cmds[f].replace("|",", ")
2158 ui.write(" %s:\n %s\n"%(commands, h[f]))
2158 ui.write(" %s:\n %s\n"%(commands, h[f]))
2159 else:
2159 else:
2160 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2160 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2161 initindent=' %-*s ' % (m, f),
2161 initindent=' %-*s ' % (m, f),
2162 hangindent=' ' * (m + 4))))
2162 hangindent=' ' * (m + 4))))
2163
2163
2164 if not ui.quiet:
2164 if not ui.quiet:
2165 addglobalopts(True)
2165 addglobalopts(True)
2166
2166
2167 def helptopic(name):
2167 def helptopic(name):
2168 for names, header, doc in help.helptable:
2168 for names, header, doc in help.helptable:
2169 if name in names:
2169 if name in names:
2170 break
2170 break
2171 else:
2171 else:
2172 raise error.UnknownCommand(name)
2172 raise error.UnknownCommand(name)
2173
2173
2174 # description
2174 # description
2175 if not doc:
2175 if not doc:
2176 doc = _("(no help text available)")
2176 doc = _("(no help text available)")
2177 if hasattr(doc, '__call__'):
2177 if hasattr(doc, '__call__'):
2178 doc = doc()
2178 doc = doc()
2179
2179
2180 ui.write("%s\n\n" % header)
2180 ui.write("%s\n\n" % header)
2181 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2181 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2182
2182
2183 def helpext(name):
2183 def helpext(name):
2184 try:
2184 try:
2185 mod = extensions.find(name)
2185 mod = extensions.find(name)
2186 doc = gettext(mod.__doc__) or _('no help text available')
2186 doc = gettext(mod.__doc__) or _('no help text available')
2187 except KeyError:
2187 except KeyError:
2188 mod = None
2188 mod = None
2189 doc = extensions.disabledext(name)
2189 doc = extensions.disabledext(name)
2190 if not doc:
2190 if not doc:
2191 raise error.UnknownCommand(name)
2191 raise error.UnknownCommand(name)
2192
2192
2193 if '\n' not in doc:
2193 if '\n' not in doc:
2194 head, tail = doc, ""
2194 head, tail = doc, ""
2195 else:
2195 else:
2196 head, tail = doc.split('\n', 1)
2196 head, tail = doc.split('\n', 1)
2197 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2197 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2198 if tail:
2198 if tail:
2199 ui.write(minirst.format(tail, textwidth))
2199 ui.write(minirst.format(tail, textwidth))
2200 ui.status('\n\n')
2200 ui.status('\n\n')
2201
2201
2202 if mod:
2202 if mod:
2203 try:
2203 try:
2204 ct = mod.cmdtable
2204 ct = mod.cmdtable
2205 except AttributeError:
2205 except AttributeError:
2206 ct = {}
2206 ct = {}
2207 modcmds = set([c.split('|', 1)[0] for c in ct])
2207 modcmds = set([c.split('|', 1)[0] for c in ct])
2208 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2208 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2209 else:
2209 else:
2210 ui.write(_('use "hg help extensions" for information on enabling '
2210 ui.write(_('use "hg help extensions" for information on enabling '
2211 'extensions\n'))
2211 'extensions\n'))
2212
2212
2213 def helpextcmd(name):
2213 def helpextcmd(name):
2214 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2214 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2215 doc = gettext(mod.__doc__).splitlines()[0]
2215 doc = gettext(mod.__doc__).splitlines()[0]
2216
2216
2217 msg = help.listexts(_("'%s' is provided by the following "
2217 msg = help.listexts(_("'%s' is provided by the following "
2218 "extension:") % cmd, {ext: doc}, len(ext),
2218 "extension:") % cmd, {ext: doc}, len(ext),
2219 indent=4)
2219 indent=4)
2220 ui.write(minirst.format(msg, textwidth))
2220 ui.write(minirst.format(msg, textwidth))
2221 ui.write('\n\n')
2221 ui.write('\n\n')
2222 ui.write(_('use "hg help extensions" for information on enabling '
2222 ui.write(_('use "hg help extensions" for information on enabling '
2223 'extensions\n'))
2223 'extensions\n'))
2224
2224
2225 help.addtopichook('revsets', revset.makedoc)
2225 help.addtopichook('revsets', revset.makedoc)
2226 help.addtopichook('templates', templatekw.makedoc)
2226 help.addtopichook('templates', templatekw.makedoc)
2227 help.addtopichook('templates', templatefilters.makedoc)
2227 help.addtopichook('templates', templatefilters.makedoc)
2228
2228
2229 if name and name != 'shortlist':
2229 if name and name != 'shortlist':
2230 i = None
2230 i = None
2231 if unknowncmd:
2231 if unknowncmd:
2232 queries = (helpextcmd,)
2232 queries = (helpextcmd,)
2233 else:
2233 else:
2234 queries = (helptopic, helpcmd, helpext, helpextcmd)
2234 queries = (helptopic, helpcmd, helpext, helpextcmd)
2235 for f in queries:
2235 for f in queries:
2236 try:
2236 try:
2237 f(name)
2237 f(name)
2238 i = None
2238 i = None
2239 break
2239 break
2240 except error.UnknownCommand, inst:
2240 except error.UnknownCommand, inst:
2241 i = inst
2241 i = inst
2242 if i:
2242 if i:
2243 raise i
2243 raise i
2244
2244
2245 else:
2245 else:
2246 # program name
2246 # program name
2247 if ui.verbose or with_version:
2247 if ui.verbose or with_version:
2248 version_(ui)
2248 version_(ui)
2249 else:
2249 else:
2250 ui.status(_("Mercurial Distributed SCM\n"))
2250 ui.status(_("Mercurial Distributed SCM\n"))
2251 ui.status('\n')
2251 ui.status('\n')
2252
2252
2253 # list of commands
2253 # list of commands
2254 if name == "shortlist":
2254 if name == "shortlist":
2255 header = _('basic commands:\n\n')
2255 header = _('basic commands:\n\n')
2256 else:
2256 else:
2257 header = _('list of commands:\n\n')
2257 header = _('list of commands:\n\n')
2258
2258
2259 helplist(header)
2259 helplist(header)
2260 if name != 'shortlist':
2260 if name != 'shortlist':
2261 exts, maxlength = extensions.enabled()
2261 exts, maxlength = extensions.enabled()
2262 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2262 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2263 if text:
2263 if text:
2264 ui.write("\n%s\n" % minirst.format(text, textwidth))
2264 ui.write("\n%s\n" % minirst.format(text, textwidth))
2265
2265
2266 # list all option lists
2266 # list all option lists
2267 opt_output = []
2267 opt_output = []
2268 multioccur = False
2268 multioccur = False
2269 for title, options in option_lists:
2269 for title, options in option_lists:
2270 opt_output.append(("\n%s" % title, None))
2270 opt_output.append(("\n%s" % title, None))
2271 for option in options:
2271 for option in options:
2272 if len(option) == 5:
2272 if len(option) == 5:
2273 shortopt, longopt, default, desc, optlabel = option
2273 shortopt, longopt, default, desc, optlabel = option
2274 else:
2274 else:
2275 shortopt, longopt, default, desc = option
2275 shortopt, longopt, default, desc = option
2276 optlabel = _("VALUE") # default label
2276 optlabel = _("VALUE") # default label
2277
2277
2278 if _("DEPRECATED") in desc and not ui.verbose:
2278 if _("DEPRECATED") in desc and not ui.verbose:
2279 continue
2279 continue
2280 if isinstance(default, list):
2280 if isinstance(default, list):
2281 numqualifier = " %s [+]" % optlabel
2281 numqualifier = " %s [+]" % optlabel
2282 multioccur = True
2282 multioccur = True
2283 elif (default is not None) and not isinstance(default, bool):
2283 elif (default is not None) and not isinstance(default, bool):
2284 numqualifier = " %s" % optlabel
2284 numqualifier = " %s" % optlabel
2285 else:
2285 else:
2286 numqualifier = ""
2286 numqualifier = ""
2287 opt_output.append(("%2s%s" %
2287 opt_output.append(("%2s%s" %
2288 (shortopt and "-%s" % shortopt,
2288 (shortopt and "-%s" % shortopt,
2289 longopt and " --%s%s" %
2289 longopt and " --%s%s" %
2290 (longopt, numqualifier)),
2290 (longopt, numqualifier)),
2291 "%s%s" % (desc,
2291 "%s%s" % (desc,
2292 default
2292 default
2293 and _(" (default: %s)") % default
2293 and _(" (default: %s)") % default
2294 or "")))
2294 or "")))
2295 if multioccur:
2295 if multioccur:
2296 msg = _("\n[+] marked option can be specified multiple times")
2296 msg = _("\n[+] marked option can be specified multiple times")
2297 if ui.verbose and name != 'shortlist':
2297 if ui.verbose and name != 'shortlist':
2298 opt_output.append((msg, None))
2298 opt_output.append((msg, None))
2299 else:
2299 else:
2300 opt_output.insert(-1, (msg, None))
2300 opt_output.insert(-1, (msg, None))
2301
2301
2302 if not name:
2302 if not name:
2303 ui.write(_("\nadditional help topics:\n\n"))
2303 ui.write(_("\nadditional help topics:\n\n"))
2304 topics = []
2304 topics = []
2305 for names, header, doc in help.helptable:
2305 for names, header, doc in help.helptable:
2306 topics.append((sorted(names, key=len, reverse=True)[0], header))
2306 topics.append((sorted(names, key=len, reverse=True)[0], header))
2307 topics_len = max([len(s[0]) for s in topics])
2307 topics_len = max([len(s[0]) for s in topics])
2308 for t, desc in topics:
2308 for t, desc in topics:
2309 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2309 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2310
2310
2311 if opt_output:
2311 if opt_output:
2312 colwidth = encoding.colwidth
2312 colwidth = encoding.colwidth
2313 # normalize: (opt or message, desc or None, width of opt)
2313 # normalize: (opt or message, desc or None, width of opt)
2314 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2314 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2315 for opt, desc in opt_output]
2315 for opt, desc in opt_output]
2316 hanging = max([e[2] for e in entries])
2316 hanging = max([e[2] for e in entries])
2317 for opt, desc, width in entries:
2317 for opt, desc, width in entries:
2318 if desc:
2318 if desc:
2319 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2319 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2320 hangindent = ' ' * (hanging + 3)
2320 hangindent = ' ' * (hanging + 3)
2321 ui.write('%s\n' % (util.wrap(desc, textwidth,
2321 ui.write('%s\n' % (util.wrap(desc, textwidth,
2322 initindent=initindent,
2322 initindent=initindent,
2323 hangindent=hangindent)))
2323 hangindent=hangindent)))
2324 else:
2324 else:
2325 ui.write("%s\n" % opt)
2325 ui.write("%s\n" % opt)
2326
2326
2327 def identify(ui, repo, source=None, rev=None,
2327 def identify(ui, repo, source=None, rev=None,
2328 num=None, id=None, branch=None, tags=None, bookmarks=None):
2328 num=None, id=None, branch=None, tags=None, bookmarks=None):
2329 """identify the working copy or specified revision
2329 """identify the working copy or specified revision
2330
2330
2331 With no revision, print a summary of the current state of the
2331 With no revision, print a summary of the current state of the
2332 repository.
2332 repository.
2333
2333
2334 Specifying a path to a repository root or Mercurial bundle will
2334 Specifying a path to a repository root or Mercurial bundle will
2335 cause lookup to operate on that repository/bundle.
2335 cause lookup to operate on that repository/bundle.
2336
2336
2337 This summary identifies the repository state using one or two
2337 This summary identifies the repository state using one or two
2338 parent hash identifiers, followed by a "+" if there are
2338 parent hash identifiers, followed by a "+" if there are
2339 uncommitted changes in the working directory, a list of tags for
2339 uncommitted changes in the working directory, a list of tags for
2340 this revision and a branch name for non-default branches.
2340 this revision and a branch name for non-default branches.
2341
2341
2342 Returns 0 if successful.
2342 Returns 0 if successful.
2343 """
2343 """
2344
2344
2345 if not repo and not source:
2345 if not repo and not source:
2346 raise util.Abort(_("there is no Mercurial repository here "
2346 raise util.Abort(_("there is no Mercurial repository here "
2347 "(.hg not found)"))
2347 "(.hg not found)"))
2348
2348
2349 hexfunc = ui.debugflag and hex or short
2349 hexfunc = ui.debugflag and hex or short
2350 default = not (num or id or branch or tags or bookmarks)
2350 default = not (num or id or branch or tags or bookmarks)
2351 output = []
2351 output = []
2352
2352
2353 revs = []
2353 revs = []
2354 bms = []
2354 bms = []
2355 if source:
2355 if source:
2356 source, branches = hg.parseurl(ui.expandpath(source))
2356 source, branches = hg.parseurl(ui.expandpath(source))
2357 repo = hg.repository(ui, source)
2357 repo = hg.repository(ui, source)
2358 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2358 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2359
2359
2360 if not repo.local():
2360 if not repo.local():
2361 if not rev and revs:
2361 if not rev and revs:
2362 rev = revs[0]
2362 rev = revs[0]
2363 if not rev:
2363 if not rev:
2364 rev = "tip"
2364 rev = "tip"
2365 if num or branch or tags:
2365 if num or branch or tags:
2366 raise util.Abort(
2366 raise util.Abort(
2367 _("can't query remote revision number, branch, or tags"))
2367 _("can't query remote revision number, branch, or tags"))
2368
2368
2369 remoterev = repo.lookup(rev)
2369 remoterev = repo.lookup(rev)
2370 if default or id:
2370 if default or id:
2371 output = [hexfunc(remoterev)]
2371 output = [hexfunc(remoterev)]
2372
2372
2373 if 'bookmarks' in repo.listkeys('namespaces'):
2373 if 'bookmarks' in repo.listkeys('namespaces'):
2374 hexremoterev = hex(remoterev)
2374 hexremoterev = hex(remoterev)
2375 bms = [bm for bm, bmrev in repo.listkeys('bookmarks').iteritems()
2375 bms = [bm for bm, bmrev in repo.listkeys('bookmarks').iteritems()
2376 if bmrev == hexremoterev]
2376 if bmrev == hexremoterev]
2377
2377
2378 elif not rev:
2378 elif not rev:
2379 ctx = repo[None]
2379 ctx = repo[None]
2380 parents = ctx.parents()
2380 parents = ctx.parents()
2381 changed = False
2381 changed = False
2382 if default or id or num:
2382 if default or id or num:
2383 changed = util.any(repo.status())
2383 changed = util.any(repo.status())
2384 if default or id:
2384 if default or id:
2385 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
2385 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
2386 (changed) and "+" or "")]
2386 (changed) and "+" or "")]
2387 if num:
2387 if num:
2388 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
2388 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
2389 (changed) and "+" or ""))
2389 (changed) and "+" or ""))
2390 else:
2390 else:
2391 ctx = cmdutil.revsingle(repo, rev)
2391 ctx = cmdutil.revsingle(repo, rev)
2392 if default or id:
2392 if default or id:
2393 output = [hexfunc(ctx.node())]
2393 output = [hexfunc(ctx.node())]
2394 if num:
2394 if num:
2395 output.append(str(ctx.rev()))
2395 output.append(str(ctx.rev()))
2396
2396
2397 if repo.local():
2397 if repo.local():
2398 bms = ctx.bookmarks()
2398 bms = ctx.bookmarks()
2399
2399
2400 if repo.local() and default and not ui.quiet:
2400 if repo.local() and default and not ui.quiet:
2401 b = ctx.branch()
2401 b = ctx.branch()
2402 if b != 'default':
2402 if b != 'default':
2403 output.append("(%s)" % b)
2403 output.append("(%s)" % b)
2404
2404
2405 # multiple tags for a single parent separated by '/'
2405 # multiple tags for a single parent separated by '/'
2406 t = "/".join(ctx.tags())
2406 t = "/".join(ctx.tags())
2407 if t:
2407 if t:
2408 output.append(t)
2408 output.append(t)
2409
2409
2410 if default and not ui.quiet:
2410 if default and not ui.quiet:
2411 # multiple bookmarks for a single parent separated by '/'
2411 # multiple bookmarks for a single parent separated by '/'
2412 bm = '/'.join(bms)
2412 bm = '/'.join(bms)
2413 if bm:
2413 if bm:
2414 output.append(bm)
2414 output.append(bm)
2415
2415
2416 if branch:
2416 if branch:
2417 output.append(ctx.branch())
2417 output.append(ctx.branch())
2418
2418
2419 if tags:
2419 if tags:
2420 output.extend(ctx.tags())
2420 output.extend(ctx.tags())
2421
2421
2422 if bookmarks:
2422 if bookmarks:
2423 output.extend(bms)
2423 output.extend(bms)
2424
2424
2425 ui.write("%s\n" % ' '.join(output))
2425 ui.write("%s\n" % ' '.join(output))
2426
2426
2427 def import_(ui, repo, patch1, *patches, **opts):
2427 def import_(ui, repo, patch1, *patches, **opts):
2428 """import an ordered set of patches
2428 """import an ordered set of patches
2429
2429
2430 Import a list of patches and commit them individually (unless
2430 Import a list of patches and commit them individually (unless
2431 --no-commit is specified).
2431 --no-commit is specified).
2432
2432
2433 If there are outstanding changes in the working directory, import
2433 If there are outstanding changes in the working directory, import
2434 will abort unless given the -f/--force flag.
2434 will abort unless given the -f/--force flag.
2435
2435
2436 You can import a patch straight from a mail message. Even patches
2436 You can import a patch straight from a mail message. Even patches
2437 as attachments work (to use the body part, it must have type
2437 as attachments work (to use the body part, it must have type
2438 text/plain or text/x-patch). From and Subject headers of email
2438 text/plain or text/x-patch). From and Subject headers of email
2439 message are used as default committer and commit message. All
2439 message are used as default committer and commit message. All
2440 text/plain body parts before first diff are added to commit
2440 text/plain body parts before first diff are added to commit
2441 message.
2441 message.
2442
2442
2443 If the imported patch was generated by :hg:`export`, user and
2443 If the imported patch was generated by :hg:`export`, user and
2444 description from patch override values from message headers and
2444 description from patch override values from message headers and
2445 body. Values given on command line with -m/--message and -u/--user
2445 body. Values given on command line with -m/--message and -u/--user
2446 override these.
2446 override these.
2447
2447
2448 If --exact is specified, import will set the working directory to
2448 If --exact is specified, import will set the working directory to
2449 the parent of each patch before applying it, and will abort if the
2449 the parent of each patch before applying it, and will abort if the
2450 resulting changeset has a different ID than the one recorded in
2450 resulting changeset has a different ID than the one recorded in
2451 the patch. This may happen due to character set problems or other
2451 the patch. This may happen due to character set problems or other
2452 deficiencies in the text patch format.
2452 deficiencies in the text patch format.
2453
2453
2454 With -s/--similarity, hg will attempt to discover renames and
2454 With -s/--similarity, hg will attempt to discover renames and
2455 copies in the patch in the same way as 'addremove'.
2455 copies in the patch in the same way as 'addremove'.
2456
2456
2457 To read a patch from standard input, use "-" as the patch name. If
2457 To read a patch from standard input, use "-" as the patch name. If
2458 a URL is specified, the patch will be downloaded from it.
2458 a URL is specified, the patch will be downloaded from it.
2459 See :hg:`help dates` for a list of formats valid for -d/--date.
2459 See :hg:`help dates` for a list of formats valid for -d/--date.
2460
2460
2461 Returns 0 on success.
2461 Returns 0 on success.
2462 """
2462 """
2463 patches = (patch1,) + patches
2463 patches = (patch1,) + patches
2464
2464
2465 date = opts.get('date')
2465 date = opts.get('date')
2466 if date:
2466 if date:
2467 opts['date'] = util.parsedate(date)
2467 opts['date'] = util.parsedate(date)
2468
2468
2469 try:
2469 try:
2470 sim = float(opts.get('similarity') or 0)
2470 sim = float(opts.get('similarity') or 0)
2471 except ValueError:
2471 except ValueError:
2472 raise util.Abort(_('similarity must be a number'))
2472 raise util.Abort(_('similarity must be a number'))
2473 if sim < 0 or sim > 100:
2473 if sim < 0 or sim > 100:
2474 raise util.Abort(_('similarity must be between 0 and 100'))
2474 raise util.Abort(_('similarity must be between 0 and 100'))
2475
2475
2476 if opts.get('exact') or not opts.get('force'):
2476 if opts.get('exact') or not opts.get('force'):
2477 cmdutil.bail_if_changed(repo)
2477 cmdutil.bail_if_changed(repo)
2478
2478
2479 d = opts["base"]
2479 d = opts["base"]
2480 strip = opts["strip"]
2480 strip = opts["strip"]
2481 wlock = lock = None
2481 wlock = lock = None
2482 msgs = []
2482 msgs = []
2483
2483
2484 def tryone(ui, hunk):
2484 def tryone(ui, hunk):
2485 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2485 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2486 patch.extract(ui, hunk)
2486 patch.extract(ui, hunk)
2487
2487
2488 if not tmpname:
2488 if not tmpname:
2489 return None
2489 return None
2490 commitid = _('to working directory')
2490 commitid = _('to working directory')
2491
2491
2492 try:
2492 try:
2493 cmdline_message = cmdutil.logmessage(opts)
2493 cmdline_message = cmdutil.logmessage(opts)
2494 if cmdline_message:
2494 if cmdline_message:
2495 # pickup the cmdline msg
2495 # pickup the cmdline msg
2496 message = cmdline_message
2496 message = cmdline_message
2497 elif message:
2497 elif message:
2498 # pickup the patch msg
2498 # pickup the patch msg
2499 message = message.strip()
2499 message = message.strip()
2500 else:
2500 else:
2501 # launch the editor
2501 # launch the editor
2502 message = None
2502 message = None
2503 ui.debug('message:\n%s\n' % message)
2503 ui.debug('message:\n%s\n' % message)
2504
2504
2505 wp = repo.parents()
2505 wp = repo.parents()
2506 if opts.get('exact'):
2506 if opts.get('exact'):
2507 if not nodeid or not p1:
2507 if not nodeid or not p1:
2508 raise util.Abort(_('not a Mercurial patch'))
2508 raise util.Abort(_('not a Mercurial patch'))
2509 p1 = repo.lookup(p1)
2509 p1 = repo.lookup(p1)
2510 p2 = repo.lookup(p2 or hex(nullid))
2510 p2 = repo.lookup(p2 or hex(nullid))
2511
2511
2512 if p1 != wp[0].node():
2512 if p1 != wp[0].node():
2513 hg.clean(repo, p1)
2513 hg.clean(repo, p1)
2514 repo.dirstate.setparents(p1, p2)
2514 repo.dirstate.setparents(p1, p2)
2515 elif p2:
2515 elif p2:
2516 try:
2516 try:
2517 p1 = repo.lookup(p1)
2517 p1 = repo.lookup(p1)
2518 p2 = repo.lookup(p2)
2518 p2 = repo.lookup(p2)
2519 if p1 == wp[0].node():
2519 if p1 == wp[0].node():
2520 repo.dirstate.setparents(p1, p2)
2520 repo.dirstate.setparents(p1, p2)
2521 except error.RepoError:
2521 except error.RepoError:
2522 pass
2522 pass
2523 if opts.get('exact') or opts.get('import_branch'):
2523 if opts.get('exact') or opts.get('import_branch'):
2524 repo.dirstate.setbranch(branch or 'default')
2524 repo.dirstate.setbranch(branch or 'default')
2525
2525
2526 files = {}
2526 files = {}
2527 try:
2527 try:
2528 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2528 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2529 files=files, eolmode=None)
2529 files=files, eolmode=None)
2530 finally:
2530 finally:
2531 files = cmdutil.updatedir(ui, repo, files,
2531 files = cmdutil.updatedir(ui, repo, files,
2532 similarity=sim / 100.0)
2532 similarity=sim / 100.0)
2533 if opts.get('no_commit'):
2533 if opts.get('no_commit'):
2534 if message:
2534 if message:
2535 msgs.append(message)
2535 msgs.append(message)
2536 else:
2536 else:
2537 if opts.get('exact'):
2537 if opts.get('exact'):
2538 m = None
2538 m = None
2539 else:
2539 else:
2540 m = cmdutil.matchfiles(repo, files or [])
2540 m = cmdutil.matchfiles(repo, files or [])
2541 n = repo.commit(message, opts.get('user') or user,
2541 n = repo.commit(message, opts.get('user') or user,
2542 opts.get('date') or date, match=m,
2542 opts.get('date') or date, match=m,
2543 editor=cmdutil.commiteditor)
2543 editor=cmdutil.commiteditor)
2544 if opts.get('exact'):
2544 if opts.get('exact'):
2545 if hex(n) != nodeid:
2545 if hex(n) != nodeid:
2546 repo.rollback()
2546 repo.rollback()
2547 raise util.Abort(_('patch is damaged'
2547 raise util.Abort(_('patch is damaged'
2548 ' or loses information'))
2548 ' or loses information'))
2549 # Force a dirstate write so that the next transaction
2549 # Force a dirstate write so that the next transaction
2550 # backups an up-do-date file.
2550 # backups an up-do-date file.
2551 repo.dirstate.write()
2551 repo.dirstate.write()
2552 if n:
2552 if n:
2553 commitid = short(n)
2553 commitid = short(n)
2554
2554
2555 return commitid
2555 return commitid
2556 finally:
2556 finally:
2557 os.unlink(tmpname)
2557 os.unlink(tmpname)
2558
2558
2559 try:
2559 try:
2560 wlock = repo.wlock()
2560 wlock = repo.wlock()
2561 lock = repo.lock()
2561 lock = repo.lock()
2562 lastcommit = None
2562 lastcommit = None
2563 for p in patches:
2563 for p in patches:
2564 pf = os.path.join(d, p)
2564 pf = os.path.join(d, p)
2565
2565
2566 if pf == '-':
2566 if pf == '-':
2567 ui.status(_("applying patch from stdin\n"))
2567 ui.status(_("applying patch from stdin\n"))
2568 pf = sys.stdin
2568 pf = sys.stdin
2569 else:
2569 else:
2570 ui.status(_("applying %s\n") % p)
2570 ui.status(_("applying %s\n") % p)
2571 pf = url.open(ui, pf)
2571 pf = url.open(ui, pf)
2572
2572
2573 haspatch = False
2573 haspatch = False
2574 for hunk in patch.split(pf):
2574 for hunk in patch.split(pf):
2575 commitid = tryone(ui, hunk)
2575 commitid = tryone(ui, hunk)
2576 if commitid:
2576 if commitid:
2577 haspatch = True
2577 haspatch = True
2578 if lastcommit:
2578 if lastcommit:
2579 ui.status(_('applied %s\n') % lastcommit)
2579 ui.status(_('applied %s\n') % lastcommit)
2580 lastcommit = commitid
2580 lastcommit = commitid
2581
2581
2582 if not haspatch:
2582 if not haspatch:
2583 raise util.Abort(_('no diffs found'))
2583 raise util.Abort(_('no diffs found'))
2584
2584
2585 if msgs:
2585 if msgs:
2586 repo.opener('last-message.txt', 'wb').write('\n* * *\n'.join(msgs))
2586 repo.opener('last-message.txt', 'wb').write('\n* * *\n'.join(msgs))
2587 finally:
2587 finally:
2588 release(lock, wlock)
2588 release(lock, wlock)
2589
2589
2590 def incoming(ui, repo, source="default", **opts):
2590 def incoming(ui, repo, source="default", **opts):
2591 """show new changesets found in source
2591 """show new changesets found in source
2592
2592
2593 Show new changesets found in the specified path/URL or the default
2593 Show new changesets found in the specified path/URL or the default
2594 pull location. These are the changesets that would have been pulled
2594 pull location. These are the changesets that would have been pulled
2595 if a pull at the time you issued this command.
2595 if a pull at the time you issued this command.
2596
2596
2597 For remote repository, using --bundle avoids downloading the
2597 For remote repository, using --bundle avoids downloading the
2598 changesets twice if the incoming is followed by a pull.
2598 changesets twice if the incoming is followed by a pull.
2599
2599
2600 See pull for valid source format details.
2600 See pull for valid source format details.
2601
2601
2602 Returns 0 if there are incoming changes, 1 otherwise.
2602 Returns 0 if there are incoming changes, 1 otherwise.
2603 """
2603 """
2604 if opts.get('bundle') and opts.get('subrepos'):
2604 if opts.get('bundle') and opts.get('subrepos'):
2605 raise util.Abort(_('cannot combine --bundle and --subrepos'))
2605 raise util.Abort(_('cannot combine --bundle and --subrepos'))
2606
2606
2607 if opts.get('bookmarks'):
2607 if opts.get('bookmarks'):
2608 source, branches = hg.parseurl(ui.expandpath(source),
2608 source, branches = hg.parseurl(ui.expandpath(source),
2609 opts.get('branch'))
2609 opts.get('branch'))
2610 other = hg.repository(hg.remoteui(repo, opts), source)
2610 other = hg.repository(hg.remoteui(repo, opts), source)
2611 if 'bookmarks' not in other.listkeys('namespaces'):
2611 if 'bookmarks' not in other.listkeys('namespaces'):
2612 ui.warn(_("remote doesn't support bookmarks\n"))
2612 ui.warn(_("remote doesn't support bookmarks\n"))
2613 return 0
2613 return 0
2614 ui.status(_('comparing with %s\n') % url.hidepassword(source))
2614 ui.status(_('comparing with %s\n') % url.hidepassword(source))
2615 return bookmarks.diff(ui, repo, other)
2615 return bookmarks.diff(ui, repo, other)
2616
2616
2617 ret = hg.incoming(ui, repo, source, opts)
2617 ret = hg.incoming(ui, repo, source, opts)
2618 return ret
2618 return ret
2619
2619
2620 def init(ui, dest=".", **opts):
2620 def init(ui, dest=".", **opts):
2621 """create a new repository in the given directory
2621 """create a new repository in the given directory
2622
2622
2623 Initialize a new repository in the given directory. If the given
2623 Initialize a new repository in the given directory. If the given
2624 directory does not exist, it will be created.
2624 directory does not exist, it will be created.
2625
2625
2626 If no directory is given, the current directory is used.
2626 If no directory is given, the current directory is used.
2627
2627
2628 It is possible to specify an ``ssh://`` URL as the destination.
2628 It is possible to specify an ``ssh://`` URL as the destination.
2629 See :hg:`help urls` for more information.
2629 See :hg:`help urls` for more information.
2630
2630
2631 Returns 0 on success.
2631 Returns 0 on success.
2632 """
2632 """
2633 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=1)
2633 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=1)
2634
2634
2635 def locate(ui, repo, *pats, **opts):
2635 def locate(ui, repo, *pats, **opts):
2636 """locate files matching specific patterns
2636 """locate files matching specific patterns
2637
2637
2638 Print files under Mercurial control in the working directory whose
2638 Print files under Mercurial control in the working directory whose
2639 names match the given patterns.
2639 names match the given patterns.
2640
2640
2641 By default, this command searches all directories in the working
2641 By default, this command searches all directories in the working
2642 directory. To search just the current directory and its
2642 directory. To search just the current directory and its
2643 subdirectories, use "--include .".
2643 subdirectories, use "--include .".
2644
2644
2645 If no patterns are given to match, this command prints the names
2645 If no patterns are given to match, this command prints the names
2646 of all files under Mercurial control in the working directory.
2646 of all files under Mercurial control in the working directory.
2647
2647
2648 If you want to feed the output of this command into the "xargs"
2648 If you want to feed the output of this command into the "xargs"
2649 command, use the -0 option to both this command and "xargs". This
2649 command, use the -0 option to both this command and "xargs". This
2650 will avoid the problem of "xargs" treating single filenames that
2650 will avoid the problem of "xargs" treating single filenames that
2651 contain whitespace as multiple filenames.
2651 contain whitespace as multiple filenames.
2652
2652
2653 Returns 0 if a match is found, 1 otherwise.
2653 Returns 0 if a match is found, 1 otherwise.
2654 """
2654 """
2655 end = opts.get('print0') and '\0' or '\n'
2655 end = opts.get('print0') and '\0' or '\n'
2656 rev = cmdutil.revsingle(repo, opts.get('rev'), None).node()
2656 rev = cmdutil.revsingle(repo, opts.get('rev'), None).node()
2657
2657
2658 ret = 1
2658 ret = 1
2659 m = cmdutil.match(repo, pats, opts, default='relglob')
2659 m = cmdutil.match(repo, pats, opts, default='relglob')
2660 m.bad = lambda x, y: False
2660 m.bad = lambda x, y: False
2661 for abs in repo[rev].walk(m):
2661 for abs in repo[rev].walk(m):
2662 if not rev and abs not in repo.dirstate:
2662 if not rev and abs not in repo.dirstate:
2663 continue
2663 continue
2664 if opts.get('fullpath'):
2664 if opts.get('fullpath'):
2665 ui.write(repo.wjoin(abs), end)
2665 ui.write(repo.wjoin(abs), end)
2666 else:
2666 else:
2667 ui.write(((pats and m.rel(abs)) or abs), end)
2667 ui.write(((pats and m.rel(abs)) or abs), end)
2668 ret = 0
2668 ret = 0
2669
2669
2670 return ret
2670 return ret
2671
2671
2672 def log(ui, repo, *pats, **opts):
2672 def log(ui, repo, *pats, **opts):
2673 """show revision history of entire repository or files
2673 """show revision history of entire repository or files
2674
2674
2675 Print the revision history of the specified files or the entire
2675 Print the revision history of the specified files or the entire
2676 project.
2676 project.
2677
2677
2678 File history is shown without following rename or copy history of
2678 File history is shown without following rename or copy history of
2679 files. Use -f/--follow with a filename to follow history across
2679 files. Use -f/--follow with a filename to follow history across
2680 renames and copies. --follow without a filename will only show
2680 renames and copies. --follow without a filename will only show
2681 ancestors or descendants of the starting revision. --follow-first
2681 ancestors or descendants of the starting revision. --follow-first
2682 only follows the first parent of merge revisions.
2682 only follows the first parent of merge revisions.
2683
2683
2684 If no revision range is specified, the default is ``tip:0`` unless
2684 If no revision range is specified, the default is ``tip:0`` unless
2685 --follow is set, in which case the working directory parent is
2685 --follow is set, in which case the working directory parent is
2686 used as the starting revision. You can specify a revision set for
2686 used as the starting revision. You can specify a revision set for
2687 log, see :hg:`help revsets` for more information.
2687 log, see :hg:`help revsets` for more information.
2688
2688
2689 See :hg:`help dates` for a list of formats valid for -d/--date.
2689 See :hg:`help dates` for a list of formats valid for -d/--date.
2690
2690
2691 By default this command prints revision number and changeset id,
2691 By default this command prints revision number and changeset id,
2692 tags, non-trivial parents, user, date and time, and a summary for
2692 tags, non-trivial parents, user, date and time, and a summary for
2693 each commit. When the -v/--verbose switch is used, the list of
2693 each commit. When the -v/--verbose switch is used, the list of
2694 changed files and full commit message are shown.
2694 changed files and full commit message are shown.
2695
2695
2696 .. note::
2696 .. note::
2697 log -p/--patch may generate unexpected diff output for merge
2697 log -p/--patch may generate unexpected diff output for merge
2698 changesets, as it will only compare the merge changeset against
2698 changesets, as it will only compare the merge changeset against
2699 its first parent. Also, only files different from BOTH parents
2699 its first parent. Also, only files different from BOTH parents
2700 will appear in files:.
2700 will appear in files:.
2701
2701
2702 Returns 0 on success.
2702 Returns 0 on success.
2703 """
2703 """
2704
2704
2705 matchfn = cmdutil.match(repo, pats, opts)
2705 matchfn = cmdutil.match(repo, pats, opts)
2706 limit = cmdutil.loglimit(opts)
2706 limit = cmdutil.loglimit(opts)
2707 count = 0
2707 count = 0
2708
2708
2709 endrev = None
2709 endrev = None
2710 if opts.get('copies') and opts.get('rev'):
2710 if opts.get('copies') and opts.get('rev'):
2711 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2711 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2712
2712
2713 df = False
2713 df = False
2714 if opts["date"]:
2714 if opts["date"]:
2715 df = util.matchdate(opts["date"])
2715 df = util.matchdate(opts["date"])
2716
2716
2717 branches = opts.get('branch', []) + opts.get('only_branch', [])
2717 branches = opts.get('branch', []) + opts.get('only_branch', [])
2718 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2718 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2719
2719
2720 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2720 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2721 def prep(ctx, fns):
2721 def prep(ctx, fns):
2722 rev = ctx.rev()
2722 rev = ctx.rev()
2723 parents = [p for p in repo.changelog.parentrevs(rev)
2723 parents = [p for p in repo.changelog.parentrevs(rev)
2724 if p != nullrev]
2724 if p != nullrev]
2725 if opts.get('no_merges') and len(parents) == 2:
2725 if opts.get('no_merges') and len(parents) == 2:
2726 return
2726 return
2727 if opts.get('only_merges') and len(parents) != 2:
2727 if opts.get('only_merges') and len(parents) != 2:
2728 return
2728 return
2729 if opts.get('branch') and ctx.branch() not in opts['branch']:
2729 if opts.get('branch') and ctx.branch() not in opts['branch']:
2730 return
2730 return
2731 if df and not df(ctx.date()[0]):
2731 if df and not df(ctx.date()[0]):
2732 return
2732 return
2733 if opts['user'] and not [k for k in opts['user']
2733 if opts['user'] and not [k for k in opts['user']
2734 if k.lower() in ctx.user().lower()]:
2734 if k.lower() in ctx.user().lower()]:
2735 return
2735 return
2736 if opts.get('keyword'):
2736 if opts.get('keyword'):
2737 for k in [kw.lower() for kw in opts['keyword']]:
2737 for k in [kw.lower() for kw in opts['keyword']]:
2738 if (k in ctx.user().lower() or
2738 if (k in ctx.user().lower() or
2739 k in ctx.description().lower() or
2739 k in ctx.description().lower() or
2740 k in " ".join(ctx.files()).lower()):
2740 k in " ".join(ctx.files()).lower()):
2741 break
2741 break
2742 else:
2742 else:
2743 return
2743 return
2744
2744
2745 copies = None
2745 copies = None
2746 if opts.get('copies') and rev:
2746 if opts.get('copies') and rev:
2747 copies = []
2747 copies = []
2748 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2748 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2749 for fn in ctx.files():
2749 for fn in ctx.files():
2750 rename = getrenamed(fn, rev)
2750 rename = getrenamed(fn, rev)
2751 if rename:
2751 if rename:
2752 copies.append((fn, rename[0]))
2752 copies.append((fn, rename[0]))
2753
2753
2754 revmatchfn = None
2754 revmatchfn = None
2755 if opts.get('patch') or opts.get('stat'):
2755 if opts.get('patch') or opts.get('stat'):
2756 if opts.get('follow') or opts.get('follow_first'):
2756 if opts.get('follow') or opts.get('follow_first'):
2757 # note: this might be wrong when following through merges
2757 # note: this might be wrong when following through merges
2758 revmatchfn = cmdutil.match(repo, fns, default='path')
2758 revmatchfn = cmdutil.match(repo, fns, default='path')
2759 else:
2759 else:
2760 revmatchfn = matchfn
2760 revmatchfn = matchfn
2761
2761
2762 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2762 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2763
2763
2764 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2764 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2765 if count == limit:
2765 if count == limit:
2766 break
2766 break
2767 if displayer.flush(ctx.rev()):
2767 if displayer.flush(ctx.rev()):
2768 count += 1
2768 count += 1
2769 displayer.close()
2769 displayer.close()
2770
2770
2771 def manifest(ui, repo, node=None, rev=None):
2771 def manifest(ui, repo, node=None, rev=None):
2772 """output the current or given revision of the project manifest
2772 """output the current or given revision of the project manifest
2773
2773
2774 Print a list of version controlled files for the given revision.
2774 Print a list of version controlled files for the given revision.
2775 If no revision is given, the first parent of the working directory
2775 If no revision is given, the first parent of the working directory
2776 is used, or the null revision if no revision is checked out.
2776 is used, or the null revision if no revision is checked out.
2777
2777
2778 With -v, print file permissions, symlink and executable bits.
2778 With -v, print file permissions, symlink and executable bits.
2779 With --debug, print file revision hashes.
2779 With --debug, print file revision hashes.
2780
2780
2781 Returns 0 on success.
2781 Returns 0 on success.
2782 """
2782 """
2783
2783
2784 if rev and node:
2784 if rev and node:
2785 raise util.Abort(_("please specify just one revision"))
2785 raise util.Abort(_("please specify just one revision"))
2786
2786
2787 if not node:
2787 if not node:
2788 node = rev
2788 node = rev
2789
2789
2790 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2790 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2791 ctx = cmdutil.revsingle(repo, node)
2791 ctx = cmdutil.revsingle(repo, node)
2792 for f in ctx:
2792 for f in ctx:
2793 if ui.debugflag:
2793 if ui.debugflag:
2794 ui.write("%40s " % hex(ctx.manifest()[f]))
2794 ui.write("%40s " % hex(ctx.manifest()[f]))
2795 if ui.verbose:
2795 if ui.verbose:
2796 ui.write(decor[ctx.flags(f)])
2796 ui.write(decor[ctx.flags(f)])
2797 ui.write("%s\n" % f)
2797 ui.write("%s\n" % f)
2798
2798
2799 def merge(ui, repo, node=None, **opts):
2799 def merge(ui, repo, node=None, **opts):
2800 """merge working directory with another revision
2800 """merge working directory with another revision
2801
2801
2802 The current working directory is updated with all changes made in
2802 The current working directory is updated with all changes made in
2803 the requested revision since the last common predecessor revision.
2803 the requested revision since the last common predecessor revision.
2804
2804
2805 Files that changed between either parent are marked as changed for
2805 Files that changed between either parent are marked as changed for
2806 the next commit and a commit must be performed before any further
2806 the next commit and a commit must be performed before any further
2807 updates to the repository are allowed. The next commit will have
2807 updates to the repository are allowed. The next commit will have
2808 two parents.
2808 two parents.
2809
2809
2810 ``--tool`` can be used to specify the merge tool used for file
2810 ``--tool`` can be used to specify the merge tool used for file
2811 merges. It overrides the HGMERGE environment variable and your
2811 merges. It overrides the HGMERGE environment variable and your
2812 configuration files.
2812 configuration files.
2813
2813
2814 If no revision is specified, the working directory's parent is a
2814 If no revision is specified, the working directory's parent is a
2815 head revision, and the current branch contains exactly one other
2815 head revision, and the current branch contains exactly one other
2816 head, the other head is merged with by default. Otherwise, an
2816 head, the other head is merged with by default. Otherwise, an
2817 explicit revision with which to merge with must be provided.
2817 explicit revision with which to merge with must be provided.
2818
2818
2819 :hg:`resolve` must be used to resolve unresolved files.
2819 :hg:`resolve` must be used to resolve unresolved files.
2820
2820
2821 To undo an uncommitted merge, use :hg:`update --clean .` which
2821 To undo an uncommitted merge, use :hg:`update --clean .` which
2822 will check out a clean copy of the original merge parent, losing
2822 will check out a clean copy of the original merge parent, losing
2823 all changes.
2823 all changes.
2824
2824
2825 Returns 0 on success, 1 if there are unresolved files.
2825 Returns 0 on success, 1 if there are unresolved files.
2826 """
2826 """
2827
2827
2828 if opts.get('rev') and node:
2828 if opts.get('rev') and node:
2829 raise util.Abort(_("please specify just one revision"))
2829 raise util.Abort(_("please specify just one revision"))
2830 if not node:
2830 if not node:
2831 node = opts.get('rev')
2831 node = opts.get('rev')
2832
2832
2833 if not node:
2833 if not node:
2834 branch = repo[None].branch()
2834 branch = repo[None].branch()
2835 bheads = repo.branchheads(branch)
2835 bheads = repo.branchheads(branch)
2836 if len(bheads) > 2:
2836 if len(bheads) > 2:
2837 raise util.Abort(_(
2837 raise util.Abort(_(
2838 'branch \'%s\' has %d heads - '
2838 'branch \'%s\' has %d heads - '
2839 'please merge with an explicit rev\n'
2839 'please merge with an explicit rev\n'
2840 '(run \'hg heads .\' to see heads)')
2840 '(run \'hg heads .\' to see heads)')
2841 % (branch, len(bheads)))
2841 % (branch, len(bheads)))
2842
2842
2843 parent = repo.dirstate.parents()[0]
2843 parent = repo.dirstate.p1()
2844 if len(bheads) == 1:
2844 if len(bheads) == 1:
2845 if len(repo.heads()) > 1:
2845 if len(repo.heads()) > 1:
2846 raise util.Abort(_(
2846 raise util.Abort(_(
2847 'branch \'%s\' has one head - '
2847 'branch \'%s\' has one head - '
2848 'please merge with an explicit rev\n'
2848 'please merge with an explicit rev\n'
2849 '(run \'hg heads\' to see all heads)')
2849 '(run \'hg heads\' to see all heads)')
2850 % branch)
2850 % branch)
2851 msg = _('there is nothing to merge')
2851 msg = _('there is nothing to merge')
2852 if parent != repo.lookup(repo[None].branch()):
2852 if parent != repo.lookup(repo[None].branch()):
2853 msg = _('%s - use "hg update" instead') % msg
2853 msg = _('%s - use "hg update" instead') % msg
2854 raise util.Abort(msg)
2854 raise util.Abort(msg)
2855
2855
2856 if parent not in bheads:
2856 if parent not in bheads:
2857 raise util.Abort(_('working dir not at a head rev - '
2857 raise util.Abort(_('working dir not at a head rev - '
2858 'use "hg update" or merge with an explicit rev'))
2858 'use "hg update" or merge with an explicit rev'))
2859 node = parent == bheads[0] and bheads[-1] or bheads[0]
2859 node = parent == bheads[0] and bheads[-1] or bheads[0]
2860 else:
2860 else:
2861 node = cmdutil.revsingle(repo, node).node()
2861 node = cmdutil.revsingle(repo, node).node()
2862
2862
2863 if opts.get('preview'):
2863 if opts.get('preview'):
2864 # find nodes that are ancestors of p2 but not of p1
2864 # find nodes that are ancestors of p2 but not of p1
2865 p1 = repo.lookup('.')
2865 p1 = repo.lookup('.')
2866 p2 = repo.lookup(node)
2866 p2 = repo.lookup(node)
2867 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2867 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2868
2868
2869 displayer = cmdutil.show_changeset(ui, repo, opts)
2869 displayer = cmdutil.show_changeset(ui, repo, opts)
2870 for node in nodes:
2870 for node in nodes:
2871 displayer.show(repo[node])
2871 displayer.show(repo[node])
2872 displayer.close()
2872 displayer.close()
2873 return 0
2873 return 0
2874
2874
2875 try:
2875 try:
2876 # ui.forcemerge is an internal variable, do not document
2876 # ui.forcemerge is an internal variable, do not document
2877 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2877 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2878 return hg.merge(repo, node, force=opts.get('force'))
2878 return hg.merge(repo, node, force=opts.get('force'))
2879 finally:
2879 finally:
2880 ui.setconfig('ui', 'forcemerge', '')
2880 ui.setconfig('ui', 'forcemerge', '')
2881
2881
2882 def outgoing(ui, repo, dest=None, **opts):
2882 def outgoing(ui, repo, dest=None, **opts):
2883 """show changesets not found in the destination
2883 """show changesets not found in the destination
2884
2884
2885 Show changesets not found in the specified destination repository
2885 Show changesets not found in the specified destination repository
2886 or the default push location. These are the changesets that would
2886 or the default push location. These are the changesets that would
2887 be pushed if a push was requested.
2887 be pushed if a push was requested.
2888
2888
2889 See pull for details of valid destination formats.
2889 See pull for details of valid destination formats.
2890
2890
2891 Returns 0 if there are outgoing changes, 1 otherwise.
2891 Returns 0 if there are outgoing changes, 1 otherwise.
2892 """
2892 """
2893
2893
2894 if opts.get('bookmarks'):
2894 if opts.get('bookmarks'):
2895 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2895 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2896 dest, branches = hg.parseurl(dest, opts.get('branch'))
2896 dest, branches = hg.parseurl(dest, opts.get('branch'))
2897 other = hg.repository(hg.remoteui(repo, opts), dest)
2897 other = hg.repository(hg.remoteui(repo, opts), dest)
2898 if 'bookmarks' not in other.listkeys('namespaces'):
2898 if 'bookmarks' not in other.listkeys('namespaces'):
2899 ui.warn(_("remote doesn't support bookmarks\n"))
2899 ui.warn(_("remote doesn't support bookmarks\n"))
2900 return 0
2900 return 0
2901 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2901 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2902 return bookmarks.diff(ui, other, repo)
2902 return bookmarks.diff(ui, other, repo)
2903
2903
2904 ret = hg.outgoing(ui, repo, dest, opts)
2904 ret = hg.outgoing(ui, repo, dest, opts)
2905 return ret
2905 return ret
2906
2906
2907 def parents(ui, repo, file_=None, **opts):
2907 def parents(ui, repo, file_=None, **opts):
2908 """show the parents of the working directory or revision
2908 """show the parents of the working directory or revision
2909
2909
2910 Print the working directory's parent revisions. If a revision is
2910 Print the working directory's parent revisions. If a revision is
2911 given via -r/--rev, the parent of that revision will be printed.
2911 given via -r/--rev, the parent of that revision will be printed.
2912 If a file argument is given, the revision in which the file was
2912 If a file argument is given, the revision in which the file was
2913 last changed (before the working directory revision or the
2913 last changed (before the working directory revision or the
2914 argument to --rev if given) is printed.
2914 argument to --rev if given) is printed.
2915
2915
2916 Returns 0 on success.
2916 Returns 0 on success.
2917 """
2917 """
2918
2918
2919 ctx = cmdutil.revsingle(repo, opts.get('rev'), None)
2919 ctx = cmdutil.revsingle(repo, opts.get('rev'), None)
2920
2920
2921 if file_:
2921 if file_:
2922 m = cmdutil.match(repo, (file_,), opts)
2922 m = cmdutil.match(repo, (file_,), opts)
2923 if m.anypats() or len(m.files()) != 1:
2923 if m.anypats() or len(m.files()) != 1:
2924 raise util.Abort(_('can only specify an explicit filename'))
2924 raise util.Abort(_('can only specify an explicit filename'))
2925 file_ = m.files()[0]
2925 file_ = m.files()[0]
2926 filenodes = []
2926 filenodes = []
2927 for cp in ctx.parents():
2927 for cp in ctx.parents():
2928 if not cp:
2928 if not cp:
2929 continue
2929 continue
2930 try:
2930 try:
2931 filenodes.append(cp.filenode(file_))
2931 filenodes.append(cp.filenode(file_))
2932 except error.LookupError:
2932 except error.LookupError:
2933 pass
2933 pass
2934 if not filenodes:
2934 if not filenodes:
2935 raise util.Abort(_("'%s' not found in manifest!") % file_)
2935 raise util.Abort(_("'%s' not found in manifest!") % file_)
2936 fl = repo.file(file_)
2936 fl = repo.file(file_)
2937 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2937 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2938 else:
2938 else:
2939 p = [cp.node() for cp in ctx.parents()]
2939 p = [cp.node() for cp in ctx.parents()]
2940
2940
2941 displayer = cmdutil.show_changeset(ui, repo, opts)
2941 displayer = cmdutil.show_changeset(ui, repo, opts)
2942 for n in p:
2942 for n in p:
2943 if n != nullid:
2943 if n != nullid:
2944 displayer.show(repo[n])
2944 displayer.show(repo[n])
2945 displayer.close()
2945 displayer.close()
2946
2946
2947 def paths(ui, repo, search=None):
2947 def paths(ui, repo, search=None):
2948 """show aliases for remote repositories
2948 """show aliases for remote repositories
2949
2949
2950 Show definition of symbolic path name NAME. If no name is given,
2950 Show definition of symbolic path name NAME. If no name is given,
2951 show definition of all available names.
2951 show definition of all available names.
2952
2952
2953 Path names are defined in the [paths] section of your
2953 Path names are defined in the [paths] section of your
2954 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
2954 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
2955 repository, ``.hg/hgrc`` is used, too.
2955 repository, ``.hg/hgrc`` is used, too.
2956
2956
2957 The path names ``default`` and ``default-push`` have a special
2957 The path names ``default`` and ``default-push`` have a special
2958 meaning. When performing a push or pull operation, they are used
2958 meaning. When performing a push or pull operation, they are used
2959 as fallbacks if no location is specified on the command-line.
2959 as fallbacks if no location is specified on the command-line.
2960 When ``default-push`` is set, it will be used for push and
2960 When ``default-push`` is set, it will be used for push and
2961 ``default`` will be used for pull; otherwise ``default`` is used
2961 ``default`` will be used for pull; otherwise ``default`` is used
2962 as the fallback for both. When cloning a repository, the clone
2962 as the fallback for both. When cloning a repository, the clone
2963 source is written as ``default`` in ``.hg/hgrc``. Note that
2963 source is written as ``default`` in ``.hg/hgrc``. Note that
2964 ``default`` and ``default-push`` apply to all inbound (e.g.
2964 ``default`` and ``default-push`` apply to all inbound (e.g.
2965 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2965 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2966 :hg:`bundle`) operations.
2966 :hg:`bundle`) operations.
2967
2967
2968 See :hg:`help urls` for more information.
2968 See :hg:`help urls` for more information.
2969
2969
2970 Returns 0 on success.
2970 Returns 0 on success.
2971 """
2971 """
2972 if search:
2972 if search:
2973 for name, path in ui.configitems("paths"):
2973 for name, path in ui.configitems("paths"):
2974 if name == search:
2974 if name == search:
2975 ui.write("%s\n" % url.hidepassword(path))
2975 ui.write("%s\n" % url.hidepassword(path))
2976 return
2976 return
2977 ui.warn(_("not found!\n"))
2977 ui.warn(_("not found!\n"))
2978 return 1
2978 return 1
2979 else:
2979 else:
2980 for name, path in ui.configitems("paths"):
2980 for name, path in ui.configitems("paths"):
2981 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2981 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2982
2982
2983 def postincoming(ui, repo, modheads, optupdate, checkout):
2983 def postincoming(ui, repo, modheads, optupdate, checkout):
2984 if modheads == 0:
2984 if modheads == 0:
2985 return
2985 return
2986 if optupdate:
2986 if optupdate:
2987 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2987 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2988 return hg.update(repo, checkout)
2988 return hg.update(repo, checkout)
2989 else:
2989 else:
2990 ui.status(_("not updating, since new heads added\n"))
2990 ui.status(_("not updating, since new heads added\n"))
2991 if modheads > 1:
2991 if modheads > 1:
2992 currentbranchheads = len(repo.branchheads())
2992 currentbranchheads = len(repo.branchheads())
2993 if currentbranchheads == modheads:
2993 if currentbranchheads == modheads:
2994 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2994 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2995 elif currentbranchheads > 1:
2995 elif currentbranchheads > 1:
2996 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
2996 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
2997 else:
2997 else:
2998 ui.status(_("(run 'hg heads' to see heads)\n"))
2998 ui.status(_("(run 'hg heads' to see heads)\n"))
2999 else:
2999 else:
3000 ui.status(_("(run 'hg update' to get a working copy)\n"))
3000 ui.status(_("(run 'hg update' to get a working copy)\n"))
3001
3001
3002 def pull(ui, repo, source="default", **opts):
3002 def pull(ui, repo, source="default", **opts):
3003 """pull changes from the specified source
3003 """pull changes from the specified source
3004
3004
3005 Pull changes from a remote repository to a local one.
3005 Pull changes from a remote repository to a local one.
3006
3006
3007 This finds all changes from the repository at the specified path
3007 This finds all changes from the repository at the specified path
3008 or URL and adds them to a local repository (the current one unless
3008 or URL and adds them to a local repository (the current one unless
3009 -R is specified). By default, this does not update the copy of the
3009 -R is specified). By default, this does not update the copy of the
3010 project in the working directory.
3010 project in the working directory.
3011
3011
3012 Use :hg:`incoming` if you want to see what would have been added
3012 Use :hg:`incoming` if you want to see what would have been added
3013 by a pull at the time you issued this command. If you then decide
3013 by a pull at the time you issued this command. If you then decide
3014 to add those changes to the repository, you should use :hg:`pull
3014 to add those changes to the repository, you should use :hg:`pull
3015 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3015 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3016
3016
3017 If SOURCE is omitted, the 'default' path will be used.
3017 If SOURCE is omitted, the 'default' path will be used.
3018 See :hg:`help urls` for more information.
3018 See :hg:`help urls` for more information.
3019
3019
3020 Returns 0 on success, 1 if an update had unresolved files.
3020 Returns 0 on success, 1 if an update had unresolved files.
3021 """
3021 """
3022 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3022 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3023 other = hg.repository(hg.remoteui(repo, opts), source)
3023 other = hg.repository(hg.remoteui(repo, opts), source)
3024 ui.status(_('pulling from %s\n') % url.hidepassword(source))
3024 ui.status(_('pulling from %s\n') % url.hidepassword(source))
3025 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3025 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3026
3026
3027 if opts.get('bookmark'):
3027 if opts.get('bookmark'):
3028 if not revs:
3028 if not revs:
3029 revs = []
3029 revs = []
3030 rb = other.listkeys('bookmarks')
3030 rb = other.listkeys('bookmarks')
3031 for b in opts['bookmark']:
3031 for b in opts['bookmark']:
3032 if b not in rb:
3032 if b not in rb:
3033 raise util.Abort(_('remote bookmark %s not found!') % b)
3033 raise util.Abort(_('remote bookmark %s not found!') % b)
3034 revs.append(rb[b])
3034 revs.append(rb[b])
3035
3035
3036 if revs:
3036 if revs:
3037 try:
3037 try:
3038 revs = [other.lookup(rev) for rev in revs]
3038 revs = [other.lookup(rev) for rev in revs]
3039 except error.CapabilityError:
3039 except error.CapabilityError:
3040 err = _("other repository doesn't support revision lookup, "
3040 err = _("other repository doesn't support revision lookup, "
3041 "so a rev cannot be specified.")
3041 "so a rev cannot be specified.")
3042 raise util.Abort(err)
3042 raise util.Abort(err)
3043
3043
3044 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
3044 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
3045 bookmarks.updatefromremote(ui, repo, other)
3045 bookmarks.updatefromremote(ui, repo, other)
3046 if checkout:
3046 if checkout:
3047 checkout = str(repo.changelog.rev(other.lookup(checkout)))
3047 checkout = str(repo.changelog.rev(other.lookup(checkout)))
3048 repo._subtoppath = source
3048 repo._subtoppath = source
3049 try:
3049 try:
3050 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
3050 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
3051
3051
3052 finally:
3052 finally:
3053 del repo._subtoppath
3053 del repo._subtoppath
3054
3054
3055 # update specified bookmarks
3055 # update specified bookmarks
3056 if opts.get('bookmark'):
3056 if opts.get('bookmark'):
3057 for b in opts['bookmark']:
3057 for b in opts['bookmark']:
3058 # explicit pull overrides local bookmark if any
3058 # explicit pull overrides local bookmark if any
3059 ui.status(_("importing bookmark %s\n") % b)
3059 ui.status(_("importing bookmark %s\n") % b)
3060 repo._bookmarks[b] = repo[rb[b]].node()
3060 repo._bookmarks[b] = repo[rb[b]].node()
3061 bookmarks.write(repo)
3061 bookmarks.write(repo)
3062
3062
3063 return ret
3063 return ret
3064
3064
3065 def push(ui, repo, dest=None, **opts):
3065 def push(ui, repo, dest=None, **opts):
3066 """push changes to the specified destination
3066 """push changes to the specified destination
3067
3067
3068 Push changesets from the local repository to the specified
3068 Push changesets from the local repository to the specified
3069 destination.
3069 destination.
3070
3070
3071 This operation is symmetrical to pull: it is identical to a pull
3071 This operation is symmetrical to pull: it is identical to a pull
3072 in the destination repository from the current one.
3072 in the destination repository from the current one.
3073
3073
3074 By default, push will not allow creation of new heads at the
3074 By default, push will not allow creation of new heads at the
3075 destination, since multiple heads would make it unclear which head
3075 destination, since multiple heads would make it unclear which head
3076 to use. In this situation, it is recommended to pull and merge
3076 to use. In this situation, it is recommended to pull and merge
3077 before pushing.
3077 before pushing.
3078
3078
3079 Use --new-branch if you want to allow push to create a new named
3079 Use --new-branch if you want to allow push to create a new named
3080 branch that is not present at the destination. This allows you to
3080 branch that is not present at the destination. This allows you to
3081 only create a new branch without forcing other changes.
3081 only create a new branch without forcing other changes.
3082
3082
3083 Use -f/--force to override the default behavior and push all
3083 Use -f/--force to override the default behavior and push all
3084 changesets on all branches.
3084 changesets on all branches.
3085
3085
3086 If -r/--rev is used, the specified revision and all its ancestors
3086 If -r/--rev is used, the specified revision and all its ancestors
3087 will be pushed to the remote repository.
3087 will be pushed to the remote repository.
3088
3088
3089 Please see :hg:`help urls` for important details about ``ssh://``
3089 Please see :hg:`help urls` for important details about ``ssh://``
3090 URLs. If DESTINATION is omitted, a default path will be used.
3090 URLs. If DESTINATION is omitted, a default path will be used.
3091
3091
3092 Returns 0 if push was successful, 1 if nothing to push.
3092 Returns 0 if push was successful, 1 if nothing to push.
3093 """
3093 """
3094
3094
3095 if opts.get('bookmark'):
3095 if opts.get('bookmark'):
3096 for b in opts['bookmark']:
3096 for b in opts['bookmark']:
3097 # translate -B options to -r so changesets get pushed
3097 # translate -B options to -r so changesets get pushed
3098 if b in repo._bookmarks:
3098 if b in repo._bookmarks:
3099 opts.setdefault('rev', []).append(b)
3099 opts.setdefault('rev', []).append(b)
3100 else:
3100 else:
3101 # if we try to push a deleted bookmark, translate it to null
3101 # if we try to push a deleted bookmark, translate it to null
3102 # this lets simultaneous -r, -b options continue working
3102 # this lets simultaneous -r, -b options continue working
3103 opts.setdefault('rev', []).append("null")
3103 opts.setdefault('rev', []).append("null")
3104
3104
3105 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3105 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3106 dest, branches = hg.parseurl(dest, opts.get('branch'))
3106 dest, branches = hg.parseurl(dest, opts.get('branch'))
3107 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
3107 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
3108 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
3108 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
3109 other = hg.repository(hg.remoteui(repo, opts), dest)
3109 other = hg.repository(hg.remoteui(repo, opts), dest)
3110 if revs:
3110 if revs:
3111 revs = [repo.lookup(rev) for rev in revs]
3111 revs = [repo.lookup(rev) for rev in revs]
3112
3112
3113 repo._subtoppath = dest
3113 repo._subtoppath = dest
3114 try:
3114 try:
3115 # push subrepos depth-first for coherent ordering
3115 # push subrepos depth-first for coherent ordering
3116 c = repo['']
3116 c = repo['']
3117 subs = c.substate # only repos that are committed
3117 subs = c.substate # only repos that are committed
3118 for s in sorted(subs):
3118 for s in sorted(subs):
3119 if not c.sub(s).push(opts.get('force')):
3119 if not c.sub(s).push(opts.get('force')):
3120 return False
3120 return False
3121 finally:
3121 finally:
3122 del repo._subtoppath
3122 del repo._subtoppath
3123 result = repo.push(other, opts.get('force'), revs=revs,
3123 result = repo.push(other, opts.get('force'), revs=revs,
3124 newbranch=opts.get('new_branch'))
3124 newbranch=opts.get('new_branch'))
3125
3125
3126 result = (result == 0)
3126 result = (result == 0)
3127
3127
3128 if opts.get('bookmark'):
3128 if opts.get('bookmark'):
3129 rb = other.listkeys('bookmarks')
3129 rb = other.listkeys('bookmarks')
3130 for b in opts['bookmark']:
3130 for b in opts['bookmark']:
3131 # explicit push overrides remote bookmark if any
3131 # explicit push overrides remote bookmark if any
3132 if b in repo._bookmarks:
3132 if b in repo._bookmarks:
3133 ui.status(_("exporting bookmark %s\n") % b)
3133 ui.status(_("exporting bookmark %s\n") % b)
3134 new = repo[b].hex()
3134 new = repo[b].hex()
3135 elif b in rb:
3135 elif b in rb:
3136 ui.status(_("deleting remote bookmark %s\n") % b)
3136 ui.status(_("deleting remote bookmark %s\n") % b)
3137 new = '' # delete
3137 new = '' # delete
3138 else:
3138 else:
3139 ui.warn(_('bookmark %s does not exist on the local '
3139 ui.warn(_('bookmark %s does not exist on the local '
3140 'or remote repository!\n') % b)
3140 'or remote repository!\n') % b)
3141 return 2
3141 return 2
3142 old = rb.get(b, '')
3142 old = rb.get(b, '')
3143 r = other.pushkey('bookmarks', b, old, new)
3143 r = other.pushkey('bookmarks', b, old, new)
3144 if not r:
3144 if not r:
3145 ui.warn(_('updating bookmark %s failed!\n') % b)
3145 ui.warn(_('updating bookmark %s failed!\n') % b)
3146 if not result:
3146 if not result:
3147 result = 2
3147 result = 2
3148
3148
3149 return result
3149 return result
3150
3150
3151 def recover(ui, repo):
3151 def recover(ui, repo):
3152 """roll back an interrupted transaction
3152 """roll back an interrupted transaction
3153
3153
3154 Recover from an interrupted commit or pull.
3154 Recover from an interrupted commit or pull.
3155
3155
3156 This command tries to fix the repository status after an
3156 This command tries to fix the repository status after an
3157 interrupted operation. It should only be necessary when Mercurial
3157 interrupted operation. It should only be necessary when Mercurial
3158 suggests it.
3158 suggests it.
3159
3159
3160 Returns 0 if successful, 1 if nothing to recover or verify fails.
3160 Returns 0 if successful, 1 if nothing to recover or verify fails.
3161 """
3161 """
3162 if repo.recover():
3162 if repo.recover():
3163 return hg.verify(repo)
3163 return hg.verify(repo)
3164 return 1
3164 return 1
3165
3165
3166 def remove(ui, repo, *pats, **opts):
3166 def remove(ui, repo, *pats, **opts):
3167 """remove the specified files on the next commit
3167 """remove the specified files on the next commit
3168
3168
3169 Schedule the indicated files for removal from the repository.
3169 Schedule the indicated files for removal from the repository.
3170
3170
3171 This only removes files from the current branch, not from the
3171 This only removes files from the current branch, not from the
3172 entire project history. -A/--after can be used to remove only
3172 entire project history. -A/--after can be used to remove only
3173 files that have already been deleted, -f/--force can be used to
3173 files that have already been deleted, -f/--force can be used to
3174 force deletion, and -Af can be used to remove files from the next
3174 force deletion, and -Af can be used to remove files from the next
3175 revision without deleting them from the working directory.
3175 revision without deleting them from the working directory.
3176
3176
3177 The following table details the behavior of remove for different
3177 The following table details the behavior of remove for different
3178 file states (columns) and option combinations (rows). The file
3178 file states (columns) and option combinations (rows). The file
3179 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3179 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3180 reported by :hg:`status`). The actions are Warn, Remove (from
3180 reported by :hg:`status`). The actions are Warn, Remove (from
3181 branch) and Delete (from disk)::
3181 branch) and Delete (from disk)::
3182
3182
3183 A C M !
3183 A C M !
3184 none W RD W R
3184 none W RD W R
3185 -f R RD RD R
3185 -f R RD RD R
3186 -A W W W R
3186 -A W W W R
3187 -Af R R R R
3187 -Af R R R R
3188
3188
3189 This command schedules the files to be removed at the next commit.
3189 This command schedules the files to be removed at the next commit.
3190 To undo a remove before that, see :hg:`revert`.
3190 To undo a remove before that, see :hg:`revert`.
3191
3191
3192 Returns 0 on success, 1 if any warnings encountered.
3192 Returns 0 on success, 1 if any warnings encountered.
3193 """
3193 """
3194
3194
3195 ret = 0
3195 ret = 0
3196 after, force = opts.get('after'), opts.get('force')
3196 after, force = opts.get('after'), opts.get('force')
3197 if not pats and not after:
3197 if not pats and not after:
3198 raise util.Abort(_('no files specified'))
3198 raise util.Abort(_('no files specified'))
3199
3199
3200 m = cmdutil.match(repo, pats, opts)
3200 m = cmdutil.match(repo, pats, opts)
3201 s = repo.status(match=m, clean=True)
3201 s = repo.status(match=m, clean=True)
3202 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3202 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3203
3203
3204 for f in m.files():
3204 for f in m.files():
3205 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3205 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3206 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3206 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3207 ret = 1
3207 ret = 1
3208
3208
3209 if force:
3209 if force:
3210 remove, forget = modified + deleted + clean, added
3210 remove, forget = modified + deleted + clean, added
3211 elif after:
3211 elif after:
3212 remove, forget = deleted, []
3212 remove, forget = deleted, []
3213 for f in modified + added + clean:
3213 for f in modified + added + clean:
3214 ui.warn(_('not removing %s: file still exists (use -f'
3214 ui.warn(_('not removing %s: file still exists (use -f'
3215 ' to force removal)\n') % m.rel(f))
3215 ' to force removal)\n') % m.rel(f))
3216 ret = 1
3216 ret = 1
3217 else:
3217 else:
3218 remove, forget = deleted + clean, []
3218 remove, forget = deleted + clean, []
3219 for f in modified:
3219 for f in modified:
3220 ui.warn(_('not removing %s: file is modified (use -f'
3220 ui.warn(_('not removing %s: file is modified (use -f'
3221 ' to force removal)\n') % m.rel(f))
3221 ' to force removal)\n') % m.rel(f))
3222 ret = 1
3222 ret = 1
3223 for f in added:
3223 for f in added:
3224 ui.warn(_('not removing %s: file has been marked for add (use -f'
3224 ui.warn(_('not removing %s: file has been marked for add (use -f'
3225 ' to force removal)\n') % m.rel(f))
3225 ' to force removal)\n') % m.rel(f))
3226 ret = 1
3226 ret = 1
3227
3227
3228 for f in sorted(remove + forget):
3228 for f in sorted(remove + forget):
3229 if ui.verbose or not m.exact(f):
3229 if ui.verbose or not m.exact(f):
3230 ui.status(_('removing %s\n') % m.rel(f))
3230 ui.status(_('removing %s\n') % m.rel(f))
3231
3231
3232 repo[None].forget(forget)
3232 repo[None].forget(forget)
3233 repo[None].remove(remove, unlink=not after)
3233 repo[None].remove(remove, unlink=not after)
3234 return ret
3234 return ret
3235
3235
3236 def rename(ui, repo, *pats, **opts):
3236 def rename(ui, repo, *pats, **opts):
3237 """rename files; equivalent of copy + remove
3237 """rename files; equivalent of copy + remove
3238
3238
3239 Mark dest as copies of sources; mark sources for deletion. If dest
3239 Mark dest as copies of sources; mark sources for deletion. If dest
3240 is a directory, copies are put in that directory. If dest is a
3240 is a directory, copies are put in that directory. If dest is a
3241 file, there can only be one source.
3241 file, there can only be one source.
3242
3242
3243 By default, this command copies the contents of files as they
3243 By default, this command copies the contents of files as they
3244 exist in the working directory. If invoked with -A/--after, the
3244 exist in the working directory. If invoked with -A/--after, the
3245 operation is recorded, but no copying is performed.
3245 operation is recorded, but no copying is performed.
3246
3246
3247 This command takes effect at the next commit. To undo a rename
3247 This command takes effect at the next commit. To undo a rename
3248 before that, see :hg:`revert`.
3248 before that, see :hg:`revert`.
3249
3249
3250 Returns 0 on success, 1 if errors are encountered.
3250 Returns 0 on success, 1 if errors are encountered.
3251 """
3251 """
3252 wlock = repo.wlock(False)
3252 wlock = repo.wlock(False)
3253 try:
3253 try:
3254 return cmdutil.copy(ui, repo, pats, opts, rename=True)
3254 return cmdutil.copy(ui, repo, pats, opts, rename=True)
3255 finally:
3255 finally:
3256 wlock.release()
3256 wlock.release()
3257
3257
3258 def resolve(ui, repo, *pats, **opts):
3258 def resolve(ui, repo, *pats, **opts):
3259 """redo merges or set/view the merge status of files
3259 """redo merges or set/view the merge status of files
3260
3260
3261 Merges with unresolved conflicts are often the result of
3261 Merges with unresolved conflicts are often the result of
3262 non-interactive merging using the ``internal:merge`` configuration
3262 non-interactive merging using the ``internal:merge`` configuration
3263 setting, or a command-line merge tool like ``diff3``. The resolve
3263 setting, or a command-line merge tool like ``diff3``. The resolve
3264 command is used to manage the files involved in a merge, after
3264 command is used to manage the files involved in a merge, after
3265 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3265 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3266 working directory must have two parents).
3266 working directory must have two parents).
3267
3267
3268 The resolve command can be used in the following ways:
3268 The resolve command can be used in the following ways:
3269
3269
3270 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
3270 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
3271 files, discarding any previous merge attempts. Re-merging is not
3271 files, discarding any previous merge attempts. Re-merging is not
3272 performed for files already marked as resolved. Use ``--all/-a``
3272 performed for files already marked as resolved. Use ``--all/-a``
3273 to selects all unresolved files. ``--tool`` can be used to specify
3273 to selects all unresolved files. ``--tool`` can be used to specify
3274 the merge tool used for the given files. It overrides the HGMERGE
3274 the merge tool used for the given files. It overrides the HGMERGE
3275 environment variable and your configuration files.
3275 environment variable and your configuration files.
3276
3276
3277 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3277 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3278 (e.g. after having manually fixed-up the files). The default is
3278 (e.g. after having manually fixed-up the files). The default is
3279 to mark all unresolved files.
3279 to mark all unresolved files.
3280
3280
3281 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3281 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3282 default is to mark all resolved files.
3282 default is to mark all resolved files.
3283
3283
3284 - :hg:`resolve -l`: list files which had or still have conflicts.
3284 - :hg:`resolve -l`: list files which had or still have conflicts.
3285 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3285 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3286
3286
3287 Note that Mercurial will not let you commit files with unresolved
3287 Note that Mercurial will not let you commit files with unresolved
3288 merge conflicts. You must use :hg:`resolve -m ...` before you can
3288 merge conflicts. You must use :hg:`resolve -m ...` before you can
3289 commit after a conflicting merge.
3289 commit after a conflicting merge.
3290
3290
3291 Returns 0 on success, 1 if any files fail a resolve attempt.
3291 Returns 0 on success, 1 if any files fail a resolve attempt.
3292 """
3292 """
3293
3293
3294 all, mark, unmark, show, nostatus = \
3294 all, mark, unmark, show, nostatus = \
3295 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3295 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3296
3296
3297 if (show and (mark or unmark)) or (mark and unmark):
3297 if (show and (mark or unmark)) or (mark and unmark):
3298 raise util.Abort(_("too many options specified"))
3298 raise util.Abort(_("too many options specified"))
3299 if pats and all:
3299 if pats and all:
3300 raise util.Abort(_("can't specify --all and patterns"))
3300 raise util.Abort(_("can't specify --all and patterns"))
3301 if not (all or pats or show or mark or unmark):
3301 if not (all or pats or show or mark or unmark):
3302 raise util.Abort(_('no files or directories specified; '
3302 raise util.Abort(_('no files or directories specified; '
3303 'use --all to remerge all files'))
3303 'use --all to remerge all files'))
3304
3304
3305 ms = mergemod.mergestate(repo)
3305 ms = mergemod.mergestate(repo)
3306 m = cmdutil.match(repo, pats, opts)
3306 m = cmdutil.match(repo, pats, opts)
3307 ret = 0
3307 ret = 0
3308
3308
3309 for f in ms:
3309 for f in ms:
3310 if m(f):
3310 if m(f):
3311 if show:
3311 if show:
3312 if nostatus:
3312 if nostatus:
3313 ui.write("%s\n" % f)
3313 ui.write("%s\n" % f)
3314 else:
3314 else:
3315 ui.write("%s %s\n" % (ms[f].upper(), f),
3315 ui.write("%s %s\n" % (ms[f].upper(), f),
3316 label='resolve.' +
3316 label='resolve.' +
3317 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3317 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3318 elif mark:
3318 elif mark:
3319 ms.mark(f, "r")
3319 ms.mark(f, "r")
3320 elif unmark:
3320 elif unmark:
3321 ms.mark(f, "u")
3321 ms.mark(f, "u")
3322 else:
3322 else:
3323 wctx = repo[None]
3323 wctx = repo[None]
3324 mctx = wctx.parents()[-1]
3324 mctx = wctx.parents()[-1]
3325
3325
3326 # backup pre-resolve (merge uses .orig for its own purposes)
3326 # backup pre-resolve (merge uses .orig for its own purposes)
3327 a = repo.wjoin(f)
3327 a = repo.wjoin(f)
3328 util.copyfile(a, a + ".resolve")
3328 util.copyfile(a, a + ".resolve")
3329
3329
3330 try:
3330 try:
3331 # resolve file
3331 # resolve file
3332 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3332 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3333 if ms.resolve(f, wctx, mctx):
3333 if ms.resolve(f, wctx, mctx):
3334 ret = 1
3334 ret = 1
3335 finally:
3335 finally:
3336 ui.setconfig('ui', 'forcemerge', '')
3336 ui.setconfig('ui', 'forcemerge', '')
3337
3337
3338 # replace filemerge's .orig file with our resolve file
3338 # replace filemerge's .orig file with our resolve file
3339 util.rename(a + ".resolve", a + ".orig")
3339 util.rename(a + ".resolve", a + ".orig")
3340
3340
3341 ms.commit()
3341 ms.commit()
3342 return ret
3342 return ret
3343
3343
3344 def revert(ui, repo, *pats, **opts):
3344 def revert(ui, repo, *pats, **opts):
3345 """restore individual files or directories to an earlier state
3345 """restore individual files or directories to an earlier state
3346
3346
3347 .. note::
3347 .. note::
3348 This command is most likely not what you are looking for.
3348 This command is most likely not what you are looking for.
3349 Revert will partially overwrite content in the working
3349 Revert will partially overwrite content in the working
3350 directory without changing the working directory parents. Use
3350 directory without changing the working directory parents. Use
3351 :hg:`update -r rev` to check out earlier revisions, or
3351 :hg:`update -r rev` to check out earlier revisions, or
3352 :hg:`update --clean .` to undo a merge which has added another
3352 :hg:`update --clean .` to undo a merge which has added another
3353 parent.
3353 parent.
3354
3354
3355 With no revision specified, revert the named files or directories
3355 With no revision specified, revert the named files or directories
3356 to the contents they had in the parent of the working directory.
3356 to the contents they had in the parent of the working directory.
3357 This restores the contents of the affected files to an unmodified
3357 This restores the contents of the affected files to an unmodified
3358 state and unschedules adds, removes, copies, and renames. If the
3358 state and unschedules adds, removes, copies, and renames. If the
3359 working directory has two parents, you must explicitly specify a
3359 working directory has two parents, you must explicitly specify a
3360 revision.
3360 revision.
3361
3361
3362 Using the -r/--rev option, revert the given files or directories
3362 Using the -r/--rev option, revert the given files or directories
3363 to their contents as of a specific revision. This can be helpful
3363 to their contents as of a specific revision. This can be helpful
3364 to "roll back" some or all of an earlier change. See :hg:`help
3364 to "roll back" some or all of an earlier change. See :hg:`help
3365 dates` for a list of formats valid for -d/--date.
3365 dates` for a list of formats valid for -d/--date.
3366
3366
3367 Revert modifies the working directory. It does not commit any
3367 Revert modifies the working directory. It does not commit any
3368 changes, or change the parent of the working directory. If you
3368 changes, or change the parent of the working directory. If you
3369 revert to a revision other than the parent of the working
3369 revert to a revision other than the parent of the working
3370 directory, the reverted files will thus appear modified
3370 directory, the reverted files will thus appear modified
3371 afterwards.
3371 afterwards.
3372
3372
3373 If a file has been deleted, it is restored. Files scheduled for
3373 If a file has been deleted, it is restored. Files scheduled for
3374 addition are just unscheduled and left as they are. If the
3374 addition are just unscheduled and left as they are. If the
3375 executable mode of a file was changed, it is reset.
3375 executable mode of a file was changed, it is reset.
3376
3376
3377 If names are given, all files matching the names are reverted.
3377 If names are given, all files matching the names are reverted.
3378 If no arguments are given, no files are reverted.
3378 If no arguments are given, no files are reverted.
3379
3379
3380 Modified files are saved with a .orig suffix before reverting.
3380 Modified files are saved with a .orig suffix before reverting.
3381 To disable these backups, use --no-backup.
3381 To disable these backups, use --no-backup.
3382
3382
3383 Returns 0 on success.
3383 Returns 0 on success.
3384 """
3384 """
3385
3385
3386 if opts.get("date"):
3386 if opts.get("date"):
3387 if opts.get("rev"):
3387 if opts.get("rev"):
3388 raise util.Abort(_("you can't specify a revision and a date"))
3388 raise util.Abort(_("you can't specify a revision and a date"))
3389 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3389 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3390
3390
3391 parent, p2 = repo.dirstate.parents()
3391 parent, p2 = repo.dirstate.parents()
3392 if not opts.get('rev') and p2 != nullid:
3392 if not opts.get('rev') and p2 != nullid:
3393 raise util.Abort(_('uncommitted merge - '
3393 raise util.Abort(_('uncommitted merge - '
3394 'use "hg update", see "hg help revert"'))
3394 'use "hg update", see "hg help revert"'))
3395
3395
3396 if not pats and not opts.get('all'):
3396 if not pats and not opts.get('all'):
3397 raise util.Abort(_('no files or directories specified; '
3397 raise util.Abort(_('no files or directories specified; '
3398 'use --all to revert the whole repo'))
3398 'use --all to revert the whole repo'))
3399
3399
3400 ctx = cmdutil.revsingle(repo, opts.get('rev'))
3400 ctx = cmdutil.revsingle(repo, opts.get('rev'))
3401 node = ctx.node()
3401 node = ctx.node()
3402 mf = ctx.manifest()
3402 mf = ctx.manifest()
3403 if node == parent:
3403 if node == parent:
3404 pmf = mf
3404 pmf = mf
3405 else:
3405 else:
3406 pmf = None
3406 pmf = None
3407
3407
3408 # need all matching names in dirstate and manifest of target rev,
3408 # need all matching names in dirstate and manifest of target rev,
3409 # so have to walk both. do not print errors if files exist in one
3409 # so have to walk both. do not print errors if files exist in one
3410 # but not other.
3410 # but not other.
3411
3411
3412 names = {}
3412 names = {}
3413
3413
3414 wlock = repo.wlock()
3414 wlock = repo.wlock()
3415 try:
3415 try:
3416 # walk dirstate.
3416 # walk dirstate.
3417
3417
3418 m = cmdutil.match(repo, pats, opts)
3418 m = cmdutil.match(repo, pats, opts)
3419 m.bad = lambda x, y: False
3419 m.bad = lambda x, y: False
3420 for abs in repo.walk(m):
3420 for abs in repo.walk(m):
3421 names[abs] = m.rel(abs), m.exact(abs)
3421 names[abs] = m.rel(abs), m.exact(abs)
3422
3422
3423 # walk target manifest.
3423 # walk target manifest.
3424
3424
3425 def badfn(path, msg):
3425 def badfn(path, msg):
3426 if path in names:
3426 if path in names:
3427 return
3427 return
3428 path_ = path + '/'
3428 path_ = path + '/'
3429 for f in names:
3429 for f in names:
3430 if f.startswith(path_):
3430 if f.startswith(path_):
3431 return
3431 return
3432 ui.warn("%s: %s\n" % (m.rel(path), msg))
3432 ui.warn("%s: %s\n" % (m.rel(path), msg))
3433
3433
3434 m = cmdutil.match(repo, pats, opts)
3434 m = cmdutil.match(repo, pats, opts)
3435 m.bad = badfn
3435 m.bad = badfn
3436 for abs in repo[node].walk(m):
3436 for abs in repo[node].walk(m):
3437 if abs not in names:
3437 if abs not in names:
3438 names[abs] = m.rel(abs), m.exact(abs)
3438 names[abs] = m.rel(abs), m.exact(abs)
3439
3439
3440 m = cmdutil.matchfiles(repo, names)
3440 m = cmdutil.matchfiles(repo, names)
3441 changes = repo.status(match=m)[:4]
3441 changes = repo.status(match=m)[:4]
3442 modified, added, removed, deleted = map(set, changes)
3442 modified, added, removed, deleted = map(set, changes)
3443
3443
3444 # if f is a rename, also revert the source
3444 # if f is a rename, also revert the source
3445 cwd = repo.getcwd()
3445 cwd = repo.getcwd()
3446 for f in added:
3446 for f in added:
3447 src = repo.dirstate.copied(f)
3447 src = repo.dirstate.copied(f)
3448 if src and src not in names and repo.dirstate[src] == 'r':
3448 if src and src not in names and repo.dirstate[src] == 'r':
3449 removed.add(src)
3449 removed.add(src)
3450 names[src] = (repo.pathto(src, cwd), True)
3450 names[src] = (repo.pathto(src, cwd), True)
3451
3451
3452 def removeforget(abs):
3452 def removeforget(abs):
3453 if repo.dirstate[abs] == 'a':
3453 if repo.dirstate[abs] == 'a':
3454 return _('forgetting %s\n')
3454 return _('forgetting %s\n')
3455 return _('removing %s\n')
3455 return _('removing %s\n')
3456
3456
3457 revert = ([], _('reverting %s\n'))
3457 revert = ([], _('reverting %s\n'))
3458 add = ([], _('adding %s\n'))
3458 add = ([], _('adding %s\n'))
3459 remove = ([], removeforget)
3459 remove = ([], removeforget)
3460 undelete = ([], _('undeleting %s\n'))
3460 undelete = ([], _('undeleting %s\n'))
3461
3461
3462 disptable = (
3462 disptable = (
3463 # dispatch table:
3463 # dispatch table:
3464 # file state
3464 # file state
3465 # action if in target manifest
3465 # action if in target manifest
3466 # action if not in target manifest
3466 # action if not in target manifest
3467 # make backup if in target manifest
3467 # make backup if in target manifest
3468 # make backup if not in target manifest
3468 # make backup if not in target manifest
3469 (modified, revert, remove, True, True),
3469 (modified, revert, remove, True, True),
3470 (added, revert, remove, True, False),
3470 (added, revert, remove, True, False),
3471 (removed, undelete, None, False, False),
3471 (removed, undelete, None, False, False),
3472 (deleted, revert, remove, False, False),
3472 (deleted, revert, remove, False, False),
3473 )
3473 )
3474
3474
3475 for abs, (rel, exact) in sorted(names.items()):
3475 for abs, (rel, exact) in sorted(names.items()):
3476 mfentry = mf.get(abs)
3476 mfentry = mf.get(abs)
3477 target = repo.wjoin(abs)
3477 target = repo.wjoin(abs)
3478 def handle(xlist, dobackup):
3478 def handle(xlist, dobackup):
3479 xlist[0].append(abs)
3479 xlist[0].append(abs)
3480 if (dobackup and not opts.get('no_backup') and
3480 if (dobackup and not opts.get('no_backup') and
3481 os.path.lexists(target)):
3481 os.path.lexists(target)):
3482 bakname = "%s.orig" % rel
3482 bakname = "%s.orig" % rel
3483 ui.note(_('saving current version of %s as %s\n') %
3483 ui.note(_('saving current version of %s as %s\n') %
3484 (rel, bakname))
3484 (rel, bakname))
3485 if not opts.get('dry_run'):
3485 if not opts.get('dry_run'):
3486 util.rename(target, bakname)
3486 util.rename(target, bakname)
3487 if ui.verbose or not exact:
3487 if ui.verbose or not exact:
3488 msg = xlist[1]
3488 msg = xlist[1]
3489 if not isinstance(msg, basestring):
3489 if not isinstance(msg, basestring):
3490 msg = msg(abs)
3490 msg = msg(abs)
3491 ui.status(msg % rel)
3491 ui.status(msg % rel)
3492 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3492 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3493 if abs not in table:
3493 if abs not in table:
3494 continue
3494 continue
3495 # file has changed in dirstate
3495 # file has changed in dirstate
3496 if mfentry:
3496 if mfentry:
3497 handle(hitlist, backuphit)
3497 handle(hitlist, backuphit)
3498 elif misslist is not None:
3498 elif misslist is not None:
3499 handle(misslist, backupmiss)
3499 handle(misslist, backupmiss)
3500 break
3500 break
3501 else:
3501 else:
3502 if abs not in repo.dirstate:
3502 if abs not in repo.dirstate:
3503 if mfentry:
3503 if mfentry:
3504 handle(add, True)
3504 handle(add, True)
3505 elif exact:
3505 elif exact:
3506 ui.warn(_('file not managed: %s\n') % rel)
3506 ui.warn(_('file not managed: %s\n') % rel)
3507 continue
3507 continue
3508 # file has not changed in dirstate
3508 # file has not changed in dirstate
3509 if node == parent:
3509 if node == parent:
3510 if exact:
3510 if exact:
3511 ui.warn(_('no changes needed to %s\n') % rel)
3511 ui.warn(_('no changes needed to %s\n') % rel)
3512 continue
3512 continue
3513 if pmf is None:
3513 if pmf is None:
3514 # only need parent manifest in this unlikely case,
3514 # only need parent manifest in this unlikely case,
3515 # so do not read by default
3515 # so do not read by default
3516 pmf = repo[parent].manifest()
3516 pmf = repo[parent].manifest()
3517 if abs in pmf:
3517 if abs in pmf:
3518 if mfentry:
3518 if mfentry:
3519 # if version of file is same in parent and target
3519 # if version of file is same in parent and target
3520 # manifests, do nothing
3520 # manifests, do nothing
3521 if (pmf[abs] != mfentry or
3521 if (pmf[abs] != mfentry or
3522 pmf.flags(abs) != mf.flags(abs)):
3522 pmf.flags(abs) != mf.flags(abs)):
3523 handle(revert, False)
3523 handle(revert, False)
3524 else:
3524 else:
3525 handle(remove, False)
3525 handle(remove, False)
3526
3526
3527 if not opts.get('dry_run'):
3527 if not opts.get('dry_run'):
3528 def checkout(f):
3528 def checkout(f):
3529 fc = ctx[f]
3529 fc = ctx[f]
3530 repo.wwrite(f, fc.data(), fc.flags())
3530 repo.wwrite(f, fc.data(), fc.flags())
3531
3531
3532 audit_path = util.path_auditor(repo.root)
3532 audit_path = util.path_auditor(repo.root)
3533 for f in remove[0]:
3533 for f in remove[0]:
3534 if repo.dirstate[f] == 'a':
3534 if repo.dirstate[f] == 'a':
3535 repo.dirstate.forget(f)
3535 repo.dirstate.forget(f)
3536 continue
3536 continue
3537 audit_path(f)
3537 audit_path(f)
3538 try:
3538 try:
3539 util.unlinkpath(repo.wjoin(f))
3539 util.unlinkpath(repo.wjoin(f))
3540 except OSError:
3540 except OSError:
3541 pass
3541 pass
3542 repo.dirstate.remove(f)
3542 repo.dirstate.remove(f)
3543
3543
3544 normal = None
3544 normal = None
3545 if node == parent:
3545 if node == parent:
3546 # We're reverting to our parent. If possible, we'd like status
3546 # We're reverting to our parent. If possible, we'd like status
3547 # to report the file as clean. We have to use normallookup for
3547 # to report the file as clean. We have to use normallookup for
3548 # merges to avoid losing information about merged/dirty files.
3548 # merges to avoid losing information about merged/dirty files.
3549 if p2 != nullid:
3549 if p2 != nullid:
3550 normal = repo.dirstate.normallookup
3550 normal = repo.dirstate.normallookup
3551 else:
3551 else:
3552 normal = repo.dirstate.normal
3552 normal = repo.dirstate.normal
3553 for f in revert[0]:
3553 for f in revert[0]:
3554 checkout(f)
3554 checkout(f)
3555 if normal:
3555 if normal:
3556 normal(f)
3556 normal(f)
3557
3557
3558 for f in add[0]:
3558 for f in add[0]:
3559 checkout(f)
3559 checkout(f)
3560 repo.dirstate.add(f)
3560 repo.dirstate.add(f)
3561
3561
3562 normal = repo.dirstate.normallookup
3562 normal = repo.dirstate.normallookup
3563 if node == parent and p2 == nullid:
3563 if node == parent and p2 == nullid:
3564 normal = repo.dirstate.normal
3564 normal = repo.dirstate.normal
3565 for f in undelete[0]:
3565 for f in undelete[0]:
3566 checkout(f)
3566 checkout(f)
3567 normal(f)
3567 normal(f)
3568
3568
3569 finally:
3569 finally:
3570 wlock.release()
3570 wlock.release()
3571
3571
3572 def rollback(ui, repo, **opts):
3572 def rollback(ui, repo, **opts):
3573 """roll back the last transaction (dangerous)
3573 """roll back the last transaction (dangerous)
3574
3574
3575 This command should be used with care. There is only one level of
3575 This command should be used with care. There is only one level of
3576 rollback, and there is no way to undo a rollback. It will also
3576 rollback, and there is no way to undo a rollback. It will also
3577 restore the dirstate at the time of the last transaction, losing
3577 restore the dirstate at the time of the last transaction, losing
3578 any dirstate changes since that time. This command does not alter
3578 any dirstate changes since that time. This command does not alter
3579 the working directory.
3579 the working directory.
3580
3580
3581 Transactions are used to encapsulate the effects of all commands
3581 Transactions are used to encapsulate the effects of all commands
3582 that create new changesets or propagate existing changesets into a
3582 that create new changesets or propagate existing changesets into a
3583 repository. For example, the following commands are transactional,
3583 repository. For example, the following commands are transactional,
3584 and their effects can be rolled back:
3584 and their effects can be rolled back:
3585
3585
3586 - commit
3586 - commit
3587 - import
3587 - import
3588 - pull
3588 - pull
3589 - push (with this repository as the destination)
3589 - push (with this repository as the destination)
3590 - unbundle
3590 - unbundle
3591
3591
3592 This command is not intended for use on public repositories. Once
3592 This command is not intended for use on public repositories. Once
3593 changes are visible for pull by other users, rolling a transaction
3593 changes are visible for pull by other users, rolling a transaction
3594 back locally is ineffective (someone else may already have pulled
3594 back locally is ineffective (someone else may already have pulled
3595 the changes). Furthermore, a race is possible with readers of the
3595 the changes). Furthermore, a race is possible with readers of the
3596 repository; for example an in-progress pull from the repository
3596 repository; for example an in-progress pull from the repository
3597 may fail if a rollback is performed.
3597 may fail if a rollback is performed.
3598
3598
3599 Returns 0 on success, 1 if no rollback data is available.
3599 Returns 0 on success, 1 if no rollback data is available.
3600 """
3600 """
3601 return repo.rollback(opts.get('dry_run'))
3601 return repo.rollback(opts.get('dry_run'))
3602
3602
3603 def root(ui, repo):
3603 def root(ui, repo):
3604 """print the root (top) of the current working directory
3604 """print the root (top) of the current working directory
3605
3605
3606 Print the root directory of the current repository.
3606 Print the root directory of the current repository.
3607
3607
3608 Returns 0 on success.
3608 Returns 0 on success.
3609 """
3609 """
3610 ui.write(repo.root + "\n")
3610 ui.write(repo.root + "\n")
3611
3611
3612 def serve(ui, repo, **opts):
3612 def serve(ui, repo, **opts):
3613 """start stand-alone webserver
3613 """start stand-alone webserver
3614
3614
3615 Start a local HTTP repository browser and pull server. You can use
3615 Start a local HTTP repository browser and pull server. You can use
3616 this for ad-hoc sharing and browsing of repositories. It is
3616 this for ad-hoc sharing and browsing of repositories. It is
3617 recommended to use a real web server to serve a repository for
3617 recommended to use a real web server to serve a repository for
3618 longer periods of time.
3618 longer periods of time.
3619
3619
3620 Please note that the server does not implement access control.
3620 Please note that the server does not implement access control.
3621 This means that, by default, anybody can read from the server and
3621 This means that, by default, anybody can read from the server and
3622 nobody can write to it by default. Set the ``web.allow_push``
3622 nobody can write to it by default. Set the ``web.allow_push``
3623 option to ``*`` to allow everybody to push to the server. You
3623 option to ``*`` to allow everybody to push to the server. You
3624 should use a real web server if you need to authenticate users.
3624 should use a real web server if you need to authenticate users.
3625
3625
3626 By default, the server logs accesses to stdout and errors to
3626 By default, the server logs accesses to stdout and errors to
3627 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3627 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3628 files.
3628 files.
3629
3629
3630 To have the server choose a free port number to listen on, specify
3630 To have the server choose a free port number to listen on, specify
3631 a port number of 0; in this case, the server will print the port
3631 a port number of 0; in this case, the server will print the port
3632 number it uses.
3632 number it uses.
3633
3633
3634 Returns 0 on success.
3634 Returns 0 on success.
3635 """
3635 """
3636
3636
3637 if opts["stdio"]:
3637 if opts["stdio"]:
3638 if repo is None:
3638 if repo is None:
3639 raise error.RepoError(_("There is no Mercurial repository here"
3639 raise error.RepoError(_("There is no Mercurial repository here"
3640 " (.hg not found)"))
3640 " (.hg not found)"))
3641 s = sshserver.sshserver(ui, repo)
3641 s = sshserver.sshserver(ui, repo)
3642 s.serve_forever()
3642 s.serve_forever()
3643
3643
3644 # this way we can check if something was given in the command-line
3644 # this way we can check if something was given in the command-line
3645 if opts.get('port'):
3645 if opts.get('port'):
3646 opts['port'] = util.getport(opts.get('port'))
3646 opts['port'] = util.getport(opts.get('port'))
3647
3647
3648 baseui = repo and repo.baseui or ui
3648 baseui = repo and repo.baseui or ui
3649 optlist = ("name templates style address port prefix ipv6"
3649 optlist = ("name templates style address port prefix ipv6"
3650 " accesslog errorlog certificate encoding")
3650 " accesslog errorlog certificate encoding")
3651 for o in optlist.split():
3651 for o in optlist.split():
3652 val = opts.get(o, '')
3652 val = opts.get(o, '')
3653 if val in (None, ''): # should check against default options instead
3653 if val in (None, ''): # should check against default options instead
3654 continue
3654 continue
3655 baseui.setconfig("web", o, val)
3655 baseui.setconfig("web", o, val)
3656 if repo and repo.ui != baseui:
3656 if repo and repo.ui != baseui:
3657 repo.ui.setconfig("web", o, val)
3657 repo.ui.setconfig("web", o, val)
3658
3658
3659 o = opts.get('web_conf') or opts.get('webdir_conf')
3659 o = opts.get('web_conf') or opts.get('webdir_conf')
3660 if not o:
3660 if not o:
3661 if not repo:
3661 if not repo:
3662 raise error.RepoError(_("There is no Mercurial repository"
3662 raise error.RepoError(_("There is no Mercurial repository"
3663 " here (.hg not found)"))
3663 " here (.hg not found)"))
3664 o = repo.root
3664 o = repo.root
3665
3665
3666 app = hgweb.hgweb(o, baseui=ui)
3666 app = hgweb.hgweb(o, baseui=ui)
3667
3667
3668 class service(object):
3668 class service(object):
3669 def init(self):
3669 def init(self):
3670 util.set_signal_handler()
3670 util.set_signal_handler()
3671 self.httpd = hgweb.server.create_server(ui, app)
3671 self.httpd = hgweb.server.create_server(ui, app)
3672
3672
3673 if opts['port'] and not ui.verbose:
3673 if opts['port'] and not ui.verbose:
3674 return
3674 return
3675
3675
3676 if self.httpd.prefix:
3676 if self.httpd.prefix:
3677 prefix = self.httpd.prefix.strip('/') + '/'
3677 prefix = self.httpd.prefix.strip('/') + '/'
3678 else:
3678 else:
3679 prefix = ''
3679 prefix = ''
3680
3680
3681 port = ':%d' % self.httpd.port
3681 port = ':%d' % self.httpd.port
3682 if port == ':80':
3682 if port == ':80':
3683 port = ''
3683 port = ''
3684
3684
3685 bindaddr = self.httpd.addr
3685 bindaddr = self.httpd.addr
3686 if bindaddr == '0.0.0.0':
3686 if bindaddr == '0.0.0.0':
3687 bindaddr = '*'
3687 bindaddr = '*'
3688 elif ':' in bindaddr: # IPv6
3688 elif ':' in bindaddr: # IPv6
3689 bindaddr = '[%s]' % bindaddr
3689 bindaddr = '[%s]' % bindaddr
3690
3690
3691 fqaddr = self.httpd.fqaddr
3691 fqaddr = self.httpd.fqaddr
3692 if ':' in fqaddr:
3692 if ':' in fqaddr:
3693 fqaddr = '[%s]' % fqaddr
3693 fqaddr = '[%s]' % fqaddr
3694 if opts['port']:
3694 if opts['port']:
3695 write = ui.status
3695 write = ui.status
3696 else:
3696 else:
3697 write = ui.write
3697 write = ui.write
3698 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3698 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3699 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3699 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3700
3700
3701 def run(self):
3701 def run(self):
3702 self.httpd.serve_forever()
3702 self.httpd.serve_forever()
3703
3703
3704 service = service()
3704 service = service()
3705
3705
3706 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3706 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3707
3707
3708 def status(ui, repo, *pats, **opts):
3708 def status(ui, repo, *pats, **opts):
3709 """show changed files in the working directory
3709 """show changed files in the working directory
3710
3710
3711 Show status of files in the repository. If names are given, only
3711 Show status of files in the repository. If names are given, only
3712 files that match are shown. Files that are clean or ignored or
3712 files that match are shown. Files that are clean or ignored or
3713 the source of a copy/move operation, are not listed unless
3713 the source of a copy/move operation, are not listed unless
3714 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3714 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3715 Unless options described with "show only ..." are given, the
3715 Unless options described with "show only ..." are given, the
3716 options -mardu are used.
3716 options -mardu are used.
3717
3717
3718 Option -q/--quiet hides untracked (unknown and ignored) files
3718 Option -q/--quiet hides untracked (unknown and ignored) files
3719 unless explicitly requested with -u/--unknown or -i/--ignored.
3719 unless explicitly requested with -u/--unknown or -i/--ignored.
3720
3720
3721 .. note::
3721 .. note::
3722 status may appear to disagree with diff if permissions have
3722 status may appear to disagree with diff if permissions have
3723 changed or a merge has occurred. The standard diff format does
3723 changed or a merge has occurred. The standard diff format does
3724 not report permission changes and diff only reports changes
3724 not report permission changes and diff only reports changes
3725 relative to one merge parent.
3725 relative to one merge parent.
3726
3726
3727 If one revision is given, it is used as the base revision.
3727 If one revision is given, it is used as the base revision.
3728 If two revisions are given, the differences between them are
3728 If two revisions are given, the differences between them are
3729 shown. The --change option can also be used as a shortcut to list
3729 shown. The --change option can also be used as a shortcut to list
3730 the changed files of a revision from its first parent.
3730 the changed files of a revision from its first parent.
3731
3731
3732 The codes used to show the status of files are::
3732 The codes used to show the status of files are::
3733
3733
3734 M = modified
3734 M = modified
3735 A = added
3735 A = added
3736 R = removed
3736 R = removed
3737 C = clean
3737 C = clean
3738 ! = missing (deleted by non-hg command, but still tracked)
3738 ! = missing (deleted by non-hg command, but still tracked)
3739 ? = not tracked
3739 ? = not tracked
3740 I = ignored
3740 I = ignored
3741 = origin of the previous file listed as A (added)
3741 = origin of the previous file listed as A (added)
3742
3742
3743 Returns 0 on success.
3743 Returns 0 on success.
3744 """
3744 """
3745
3745
3746 revs = opts.get('rev')
3746 revs = opts.get('rev')
3747 change = opts.get('change')
3747 change = opts.get('change')
3748
3748
3749 if revs and change:
3749 if revs and change:
3750 msg = _('cannot specify --rev and --change at the same time')
3750 msg = _('cannot specify --rev and --change at the same time')
3751 raise util.Abort(msg)
3751 raise util.Abort(msg)
3752 elif change:
3752 elif change:
3753 node2 = repo.lookup(change)
3753 node2 = repo.lookup(change)
3754 node1 = repo[node2].parents()[0].node()
3754 node1 = repo[node2].p1().node()
3755 else:
3755 else:
3756 node1, node2 = cmdutil.revpair(repo, revs)
3756 node1, node2 = cmdutil.revpair(repo, revs)
3757
3757
3758 cwd = (pats and repo.getcwd()) or ''
3758 cwd = (pats and repo.getcwd()) or ''
3759 end = opts.get('print0') and '\0' or '\n'
3759 end = opts.get('print0') and '\0' or '\n'
3760 copy = {}
3760 copy = {}
3761 states = 'modified added removed deleted unknown ignored clean'.split()
3761 states = 'modified added removed deleted unknown ignored clean'.split()
3762 show = [k for k in states if opts.get(k)]
3762 show = [k for k in states if opts.get(k)]
3763 if opts.get('all'):
3763 if opts.get('all'):
3764 show += ui.quiet and (states[:4] + ['clean']) or states
3764 show += ui.quiet and (states[:4] + ['clean']) or states
3765 if not show:
3765 if not show:
3766 show = ui.quiet and states[:4] or states[:5]
3766 show = ui.quiet and states[:4] or states[:5]
3767
3767
3768 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3768 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3769 'ignored' in show, 'clean' in show, 'unknown' in show,
3769 'ignored' in show, 'clean' in show, 'unknown' in show,
3770 opts.get('subrepos'))
3770 opts.get('subrepos'))
3771 changestates = zip(states, 'MAR!?IC', stat)
3771 changestates = zip(states, 'MAR!?IC', stat)
3772
3772
3773 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3773 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3774 ctxn = repo[nullid]
3774 ctxn = repo[nullid]
3775 ctx1 = repo[node1]
3775 ctx1 = repo[node1]
3776 ctx2 = repo[node2]
3776 ctx2 = repo[node2]
3777 added = stat[1]
3777 added = stat[1]
3778 if node2 is None:
3778 if node2 is None:
3779 added = stat[0] + stat[1] # merged?
3779 added = stat[0] + stat[1] # merged?
3780
3780
3781 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3781 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3782 if k in added:
3782 if k in added:
3783 copy[k] = v
3783 copy[k] = v
3784 elif v in added:
3784 elif v in added:
3785 copy[v] = k
3785 copy[v] = k
3786
3786
3787 for state, char, files in changestates:
3787 for state, char, files in changestates:
3788 if state in show:
3788 if state in show:
3789 format = "%s %%s%s" % (char, end)
3789 format = "%s %%s%s" % (char, end)
3790 if opts.get('no_status'):
3790 if opts.get('no_status'):
3791 format = "%%s%s" % end
3791 format = "%%s%s" % end
3792
3792
3793 for f in files:
3793 for f in files:
3794 ui.write(format % repo.pathto(f, cwd),
3794 ui.write(format % repo.pathto(f, cwd),
3795 label='status.' + state)
3795 label='status.' + state)
3796 if f in copy:
3796 if f in copy:
3797 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3797 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3798 label='status.copied')
3798 label='status.copied')
3799
3799
3800 def summary(ui, repo, **opts):
3800 def summary(ui, repo, **opts):
3801 """summarize working directory state
3801 """summarize working directory state
3802
3802
3803 This generates a brief summary of the working directory state,
3803 This generates a brief summary of the working directory state,
3804 including parents, branch, commit status, and available updates.
3804 including parents, branch, commit status, and available updates.
3805
3805
3806 With the --remote option, this will check the default paths for
3806 With the --remote option, this will check the default paths for
3807 incoming and outgoing changes. This can be time-consuming.
3807 incoming and outgoing changes. This can be time-consuming.
3808
3808
3809 Returns 0 on success.
3809 Returns 0 on success.
3810 """
3810 """
3811
3811
3812 ctx = repo[None]
3812 ctx = repo[None]
3813 parents = ctx.parents()
3813 parents = ctx.parents()
3814 pnode = parents[0].node()
3814 pnode = parents[0].node()
3815
3815
3816 for p in parents:
3816 for p in parents:
3817 # label with log.changeset (instead of log.parent) since this
3817 # label with log.changeset (instead of log.parent) since this
3818 # shows a working directory parent *changeset*:
3818 # shows a working directory parent *changeset*:
3819 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3819 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3820 label='log.changeset')
3820 label='log.changeset')
3821 ui.write(' '.join(p.tags()), label='log.tag')
3821 ui.write(' '.join(p.tags()), label='log.tag')
3822 if p.bookmarks():
3822 if p.bookmarks():
3823 ui.write(' ' + ' '.join(p.bookmarks()), label='log.bookmark')
3823 ui.write(' ' + ' '.join(p.bookmarks()), label='log.bookmark')
3824 if p.rev() == -1:
3824 if p.rev() == -1:
3825 if not len(repo):
3825 if not len(repo):
3826 ui.write(_(' (empty repository)'))
3826 ui.write(_(' (empty repository)'))
3827 else:
3827 else:
3828 ui.write(_(' (no revision checked out)'))
3828 ui.write(_(' (no revision checked out)'))
3829 ui.write('\n')
3829 ui.write('\n')
3830 if p.description():
3830 if p.description():
3831 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3831 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3832 label='log.summary')
3832 label='log.summary')
3833
3833
3834 branch = ctx.branch()
3834 branch = ctx.branch()
3835 bheads = repo.branchheads(branch)
3835 bheads = repo.branchheads(branch)
3836 m = _('branch: %s\n') % branch
3836 m = _('branch: %s\n') % branch
3837 if branch != 'default':
3837 if branch != 'default':
3838 ui.write(m, label='log.branch')
3838 ui.write(m, label='log.branch')
3839 else:
3839 else:
3840 ui.status(m, label='log.branch')
3840 ui.status(m, label='log.branch')
3841
3841
3842 st = list(repo.status(unknown=True))[:6]
3842 st = list(repo.status(unknown=True))[:6]
3843
3843
3844 c = repo.dirstate.copies()
3844 c = repo.dirstate.copies()
3845 copied, renamed = [], []
3845 copied, renamed = [], []
3846 for d, s in c.iteritems():
3846 for d, s in c.iteritems():
3847 if s in st[2]:
3847 if s in st[2]:
3848 st[2].remove(s)
3848 st[2].remove(s)
3849 renamed.append(d)
3849 renamed.append(d)
3850 else:
3850 else:
3851 copied.append(d)
3851 copied.append(d)
3852 if d in st[1]:
3852 if d in st[1]:
3853 st[1].remove(d)
3853 st[1].remove(d)
3854 st.insert(3, renamed)
3854 st.insert(3, renamed)
3855 st.insert(4, copied)
3855 st.insert(4, copied)
3856
3856
3857 ms = mergemod.mergestate(repo)
3857 ms = mergemod.mergestate(repo)
3858 st.append([f for f in ms if ms[f] == 'u'])
3858 st.append([f for f in ms if ms[f] == 'u'])
3859
3859
3860 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3860 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3861 st.append(subs)
3861 st.append(subs)
3862
3862
3863 labels = [ui.label(_('%d modified'), 'status.modified'),
3863 labels = [ui.label(_('%d modified'), 'status.modified'),
3864 ui.label(_('%d added'), 'status.added'),
3864 ui.label(_('%d added'), 'status.added'),
3865 ui.label(_('%d removed'), 'status.removed'),
3865 ui.label(_('%d removed'), 'status.removed'),
3866 ui.label(_('%d renamed'), 'status.copied'),
3866 ui.label(_('%d renamed'), 'status.copied'),
3867 ui.label(_('%d copied'), 'status.copied'),
3867 ui.label(_('%d copied'), 'status.copied'),
3868 ui.label(_('%d deleted'), 'status.deleted'),
3868 ui.label(_('%d deleted'), 'status.deleted'),
3869 ui.label(_('%d unknown'), 'status.unknown'),
3869 ui.label(_('%d unknown'), 'status.unknown'),
3870 ui.label(_('%d ignored'), 'status.ignored'),
3870 ui.label(_('%d ignored'), 'status.ignored'),
3871 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3871 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3872 ui.label(_('%d subrepos'), 'status.modified')]
3872 ui.label(_('%d subrepos'), 'status.modified')]
3873 t = []
3873 t = []
3874 for s, l in zip(st, labels):
3874 for s, l in zip(st, labels):
3875 if s:
3875 if s:
3876 t.append(l % len(s))
3876 t.append(l % len(s))
3877
3877
3878 t = ', '.join(t)
3878 t = ', '.join(t)
3879 cleanworkdir = False
3879 cleanworkdir = False
3880
3880
3881 if len(parents) > 1:
3881 if len(parents) > 1:
3882 t += _(' (merge)')
3882 t += _(' (merge)')
3883 elif branch != parents[0].branch():
3883 elif branch != parents[0].branch():
3884 t += _(' (new branch)')
3884 t += _(' (new branch)')
3885 elif (parents[0].extra().get('close') and
3885 elif (parents[0].extra().get('close') and
3886 pnode in repo.branchheads(branch, closed=True)):
3886 pnode in repo.branchheads(branch, closed=True)):
3887 t += _(' (head closed)')
3887 t += _(' (head closed)')
3888 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3888 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3889 t += _(' (clean)')
3889 t += _(' (clean)')
3890 cleanworkdir = True
3890 cleanworkdir = True
3891 elif pnode not in bheads:
3891 elif pnode not in bheads:
3892 t += _(' (new branch head)')
3892 t += _(' (new branch head)')
3893
3893
3894 if cleanworkdir:
3894 if cleanworkdir:
3895 ui.status(_('commit: %s\n') % t.strip())
3895 ui.status(_('commit: %s\n') % t.strip())
3896 else:
3896 else:
3897 ui.write(_('commit: %s\n') % t.strip())
3897 ui.write(_('commit: %s\n') % t.strip())
3898
3898
3899 # all ancestors of branch heads - all ancestors of parent = new csets
3899 # all ancestors of branch heads - all ancestors of parent = new csets
3900 new = [0] * len(repo)
3900 new = [0] * len(repo)
3901 cl = repo.changelog
3901 cl = repo.changelog
3902 for a in [cl.rev(n) for n in bheads]:
3902 for a in [cl.rev(n) for n in bheads]:
3903 new[a] = 1
3903 new[a] = 1
3904 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3904 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3905 new[a] = 1
3905 new[a] = 1
3906 for a in [p.rev() for p in parents]:
3906 for a in [p.rev() for p in parents]:
3907 if a >= 0:
3907 if a >= 0:
3908 new[a] = 0
3908 new[a] = 0
3909 for a in cl.ancestors(*[p.rev() for p in parents]):
3909 for a in cl.ancestors(*[p.rev() for p in parents]):
3910 new[a] = 0
3910 new[a] = 0
3911 new = sum(new)
3911 new = sum(new)
3912
3912
3913 if new == 0:
3913 if new == 0:
3914 ui.status(_('update: (current)\n'))
3914 ui.status(_('update: (current)\n'))
3915 elif pnode not in bheads:
3915 elif pnode not in bheads:
3916 ui.write(_('update: %d new changesets (update)\n') % new)
3916 ui.write(_('update: %d new changesets (update)\n') % new)
3917 else:
3917 else:
3918 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3918 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3919 (new, len(bheads)))
3919 (new, len(bheads)))
3920
3920
3921 if opts.get('remote'):
3921 if opts.get('remote'):
3922 t = []
3922 t = []
3923 source, branches = hg.parseurl(ui.expandpath('default'))
3923 source, branches = hg.parseurl(ui.expandpath('default'))
3924 other = hg.repository(hg.remoteui(repo, {}), source)
3924 other = hg.repository(hg.remoteui(repo, {}), source)
3925 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3925 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3926 ui.debug('comparing with %s\n' % url.hidepassword(source))
3926 ui.debug('comparing with %s\n' % url.hidepassword(source))
3927 repo.ui.pushbuffer()
3927 repo.ui.pushbuffer()
3928 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3928 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3929 repo.ui.popbuffer()
3929 repo.ui.popbuffer()
3930 if incoming:
3930 if incoming:
3931 t.append(_('1 or more incoming'))
3931 t.append(_('1 or more incoming'))
3932
3932
3933 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3933 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3934 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3934 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3935 other = hg.repository(hg.remoteui(repo, {}), dest)
3935 other = hg.repository(hg.remoteui(repo, {}), dest)
3936 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3936 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3937 repo.ui.pushbuffer()
3937 repo.ui.pushbuffer()
3938 o = discovery.findoutgoing(repo, other)
3938 o = discovery.findoutgoing(repo, other)
3939 repo.ui.popbuffer()
3939 repo.ui.popbuffer()
3940 o = repo.changelog.nodesbetween(o, None)[0]
3940 o = repo.changelog.nodesbetween(o, None)[0]
3941 if o:
3941 if o:
3942 t.append(_('%d outgoing') % len(o))
3942 t.append(_('%d outgoing') % len(o))
3943 if 'bookmarks' in other.listkeys('namespaces'):
3943 if 'bookmarks' in other.listkeys('namespaces'):
3944 lmarks = repo.listkeys('bookmarks')
3944 lmarks = repo.listkeys('bookmarks')
3945 rmarks = other.listkeys('bookmarks')
3945 rmarks = other.listkeys('bookmarks')
3946 diff = set(rmarks) - set(lmarks)
3946 diff = set(rmarks) - set(lmarks)
3947 if len(diff) > 0:
3947 if len(diff) > 0:
3948 t.append(_('%d incoming bookmarks') % len(diff))
3948 t.append(_('%d incoming bookmarks') % len(diff))
3949 diff = set(lmarks) - set(rmarks)
3949 diff = set(lmarks) - set(rmarks)
3950 if len(diff) > 0:
3950 if len(diff) > 0:
3951 t.append(_('%d outgoing bookmarks') % len(diff))
3951 t.append(_('%d outgoing bookmarks') % len(diff))
3952
3952
3953 if t:
3953 if t:
3954 ui.write(_('remote: %s\n') % (', '.join(t)))
3954 ui.write(_('remote: %s\n') % (', '.join(t)))
3955 else:
3955 else:
3956 ui.status(_('remote: (synced)\n'))
3956 ui.status(_('remote: (synced)\n'))
3957
3957
3958 def tag(ui, repo, name1, *names, **opts):
3958 def tag(ui, repo, name1, *names, **opts):
3959 """add one or more tags for the current or given revision
3959 """add one or more tags for the current or given revision
3960
3960
3961 Name a particular revision using <name>.
3961 Name a particular revision using <name>.
3962
3962
3963 Tags are used to name particular revisions of the repository and are
3963 Tags are used to name particular revisions of the repository and are
3964 very useful to compare different revisions, to go back to significant
3964 very useful to compare different revisions, to go back to significant
3965 earlier versions or to mark branch points as releases, etc. Changing
3965 earlier versions or to mark branch points as releases, etc. Changing
3966 an existing tag is normally disallowed; use -f/--force to override.
3966 an existing tag is normally disallowed; use -f/--force to override.
3967
3967
3968 If no revision is given, the parent of the working directory is
3968 If no revision is given, the parent of the working directory is
3969 used, or tip if no revision is checked out.
3969 used, or tip if no revision is checked out.
3970
3970
3971 To facilitate version control, distribution, and merging of tags,
3971 To facilitate version control, distribution, and merging of tags,
3972 they are stored as a file named ".hgtags" which is managed similarly
3972 they are stored as a file named ".hgtags" which is managed similarly
3973 to other project files and can be hand-edited if necessary. This
3973 to other project files and can be hand-edited if necessary. This
3974 also means that tagging creates a new commit. The file
3974 also means that tagging creates a new commit. The file
3975 ".hg/localtags" is used for local tags (not shared among
3975 ".hg/localtags" is used for local tags (not shared among
3976 repositories).
3976 repositories).
3977
3977
3978 Tag commits are usually made at the head of a branch. If the parent
3978 Tag commits are usually made at the head of a branch. If the parent
3979 of the working directory is not a branch head, :hg:`tag` aborts; use
3979 of the working directory is not a branch head, :hg:`tag` aborts; use
3980 -f/--force to force the tag commit to be based on a non-head
3980 -f/--force to force the tag commit to be based on a non-head
3981 changeset.
3981 changeset.
3982
3982
3983 See :hg:`help dates` for a list of formats valid for -d/--date.
3983 See :hg:`help dates` for a list of formats valid for -d/--date.
3984
3984
3985 Since tag names have priority over branch names during revision
3985 Since tag names have priority over branch names during revision
3986 lookup, using an existing branch name as a tag name is discouraged.
3986 lookup, using an existing branch name as a tag name is discouraged.
3987
3987
3988 Returns 0 on success.
3988 Returns 0 on success.
3989 """
3989 """
3990
3990
3991 rev_ = "."
3991 rev_ = "."
3992 names = [t.strip() for t in (name1,) + names]
3992 names = [t.strip() for t in (name1,) + names]
3993 if len(names) != len(set(names)):
3993 if len(names) != len(set(names)):
3994 raise util.Abort(_('tag names must be unique'))
3994 raise util.Abort(_('tag names must be unique'))
3995 for n in names:
3995 for n in names:
3996 if n in ['tip', '.', 'null']:
3996 if n in ['tip', '.', 'null']:
3997 raise util.Abort(_('the name \'%s\' is reserved') % n)
3997 raise util.Abort(_('the name \'%s\' is reserved') % n)
3998 if not n:
3998 if not n:
3999 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
3999 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
4000 if opts.get('rev') and opts.get('remove'):
4000 if opts.get('rev') and opts.get('remove'):
4001 raise util.Abort(_("--rev and --remove are incompatible"))
4001 raise util.Abort(_("--rev and --remove are incompatible"))
4002 if opts.get('rev'):
4002 if opts.get('rev'):
4003 rev_ = opts['rev']
4003 rev_ = opts['rev']
4004 message = opts.get('message')
4004 message = opts.get('message')
4005 if opts.get('remove'):
4005 if opts.get('remove'):
4006 expectedtype = opts.get('local') and 'local' or 'global'
4006 expectedtype = opts.get('local') and 'local' or 'global'
4007 for n in names:
4007 for n in names:
4008 if not repo.tagtype(n):
4008 if not repo.tagtype(n):
4009 raise util.Abort(_('tag \'%s\' does not exist') % n)
4009 raise util.Abort(_('tag \'%s\' does not exist') % n)
4010 if repo.tagtype(n) != expectedtype:
4010 if repo.tagtype(n) != expectedtype:
4011 if expectedtype == 'global':
4011 if expectedtype == 'global':
4012 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
4012 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
4013 else:
4013 else:
4014 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
4014 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
4015 rev_ = nullid
4015 rev_ = nullid
4016 if not message:
4016 if not message:
4017 # we don't translate commit messages
4017 # we don't translate commit messages
4018 message = 'Removed tag %s' % ', '.join(names)
4018 message = 'Removed tag %s' % ', '.join(names)
4019 elif not opts.get('force'):
4019 elif not opts.get('force'):
4020 for n in names:
4020 for n in names:
4021 if n in repo.tags():
4021 if n in repo.tags():
4022 raise util.Abort(_('tag \'%s\' already exists '
4022 raise util.Abort(_('tag \'%s\' already exists '
4023 '(use -f to force)') % n)
4023 '(use -f to force)') % n)
4024 if not opts.get('local'):
4024 if not opts.get('local'):
4025 p1, p2 = repo.dirstate.parents()
4025 p1, p2 = repo.dirstate.parents()
4026 if p2 != nullid:
4026 if p2 != nullid:
4027 raise util.Abort(_('uncommitted merge'))
4027 raise util.Abort(_('uncommitted merge'))
4028 bheads = repo.branchheads()
4028 bheads = repo.branchheads()
4029 if not opts.get('force') and bheads and p1 not in bheads:
4029 if not opts.get('force') and bheads and p1 not in bheads:
4030 raise util.Abort(_('not at a branch head (use -f to force)'))
4030 raise util.Abort(_('not at a branch head (use -f to force)'))
4031 r = cmdutil.revsingle(repo, rev_).node()
4031 r = cmdutil.revsingle(repo, rev_).node()
4032
4032
4033 if not message:
4033 if not message:
4034 # we don't translate commit messages
4034 # we don't translate commit messages
4035 message = ('Added tag %s for changeset %s' %
4035 message = ('Added tag %s for changeset %s' %
4036 (', '.join(names), short(r)))
4036 (', '.join(names), short(r)))
4037
4037
4038 date = opts.get('date')
4038 date = opts.get('date')
4039 if date:
4039 if date:
4040 date = util.parsedate(date)
4040 date = util.parsedate(date)
4041
4041
4042 if opts.get('edit'):
4042 if opts.get('edit'):
4043 message = ui.edit(message, ui.username())
4043 message = ui.edit(message, ui.username())
4044
4044
4045 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
4045 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
4046
4046
4047 def tags(ui, repo):
4047 def tags(ui, repo):
4048 """list repository tags
4048 """list repository tags
4049
4049
4050 This lists both regular and local tags. When the -v/--verbose
4050 This lists both regular and local tags. When the -v/--verbose
4051 switch is used, a third column "local" is printed for local tags.
4051 switch is used, a third column "local" is printed for local tags.
4052
4052
4053 Returns 0 on success.
4053 Returns 0 on success.
4054 """
4054 """
4055
4055
4056 hexfunc = ui.debugflag and hex or short
4056 hexfunc = ui.debugflag and hex or short
4057 tagtype = ""
4057 tagtype = ""
4058
4058
4059 for t, n in reversed(repo.tagslist()):
4059 for t, n in reversed(repo.tagslist()):
4060 if ui.quiet:
4060 if ui.quiet:
4061 ui.write("%s\n" % t)
4061 ui.write("%s\n" % t)
4062 continue
4062 continue
4063
4063
4064 try:
4064 try:
4065 hn = hexfunc(n)
4065 hn = hexfunc(n)
4066 r = "%5d:%s" % (repo.changelog.rev(n), hn)
4066 r = "%5d:%s" % (repo.changelog.rev(n), hn)
4067 except error.LookupError:
4067 except error.LookupError:
4068 r = " ?:%s" % hn
4068 r = " ?:%s" % hn
4069 else:
4069 else:
4070 spaces = " " * (30 - encoding.colwidth(t))
4070 spaces = " " * (30 - encoding.colwidth(t))
4071 if ui.verbose:
4071 if ui.verbose:
4072 if repo.tagtype(t) == 'local':
4072 if repo.tagtype(t) == 'local':
4073 tagtype = " local"
4073 tagtype = " local"
4074 else:
4074 else:
4075 tagtype = ""
4075 tagtype = ""
4076 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
4076 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
4077
4077
4078 def tip(ui, repo, **opts):
4078 def tip(ui, repo, **opts):
4079 """show the tip revision
4079 """show the tip revision
4080
4080
4081 The tip revision (usually just called the tip) is the changeset
4081 The tip revision (usually just called the tip) is the changeset
4082 most recently added to the repository (and therefore the most
4082 most recently added to the repository (and therefore the most
4083 recently changed head).
4083 recently changed head).
4084
4084
4085 If you have just made a commit, that commit will be the tip. If
4085 If you have just made a commit, that commit will be the tip. If
4086 you have just pulled changes from another repository, the tip of
4086 you have just pulled changes from another repository, the tip of
4087 that repository becomes the current tip. The "tip" tag is special
4087 that repository becomes the current tip. The "tip" tag is special
4088 and cannot be renamed or assigned to a different changeset.
4088 and cannot be renamed or assigned to a different changeset.
4089
4089
4090 Returns 0 on success.
4090 Returns 0 on success.
4091 """
4091 """
4092 displayer = cmdutil.show_changeset(ui, repo, opts)
4092 displayer = cmdutil.show_changeset(ui, repo, opts)
4093 displayer.show(repo[len(repo) - 1])
4093 displayer.show(repo[len(repo) - 1])
4094 displayer.close()
4094 displayer.close()
4095
4095
4096 def unbundle(ui, repo, fname1, *fnames, **opts):
4096 def unbundle(ui, repo, fname1, *fnames, **opts):
4097 """apply one or more changegroup files
4097 """apply one or more changegroup files
4098
4098
4099 Apply one or more compressed changegroup files generated by the
4099 Apply one or more compressed changegroup files generated by the
4100 bundle command.
4100 bundle command.
4101
4101
4102 Returns 0 on success, 1 if an update has unresolved files.
4102 Returns 0 on success, 1 if an update has unresolved files.
4103 """
4103 """
4104 fnames = (fname1,) + fnames
4104 fnames = (fname1,) + fnames
4105
4105
4106 lock = repo.lock()
4106 lock = repo.lock()
4107 wc = repo['.']
4107 wc = repo['.']
4108 try:
4108 try:
4109 for fname in fnames:
4109 for fname in fnames:
4110 f = url.open(ui, fname)
4110 f = url.open(ui, fname)
4111 gen = changegroup.readbundle(f, fname)
4111 gen = changegroup.readbundle(f, fname)
4112 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
4112 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
4113 lock=lock)
4113 lock=lock)
4114 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
4114 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
4115 finally:
4115 finally:
4116 lock.release()
4116 lock.release()
4117 return postincoming(ui, repo, modheads, opts.get('update'), None)
4117 return postincoming(ui, repo, modheads, opts.get('update'), None)
4118
4118
4119 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
4119 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
4120 """update working directory (or switch revisions)
4120 """update working directory (or switch revisions)
4121
4121
4122 Update the repository's working directory to the specified
4122 Update the repository's working directory to the specified
4123 changeset. If no changeset is specified, update to the tip of the
4123 changeset. If no changeset is specified, update to the tip of the
4124 current named branch.
4124 current named branch.
4125
4125
4126 If the changeset is not a descendant of the working directory's
4126 If the changeset is not a descendant of the working directory's
4127 parent, the update is aborted. With the -c/--check option, the
4127 parent, the update is aborted. With the -c/--check option, the
4128 working directory is checked for uncommitted changes; if none are
4128 working directory is checked for uncommitted changes; if none are
4129 found, the working directory is updated to the specified
4129 found, the working directory is updated to the specified
4130 changeset.
4130 changeset.
4131
4131
4132 The following rules apply when the working directory contains
4132 The following rules apply when the working directory contains
4133 uncommitted changes:
4133 uncommitted changes:
4134
4134
4135 1. If neither -c/--check nor -C/--clean is specified, and if
4135 1. If neither -c/--check nor -C/--clean is specified, and if
4136 the requested changeset is an ancestor or descendant of
4136 the requested changeset is an ancestor or descendant of
4137 the working directory's parent, the uncommitted changes
4137 the working directory's parent, the uncommitted changes
4138 are merged into the requested changeset and the merged
4138 are merged into the requested changeset and the merged
4139 result is left uncommitted. If the requested changeset is
4139 result is left uncommitted. If the requested changeset is
4140 not an ancestor or descendant (that is, it is on another
4140 not an ancestor or descendant (that is, it is on another
4141 branch), the update is aborted and the uncommitted changes
4141 branch), the update is aborted and the uncommitted changes
4142 are preserved.
4142 are preserved.
4143
4143
4144 2. With the -c/--check option, the update is aborted and the
4144 2. With the -c/--check option, the update is aborted and the
4145 uncommitted changes are preserved.
4145 uncommitted changes are preserved.
4146
4146
4147 3. With the -C/--clean option, uncommitted changes are discarded and
4147 3. With the -C/--clean option, uncommitted changes are discarded and
4148 the working directory is updated to the requested changeset.
4148 the working directory is updated to the requested changeset.
4149
4149
4150 Use null as the changeset to remove the working directory (like
4150 Use null as the changeset to remove the working directory (like
4151 :hg:`clone -U`).
4151 :hg:`clone -U`).
4152
4152
4153 If you want to update just one file to an older changeset, use
4153 If you want to update just one file to an older changeset, use
4154 :hg:`revert`.
4154 :hg:`revert`.
4155
4155
4156 See :hg:`help dates` for a list of formats valid for -d/--date.
4156 See :hg:`help dates` for a list of formats valid for -d/--date.
4157
4157
4158 Returns 0 on success, 1 if there are unresolved files.
4158 Returns 0 on success, 1 if there are unresolved files.
4159 """
4159 """
4160 if rev and node:
4160 if rev and node:
4161 raise util.Abort(_("please specify just one revision"))
4161 raise util.Abort(_("please specify just one revision"))
4162
4162
4163 if rev is None or rev == '':
4163 if rev is None or rev == '':
4164 rev = node
4164 rev = node
4165
4165
4166 # if we defined a bookmark, we have to remember the original bookmark name
4166 # if we defined a bookmark, we have to remember the original bookmark name
4167 brev = rev
4167 brev = rev
4168 rev = cmdutil.revsingle(repo, rev, rev).rev()
4168 rev = cmdutil.revsingle(repo, rev, rev).rev()
4169
4169
4170 if check and clean:
4170 if check and clean:
4171 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
4171 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
4172
4172
4173 if check:
4173 if check:
4174 # we could use dirty() but we can ignore merge and branch trivia
4174 # we could use dirty() but we can ignore merge and branch trivia
4175 c = repo[None]
4175 c = repo[None]
4176 if c.modified() or c.added() or c.removed():
4176 if c.modified() or c.added() or c.removed():
4177 raise util.Abort(_("uncommitted local changes"))
4177 raise util.Abort(_("uncommitted local changes"))
4178
4178
4179 if date:
4179 if date:
4180 if rev:
4180 if rev:
4181 raise util.Abort(_("you can't specify a revision and a date"))
4181 raise util.Abort(_("you can't specify a revision and a date"))
4182 rev = cmdutil.finddate(ui, repo, date)
4182 rev = cmdutil.finddate(ui, repo, date)
4183
4183
4184 if clean or check:
4184 if clean or check:
4185 ret = hg.clean(repo, rev)
4185 ret = hg.clean(repo, rev)
4186 else:
4186 else:
4187 ret = hg.update(repo, rev)
4187 ret = hg.update(repo, rev)
4188
4188
4189 if brev in repo._bookmarks:
4189 if brev in repo._bookmarks:
4190 bookmarks.setcurrent(repo, brev)
4190 bookmarks.setcurrent(repo, brev)
4191
4191
4192 return ret
4192 return ret
4193
4193
4194 def verify(ui, repo):
4194 def verify(ui, repo):
4195 """verify the integrity of the repository
4195 """verify the integrity of the repository
4196
4196
4197 Verify the integrity of the current repository.
4197 Verify the integrity of the current repository.
4198
4198
4199 This will perform an extensive check of the repository's
4199 This will perform an extensive check of the repository's
4200 integrity, validating the hashes and checksums of each entry in
4200 integrity, validating the hashes and checksums of each entry in
4201 the changelog, manifest, and tracked files, as well as the
4201 the changelog, manifest, and tracked files, as well as the
4202 integrity of their crosslinks and indices.
4202 integrity of their crosslinks and indices.
4203
4203
4204 Returns 0 on success, 1 if errors are encountered.
4204 Returns 0 on success, 1 if errors are encountered.
4205 """
4205 """
4206 return hg.verify(repo)
4206 return hg.verify(repo)
4207
4207
4208 def version_(ui):
4208 def version_(ui):
4209 """output version and copyright information"""
4209 """output version and copyright information"""
4210 ui.write(_("Mercurial Distributed SCM (version %s)\n")
4210 ui.write(_("Mercurial Distributed SCM (version %s)\n")
4211 % util.version())
4211 % util.version())
4212 ui.status(_(
4212 ui.status(_(
4213 "(see http://mercurial.selenic.com for more information)\n"
4213 "(see http://mercurial.selenic.com for more information)\n"
4214 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
4214 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
4215 "This is free software; see the source for copying conditions. "
4215 "This is free software; see the source for copying conditions. "
4216 "There is NO\nwarranty; "
4216 "There is NO\nwarranty; "
4217 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
4217 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
4218 ))
4218 ))
4219
4219
4220 # Command options and aliases are listed here, alphabetically
4220 # Command options and aliases are listed here, alphabetically
4221
4221
4222 globalopts = [
4222 globalopts = [
4223 ('R', 'repository', '',
4223 ('R', 'repository', '',
4224 _('repository root directory or name of overlay bundle file'),
4224 _('repository root directory or name of overlay bundle file'),
4225 _('REPO')),
4225 _('REPO')),
4226 ('', 'cwd', '',
4226 ('', 'cwd', '',
4227 _('change working directory'), _('DIR')),
4227 _('change working directory'), _('DIR')),
4228 ('y', 'noninteractive', None,
4228 ('y', 'noninteractive', None,
4229 _('do not prompt, assume \'yes\' for any required answers')),
4229 _('do not prompt, assume \'yes\' for any required answers')),
4230 ('q', 'quiet', None, _('suppress output')),
4230 ('q', 'quiet', None, _('suppress output')),
4231 ('v', 'verbose', None, _('enable additional output')),
4231 ('v', 'verbose', None, _('enable additional output')),
4232 ('', 'config', [],
4232 ('', 'config', [],
4233 _('set/override config option (use \'section.name=value\')'),
4233 _('set/override config option (use \'section.name=value\')'),
4234 _('CONFIG')),
4234 _('CONFIG')),
4235 ('', 'debug', None, _('enable debugging output')),
4235 ('', 'debug', None, _('enable debugging output')),
4236 ('', 'debugger', None, _('start debugger')),
4236 ('', 'debugger', None, _('start debugger')),
4237 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
4237 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
4238 _('ENCODE')),
4238 _('ENCODE')),
4239 ('', 'encodingmode', encoding.encodingmode,
4239 ('', 'encodingmode', encoding.encodingmode,
4240 _('set the charset encoding mode'), _('MODE')),
4240 _('set the charset encoding mode'), _('MODE')),
4241 ('', 'traceback', None, _('always print a traceback on exception')),
4241 ('', 'traceback', None, _('always print a traceback on exception')),
4242 ('', 'time', None, _('time how long the command takes')),
4242 ('', 'time', None, _('time how long the command takes')),
4243 ('', 'profile', None, _('print command execution profile')),
4243 ('', 'profile', None, _('print command execution profile')),
4244 ('', 'version', None, _('output version information and exit')),
4244 ('', 'version', None, _('output version information and exit')),
4245 ('h', 'help', None, _('display help and exit')),
4245 ('h', 'help', None, _('display help and exit')),
4246 ]
4246 ]
4247
4247
4248 dryrunopts = [('n', 'dry-run', None,
4248 dryrunopts = [('n', 'dry-run', None,
4249 _('do not perform actions, just print output'))]
4249 _('do not perform actions, just print output'))]
4250
4250
4251 remoteopts = [
4251 remoteopts = [
4252 ('e', 'ssh', '',
4252 ('e', 'ssh', '',
4253 _('specify ssh command to use'), _('CMD')),
4253 _('specify ssh command to use'), _('CMD')),
4254 ('', 'remotecmd', '',
4254 ('', 'remotecmd', '',
4255 _('specify hg command to run on the remote side'), _('CMD')),
4255 _('specify hg command to run on the remote side'), _('CMD')),
4256 ('', 'insecure', None,
4256 ('', 'insecure', None,
4257 _('do not verify server certificate (ignoring web.cacerts config)')),
4257 _('do not verify server certificate (ignoring web.cacerts config)')),
4258 ]
4258 ]
4259
4259
4260 walkopts = [
4260 walkopts = [
4261 ('I', 'include', [],
4261 ('I', 'include', [],
4262 _('include names matching the given patterns'), _('PATTERN')),
4262 _('include names matching the given patterns'), _('PATTERN')),
4263 ('X', 'exclude', [],
4263 ('X', 'exclude', [],
4264 _('exclude names matching the given patterns'), _('PATTERN')),
4264 _('exclude names matching the given patterns'), _('PATTERN')),
4265 ]
4265 ]
4266
4266
4267 commitopts = [
4267 commitopts = [
4268 ('m', 'message', '',
4268 ('m', 'message', '',
4269 _('use text as commit message'), _('TEXT')),
4269 _('use text as commit message'), _('TEXT')),
4270 ('l', 'logfile', '',
4270 ('l', 'logfile', '',
4271 _('read commit message from file'), _('FILE')),
4271 _('read commit message from file'), _('FILE')),
4272 ]
4272 ]
4273
4273
4274 commitopts2 = [
4274 commitopts2 = [
4275 ('d', 'date', '',
4275 ('d', 'date', '',
4276 _('record datecode as commit date'), _('DATE')),
4276 _('record datecode as commit date'), _('DATE')),
4277 ('u', 'user', '',
4277 ('u', 'user', '',
4278 _('record the specified user as committer'), _('USER')),
4278 _('record the specified user as committer'), _('USER')),
4279 ]
4279 ]
4280
4280
4281 templateopts = [
4281 templateopts = [
4282 ('', 'style', '',
4282 ('', 'style', '',
4283 _('display using template map file'), _('STYLE')),
4283 _('display using template map file'), _('STYLE')),
4284 ('', 'template', '',
4284 ('', 'template', '',
4285 _('display with template'), _('TEMPLATE')),
4285 _('display with template'), _('TEMPLATE')),
4286 ]
4286 ]
4287
4287
4288 logopts = [
4288 logopts = [
4289 ('p', 'patch', None, _('show patch')),
4289 ('p', 'patch', None, _('show patch')),
4290 ('g', 'git', None, _('use git extended diff format')),
4290 ('g', 'git', None, _('use git extended diff format')),
4291 ('l', 'limit', '',
4291 ('l', 'limit', '',
4292 _('limit number of changes displayed'), _('NUM')),
4292 _('limit number of changes displayed'), _('NUM')),
4293 ('M', 'no-merges', None, _('do not show merges')),
4293 ('M', 'no-merges', None, _('do not show merges')),
4294 ('', 'stat', None, _('output diffstat-style summary of changes')),
4294 ('', 'stat', None, _('output diffstat-style summary of changes')),
4295 ] + templateopts
4295 ] + templateopts
4296
4296
4297 diffopts = [
4297 diffopts = [
4298 ('a', 'text', None, _('treat all files as text')),
4298 ('a', 'text', None, _('treat all files as text')),
4299 ('g', 'git', None, _('use git extended diff format')),
4299 ('g', 'git', None, _('use git extended diff format')),
4300 ('', 'nodates', None, _('omit dates from diff headers'))
4300 ('', 'nodates', None, _('omit dates from diff headers'))
4301 ]
4301 ]
4302
4302
4303 diffopts2 = [
4303 diffopts2 = [
4304 ('p', 'show-function', None, _('show which function each change is in')),
4304 ('p', 'show-function', None, _('show which function each change is in')),
4305 ('', 'reverse', None, _('produce a diff that undoes the changes')),
4305 ('', 'reverse', None, _('produce a diff that undoes the changes')),
4306 ('w', 'ignore-all-space', None,
4306 ('w', 'ignore-all-space', None,
4307 _('ignore white space when comparing lines')),
4307 _('ignore white space when comparing lines')),
4308 ('b', 'ignore-space-change', None,
4308 ('b', 'ignore-space-change', None,
4309 _('ignore changes in the amount of white space')),
4309 _('ignore changes in the amount of white space')),
4310 ('B', 'ignore-blank-lines', None,
4310 ('B', 'ignore-blank-lines', None,
4311 _('ignore changes whose lines are all blank')),
4311 _('ignore changes whose lines are all blank')),
4312 ('U', 'unified', '',
4312 ('U', 'unified', '',
4313 _('number of lines of context to show'), _('NUM')),
4313 _('number of lines of context to show'), _('NUM')),
4314 ('', 'stat', None, _('output diffstat-style summary of changes')),
4314 ('', 'stat', None, _('output diffstat-style summary of changes')),
4315 ]
4315 ]
4316
4316
4317 similarityopts = [
4317 similarityopts = [
4318 ('s', 'similarity', '',
4318 ('s', 'similarity', '',
4319 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
4319 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
4320 ]
4320 ]
4321
4321
4322 subrepoopts = [
4322 subrepoopts = [
4323 ('S', 'subrepos', None,
4323 ('S', 'subrepos', None,
4324 _('recurse into subrepositories'))
4324 _('recurse into subrepositories'))
4325 ]
4325 ]
4326
4326
4327 table = {
4327 table = {
4328 "^add": (add, walkopts + subrepoopts + dryrunopts,
4328 "^add": (add, walkopts + subrepoopts + dryrunopts,
4329 _('[OPTION]... [FILE]...')),
4329 _('[OPTION]... [FILE]...')),
4330 "addremove":
4330 "addremove":
4331 (addremove, similarityopts + walkopts + dryrunopts,
4331 (addremove, similarityopts + walkopts + dryrunopts,
4332 _('[OPTION]... [FILE]...')),
4332 _('[OPTION]... [FILE]...')),
4333 "^annotate|blame":
4333 "^annotate|blame":
4334 (annotate,
4334 (annotate,
4335 [('r', 'rev', '',
4335 [('r', 'rev', '',
4336 _('annotate the specified revision'), _('REV')),
4336 _('annotate the specified revision'), _('REV')),
4337 ('', 'follow', None,
4337 ('', 'follow', None,
4338 _('follow copies/renames and list the filename (DEPRECATED)')),
4338 _('follow copies/renames and list the filename (DEPRECATED)')),
4339 ('', 'no-follow', None, _("don't follow copies and renames")),
4339 ('', 'no-follow', None, _("don't follow copies and renames")),
4340 ('a', 'text', None, _('treat all files as text')),
4340 ('a', 'text', None, _('treat all files as text')),
4341 ('u', 'user', None, _('list the author (long with -v)')),
4341 ('u', 'user', None, _('list the author (long with -v)')),
4342 ('f', 'file', None, _('list the filename')),
4342 ('f', 'file', None, _('list the filename')),
4343 ('d', 'date', None, _('list the date (short with -q)')),
4343 ('d', 'date', None, _('list the date (short with -q)')),
4344 ('n', 'number', None, _('list the revision number (default)')),
4344 ('n', 'number', None, _('list the revision number (default)')),
4345 ('c', 'changeset', None, _('list the changeset')),
4345 ('c', 'changeset', None, _('list the changeset')),
4346 ('l', 'line-number', None,
4346 ('l', 'line-number', None,
4347 _('show line number at the first appearance'))
4347 _('show line number at the first appearance'))
4348 ] + walkopts,
4348 ] + walkopts,
4349 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4349 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4350 "archive":
4350 "archive":
4351 (archive,
4351 (archive,
4352 [('', 'no-decode', None, _('do not pass files through decoders')),
4352 [('', 'no-decode', None, _('do not pass files through decoders')),
4353 ('p', 'prefix', '',
4353 ('p', 'prefix', '',
4354 _('directory prefix for files in archive'), _('PREFIX')),
4354 _('directory prefix for files in archive'), _('PREFIX')),
4355 ('r', 'rev', '',
4355 ('r', 'rev', '',
4356 _('revision to distribute'), _('REV')),
4356 _('revision to distribute'), _('REV')),
4357 ('t', 'type', '',
4357 ('t', 'type', '',
4358 _('type of distribution to create'), _('TYPE')),
4358 _('type of distribution to create'), _('TYPE')),
4359 ] + subrepoopts + walkopts,
4359 ] + subrepoopts + walkopts,
4360 _('[OPTION]... DEST')),
4360 _('[OPTION]... DEST')),
4361 "backout":
4361 "backout":
4362 (backout,
4362 (backout,
4363 [('', 'merge', None,
4363 [('', 'merge', None,
4364 _('merge with old dirstate parent after backout')),
4364 _('merge with old dirstate parent after backout')),
4365 ('', 'parent', '',
4365 ('', 'parent', '',
4366 _('parent to choose when backing out merge'), _('REV')),
4366 _('parent to choose when backing out merge'), _('REV')),
4367 ('t', 'tool', '',
4367 ('t', 'tool', '',
4368 _('specify merge tool')),
4368 _('specify merge tool')),
4369 ('r', 'rev', '',
4369 ('r', 'rev', '',
4370 _('revision to backout'), _('REV')),
4370 _('revision to backout'), _('REV')),
4371 ] + walkopts + commitopts + commitopts2,
4371 ] + walkopts + commitopts + commitopts2,
4372 _('[OPTION]... [-r] REV')),
4372 _('[OPTION]... [-r] REV')),
4373 "bisect":
4373 "bisect":
4374 (bisect,
4374 (bisect,
4375 [('r', 'reset', False, _('reset bisect state')),
4375 [('r', 'reset', False, _('reset bisect state')),
4376 ('g', 'good', False, _('mark changeset good')),
4376 ('g', 'good', False, _('mark changeset good')),
4377 ('b', 'bad', False, _('mark changeset bad')),
4377 ('b', 'bad', False, _('mark changeset bad')),
4378 ('s', 'skip', False, _('skip testing changeset')),
4378 ('s', 'skip', False, _('skip testing changeset')),
4379 ('e', 'extend', False, _('extend the bisect range')),
4379 ('e', 'extend', False, _('extend the bisect range')),
4380 ('c', 'command', '',
4380 ('c', 'command', '',
4381 _('use command to check changeset state'), _('CMD')),
4381 _('use command to check changeset state'), _('CMD')),
4382 ('U', 'noupdate', False, _('do not update to target'))],
4382 ('U', 'noupdate', False, _('do not update to target'))],
4383 _("[-gbsr] [-U] [-c CMD] [REV]")),
4383 _("[-gbsr] [-U] [-c CMD] [REV]")),
4384 "bookmarks":
4384 "bookmarks":
4385 (bookmark,
4385 (bookmark,
4386 [('f', 'force', False, _('force')),
4386 [('f', 'force', False, _('force')),
4387 ('r', 'rev', '', _('revision'), _('REV')),
4387 ('r', 'rev', '', _('revision'), _('REV')),
4388 ('d', 'delete', False, _('delete a given bookmark')),
4388 ('d', 'delete', False, _('delete a given bookmark')),
4389 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
4389 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
4390 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
4390 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
4391 "branch":
4391 "branch":
4392 (branch,
4392 (branch,
4393 [('f', 'force', None,
4393 [('f', 'force', None,
4394 _('set branch name even if it shadows an existing branch')),
4394 _('set branch name even if it shadows an existing branch')),
4395 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4395 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4396 _('[-fC] [NAME]')),
4396 _('[-fC] [NAME]')),
4397 "branches":
4397 "branches":
4398 (branches,
4398 (branches,
4399 [('a', 'active', False,
4399 [('a', 'active', False,
4400 _('show only branches that have unmerged heads')),
4400 _('show only branches that have unmerged heads')),
4401 ('c', 'closed', False,
4401 ('c', 'closed', False,
4402 _('show normal and closed branches'))],
4402 _('show normal and closed branches'))],
4403 _('[-ac]')),
4403 _('[-ac]')),
4404 "bundle":
4404 "bundle":
4405 (bundle,
4405 (bundle,
4406 [('f', 'force', None,
4406 [('f', 'force', None,
4407 _('run even when the destination is unrelated')),
4407 _('run even when the destination is unrelated')),
4408 ('r', 'rev', [],
4408 ('r', 'rev', [],
4409 _('a changeset intended to be added to the destination'),
4409 _('a changeset intended to be added to the destination'),
4410 _('REV')),
4410 _('REV')),
4411 ('b', 'branch', [],
4411 ('b', 'branch', [],
4412 _('a specific branch you would like to bundle'),
4412 _('a specific branch you would like to bundle'),
4413 _('BRANCH')),
4413 _('BRANCH')),
4414 ('', 'base', [],
4414 ('', 'base', [],
4415 _('a base changeset assumed to be available at the destination'),
4415 _('a base changeset assumed to be available at the destination'),
4416 _('REV')),
4416 _('REV')),
4417 ('a', 'all', None, _('bundle all changesets in the repository')),
4417 ('a', 'all', None, _('bundle all changesets in the repository')),
4418 ('t', 'type', 'bzip2',
4418 ('t', 'type', 'bzip2',
4419 _('bundle compression type to use'), _('TYPE')),
4419 _('bundle compression type to use'), _('TYPE')),
4420 ] + remoteopts,
4420 ] + remoteopts,
4421 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4421 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4422 "cat":
4422 "cat":
4423 (cat,
4423 (cat,
4424 [('o', 'output', '',
4424 [('o', 'output', '',
4425 _('print output to file with formatted name'), _('FORMAT')),
4425 _('print output to file with formatted name'), _('FORMAT')),
4426 ('r', 'rev', '',
4426 ('r', 'rev', '',
4427 _('print the given revision'), _('REV')),
4427 _('print the given revision'), _('REV')),
4428 ('', 'decode', None, _('apply any matching decode filter')),
4428 ('', 'decode', None, _('apply any matching decode filter')),
4429 ] + walkopts,
4429 ] + walkopts,
4430 _('[OPTION]... FILE...')),
4430 _('[OPTION]... FILE...')),
4431 "^clone":
4431 "^clone":
4432 (clone,
4432 (clone,
4433 [('U', 'noupdate', None,
4433 [('U', 'noupdate', None,
4434 _('the clone will include an empty working copy (only a repository)')),
4434 _('the clone will include an empty working copy (only a repository)')),
4435 ('u', 'updaterev', '',
4435 ('u', 'updaterev', '',
4436 _('revision, tag or branch to check out'), _('REV')),
4436 _('revision, tag or branch to check out'), _('REV')),
4437 ('r', 'rev', [],
4437 ('r', 'rev', [],
4438 _('include the specified changeset'), _('REV')),
4438 _('include the specified changeset'), _('REV')),
4439 ('b', 'branch', [],
4439 ('b', 'branch', [],
4440 _('clone only the specified branch'), _('BRANCH')),
4440 _('clone only the specified branch'), _('BRANCH')),
4441 ('', 'pull', None, _('use pull protocol to copy metadata')),
4441 ('', 'pull', None, _('use pull protocol to copy metadata')),
4442 ('', 'uncompressed', None,
4442 ('', 'uncompressed', None,
4443 _('use uncompressed transfer (fast over LAN)')),
4443 _('use uncompressed transfer (fast over LAN)')),
4444 ] + remoteopts,
4444 ] + remoteopts,
4445 _('[OPTION]... SOURCE [DEST]')),
4445 _('[OPTION]... SOURCE [DEST]')),
4446 "^commit|ci":
4446 "^commit|ci":
4447 (commit,
4447 (commit,
4448 [('A', 'addremove', None,
4448 [('A', 'addremove', None,
4449 _('mark new/missing files as added/removed before committing')),
4449 _('mark new/missing files as added/removed before committing')),
4450 ('', 'close-branch', None,
4450 ('', 'close-branch', None,
4451 _('mark a branch as closed, hiding it from the branch list')),
4451 _('mark a branch as closed, hiding it from the branch list')),
4452 ] + walkopts + commitopts + commitopts2,
4452 ] + walkopts + commitopts + commitopts2,
4453 _('[OPTION]... [FILE]...')),
4453 _('[OPTION]... [FILE]...')),
4454 "copy|cp":
4454 "copy|cp":
4455 (copy,
4455 (copy,
4456 [('A', 'after', None, _('record a copy that has already occurred')),
4456 [('A', 'after', None, _('record a copy that has already occurred')),
4457 ('f', 'force', None,
4457 ('f', 'force', None,
4458 _('forcibly copy over an existing managed file')),
4458 _('forcibly copy over an existing managed file')),
4459 ] + walkopts + dryrunopts,
4459 ] + walkopts + dryrunopts,
4460 _('[OPTION]... [SOURCE]... DEST')),
4460 _('[OPTION]... [SOURCE]... DEST')),
4461 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4461 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4462 "debugbuilddag":
4462 "debugbuilddag":
4463 (debugbuilddag,
4463 (debugbuilddag,
4464 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4464 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4465 ('a', 'appended-file', None, _('add single file all revs append to')),
4465 ('a', 'appended-file', None, _('add single file all revs append to')),
4466 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4466 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4467 ('n', 'new-file', None, _('add new file at each rev')),
4467 ('n', 'new-file', None, _('add new file at each rev')),
4468 ],
4468 ],
4469 _('[OPTION]... TEXT')),
4469 _('[OPTION]... TEXT')),
4470 "debugbundle":
4470 "debugbundle":
4471 (debugbundle,
4471 (debugbundle,
4472 [('a', 'all', None, _('show all details')),
4472 [('a', 'all', None, _('show all details')),
4473 ],
4473 ],
4474 _('FILE')),
4474 _('FILE')),
4475 "debugcheckstate": (debugcheckstate, [], ''),
4475 "debugcheckstate": (debugcheckstate, [], ''),
4476 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4476 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4477 "debugcomplete":
4477 "debugcomplete":
4478 (debugcomplete,
4478 (debugcomplete,
4479 [('o', 'options', None, _('show the command options'))],
4479 [('o', 'options', None, _('show the command options'))],
4480 _('[-o] CMD')),
4480 _('[-o] CMD')),
4481 "debugdag":
4481 "debugdag":
4482 (debugdag,
4482 (debugdag,
4483 [('t', 'tags', None, _('use tags as labels')),
4483 [('t', 'tags', None, _('use tags as labels')),
4484 ('b', 'branches', None, _('annotate with branch names')),
4484 ('b', 'branches', None, _('annotate with branch names')),
4485 ('', 'dots', None, _('use dots for runs')),
4485 ('', 'dots', None, _('use dots for runs')),
4486 ('s', 'spaces', None, _('separate elements by spaces')),
4486 ('s', 'spaces', None, _('separate elements by spaces')),
4487 ],
4487 ],
4488 _('[OPTION]... [FILE [REV]...]')),
4488 _('[OPTION]... [FILE [REV]...]')),
4489 "debugdate":
4489 "debugdate":
4490 (debugdate,
4490 (debugdate,
4491 [('e', 'extended', None, _('try extended date formats'))],
4491 [('e', 'extended', None, _('try extended date formats'))],
4492 _('[-e] DATE [RANGE]')),
4492 _('[-e] DATE [RANGE]')),
4493 "debugdata": (debugdata, [], _('FILE REV')),
4493 "debugdata": (debugdata, [], _('FILE REV')),
4494 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4494 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4495 "debuggetbundle":
4495 "debuggetbundle":
4496 (debuggetbundle,
4496 (debuggetbundle,
4497 [('H', 'head', [], _('id of head node'), _('ID')),
4497 [('H', 'head', [], _('id of head node'), _('ID')),
4498 ('C', 'common', [], _('id of common node'), _('ID')),
4498 ('C', 'common', [], _('id of common node'), _('ID')),
4499 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
4499 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
4500 ],
4500 ],
4501 _('REPO FILE [-H|-C ID]...')),
4501 _('REPO FILE [-H|-C ID]...')),
4502 "debugignore": (debugignore, [], ''),
4502 "debugignore": (debugignore, [], ''),
4503 "debugindex": (debugindex,
4503 "debugindex": (debugindex,
4504 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
4504 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
4505 _('FILE')),
4505 _('FILE')),
4506 "debugindexdot": (debugindexdot, [], _('FILE')),
4506 "debugindexdot": (debugindexdot, [], _('FILE')),
4507 "debuginstall": (debuginstall, [], ''),
4507 "debuginstall": (debuginstall, [], ''),
4508 "debugknown": (debugknown, [], _('REPO ID...')),
4508 "debugknown": (debugknown, [], _('REPO ID...')),
4509 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4509 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4510 "debugrebuildstate":
4510 "debugrebuildstate":
4511 (debugrebuildstate,
4511 (debugrebuildstate,
4512 [('r', 'rev', '',
4512 [('r', 'rev', '',
4513 _('revision to rebuild to'), _('REV'))],
4513 _('revision to rebuild to'), _('REV'))],
4514 _('[-r REV] [REV]')),
4514 _('[-r REV] [REV]')),
4515 "debugrename":
4515 "debugrename":
4516 (debugrename,
4516 (debugrename,
4517 [('r', 'rev', '',
4517 [('r', 'rev', '',
4518 _('revision to debug'), _('REV'))],
4518 _('revision to debug'), _('REV'))],
4519 _('[-r REV] FILE')),
4519 _('[-r REV] FILE')),
4520 "debugrevspec":
4520 "debugrevspec":
4521 (debugrevspec, [], ('REVSPEC')),
4521 (debugrevspec, [], ('REVSPEC')),
4522 "debugsetparents":
4522 "debugsetparents":
4523 (debugsetparents, [], _('REV1 [REV2]')),
4523 (debugsetparents, [], _('REV1 [REV2]')),
4524 "debugstate":
4524 "debugstate":
4525 (debugstate,
4525 (debugstate,
4526 [('', 'nodates', None, _('do not display the saved mtime')),
4526 [('', 'nodates', None, _('do not display the saved mtime')),
4527 ('', 'datesort', None, _('sort by saved mtime'))],
4527 ('', 'datesort', None, _('sort by saved mtime'))],
4528 _('[OPTION]...')),
4528 _('[OPTION]...')),
4529 "debugsub":
4529 "debugsub":
4530 (debugsub,
4530 (debugsub,
4531 [('r', 'rev', '',
4531 [('r', 'rev', '',
4532 _('revision to check'), _('REV'))],
4532 _('revision to check'), _('REV'))],
4533 _('[-r REV] [REV]')),
4533 _('[-r REV] [REV]')),
4534 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4534 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4535 "debugwireargs":
4535 "debugwireargs":
4536 (debugwireargs,
4536 (debugwireargs,
4537 [('', 'three', '', 'three'),
4537 [('', 'three', '', 'three'),
4538 ('', 'four', '', 'four'),
4538 ('', 'four', '', 'four'),
4539 ] + remoteopts,
4539 ] + remoteopts,
4540 _('REPO [OPTIONS]... [ONE [TWO]]')),
4540 _('REPO [OPTIONS]... [ONE [TWO]]')),
4541 "^diff":
4541 "^diff":
4542 (diff,
4542 (diff,
4543 [('r', 'rev', [],
4543 [('r', 'rev', [],
4544 _('revision'), _('REV')),
4544 _('revision'), _('REV')),
4545 ('c', 'change', '',
4545 ('c', 'change', '',
4546 _('change made by revision'), _('REV'))
4546 _('change made by revision'), _('REV'))
4547 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4547 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4548 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4548 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4549 "^export":
4549 "^export":
4550 (export,
4550 (export,
4551 [('o', 'output', '',
4551 [('o', 'output', '',
4552 _('print output to file with formatted name'), _('FORMAT')),
4552 _('print output to file with formatted name'), _('FORMAT')),
4553 ('', 'switch-parent', None, _('diff against the second parent')),
4553 ('', 'switch-parent', None, _('diff against the second parent')),
4554 ('r', 'rev', [],
4554 ('r', 'rev', [],
4555 _('revisions to export'), _('REV')),
4555 _('revisions to export'), _('REV')),
4556 ] + diffopts,
4556 ] + diffopts,
4557 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4557 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4558 "^forget":
4558 "^forget":
4559 (forget,
4559 (forget,
4560 [] + walkopts,
4560 [] + walkopts,
4561 _('[OPTION]... FILE...')),
4561 _('[OPTION]... FILE...')),
4562 "grep":
4562 "grep":
4563 (grep,
4563 (grep,
4564 [('0', 'print0', None, _('end fields with NUL')),
4564 [('0', 'print0', None, _('end fields with NUL')),
4565 ('', 'all', None, _('print all revisions that match')),
4565 ('', 'all', None, _('print all revisions that match')),
4566 ('f', 'follow', None,
4566 ('f', 'follow', None,
4567 _('follow changeset history,'
4567 _('follow changeset history,'
4568 ' or file history across copies and renames')),
4568 ' or file history across copies and renames')),
4569 ('i', 'ignore-case', None, _('ignore case when matching')),
4569 ('i', 'ignore-case', None, _('ignore case when matching')),
4570 ('l', 'files-with-matches', None,
4570 ('l', 'files-with-matches', None,
4571 _('print only filenames and revisions that match')),
4571 _('print only filenames and revisions that match')),
4572 ('n', 'line-number', None, _('print matching line numbers')),
4572 ('n', 'line-number', None, _('print matching line numbers')),
4573 ('r', 'rev', [],
4573 ('r', 'rev', [],
4574 _('only search files changed within revision range'), _('REV')),
4574 _('only search files changed within revision range'), _('REV')),
4575 ('u', 'user', None, _('list the author (long with -v)')),
4575 ('u', 'user', None, _('list the author (long with -v)')),
4576 ('d', 'date', None, _('list the date (short with -q)')),
4576 ('d', 'date', None, _('list the date (short with -q)')),
4577 ] + walkopts,
4577 ] + walkopts,
4578 _('[OPTION]... PATTERN [FILE]...')),
4578 _('[OPTION]... PATTERN [FILE]...')),
4579 "heads":
4579 "heads":
4580 (heads,
4580 (heads,
4581 [('r', 'rev', '',
4581 [('r', 'rev', '',
4582 _('show only heads which are descendants of STARTREV'),
4582 _('show only heads which are descendants of STARTREV'),
4583 _('STARTREV')),
4583 _('STARTREV')),
4584 ('t', 'topo', False, _('show topological heads only')),
4584 ('t', 'topo', False, _('show topological heads only')),
4585 ('a', 'active', False,
4585 ('a', 'active', False,
4586 _('show active branchheads only (DEPRECATED)')),
4586 _('show active branchheads only (DEPRECATED)')),
4587 ('c', 'closed', False,
4587 ('c', 'closed', False,
4588 _('show normal and closed branch heads')),
4588 _('show normal and closed branch heads')),
4589 ] + templateopts,
4589 ] + templateopts,
4590 _('[-ac] [-r STARTREV] [REV]...')),
4590 _('[-ac] [-r STARTREV] [REV]...')),
4591 "help": (help_, [], _('[TOPIC]')),
4591 "help": (help_, [], _('[TOPIC]')),
4592 "identify|id":
4592 "identify|id":
4593 (identify,
4593 (identify,
4594 [('r', 'rev', '',
4594 [('r', 'rev', '',
4595 _('identify the specified revision'), _('REV')),
4595 _('identify the specified revision'), _('REV')),
4596 ('n', 'num', None, _('show local revision number')),
4596 ('n', 'num', None, _('show local revision number')),
4597 ('i', 'id', None, _('show global revision id')),
4597 ('i', 'id', None, _('show global revision id')),
4598 ('b', 'branch', None, _('show branch')),
4598 ('b', 'branch', None, _('show branch')),
4599 ('t', 'tags', None, _('show tags')),
4599 ('t', 'tags', None, _('show tags')),
4600 ('B', 'bookmarks', None, _('show bookmarks'))],
4600 ('B', 'bookmarks', None, _('show bookmarks'))],
4601 _('[-nibtB] [-r REV] [SOURCE]')),
4601 _('[-nibtB] [-r REV] [SOURCE]')),
4602 "import|patch":
4602 "import|patch":
4603 (import_,
4603 (import_,
4604 [('p', 'strip', 1,
4604 [('p', 'strip', 1,
4605 _('directory strip option for patch. This has the same '
4605 _('directory strip option for patch. This has the same '
4606 'meaning as the corresponding patch option'),
4606 'meaning as the corresponding patch option'),
4607 _('NUM')),
4607 _('NUM')),
4608 ('b', 'base', '',
4608 ('b', 'base', '',
4609 _('base path'), _('PATH')),
4609 _('base path'), _('PATH')),
4610 ('f', 'force', None,
4610 ('f', 'force', None,
4611 _('skip check for outstanding uncommitted changes')),
4611 _('skip check for outstanding uncommitted changes')),
4612 ('', 'no-commit', None,
4612 ('', 'no-commit', None,
4613 _("don't commit, just update the working directory")),
4613 _("don't commit, just update the working directory")),
4614 ('', 'exact', None,
4614 ('', 'exact', None,
4615 _('apply patch to the nodes from which it was generated')),
4615 _('apply patch to the nodes from which it was generated')),
4616 ('', 'import-branch', None,
4616 ('', 'import-branch', None,
4617 _('use any branch information in patch (implied by --exact)'))] +
4617 _('use any branch information in patch (implied by --exact)'))] +
4618 commitopts + commitopts2 + similarityopts,
4618 commitopts + commitopts2 + similarityopts,
4619 _('[OPTION]... PATCH...')),
4619 _('[OPTION]... PATCH...')),
4620 "incoming|in":
4620 "incoming|in":
4621 (incoming,
4621 (incoming,
4622 [('f', 'force', None,
4622 [('f', 'force', None,
4623 _('run even if remote repository is unrelated')),
4623 _('run even if remote repository is unrelated')),
4624 ('n', 'newest-first', None, _('show newest record first')),
4624 ('n', 'newest-first', None, _('show newest record first')),
4625 ('', 'bundle', '',
4625 ('', 'bundle', '',
4626 _('file to store the bundles into'), _('FILE')),
4626 _('file to store the bundles into'), _('FILE')),
4627 ('r', 'rev', [],
4627 ('r', 'rev', [],
4628 _('a remote changeset intended to be added'), _('REV')),
4628 _('a remote changeset intended to be added'), _('REV')),
4629 ('B', 'bookmarks', False, _("compare bookmarks")),
4629 ('B', 'bookmarks', False, _("compare bookmarks")),
4630 ('b', 'branch', [],
4630 ('b', 'branch', [],
4631 _('a specific branch you would like to pull'), _('BRANCH')),
4631 _('a specific branch you would like to pull'), _('BRANCH')),
4632 ] + logopts + remoteopts + subrepoopts,
4632 ] + logopts + remoteopts + subrepoopts,
4633 _('[-p] [-n] [-M] [-f] [-r REV]...'
4633 _('[-p] [-n] [-M] [-f] [-r REV]...'
4634 ' [--bundle FILENAME] [SOURCE]')),
4634 ' [--bundle FILENAME] [SOURCE]')),
4635 "^init":
4635 "^init":
4636 (init,
4636 (init,
4637 remoteopts,
4637 remoteopts,
4638 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4638 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4639 "locate":
4639 "locate":
4640 (locate,
4640 (locate,
4641 [('r', 'rev', '',
4641 [('r', 'rev', '',
4642 _('search the repository as it is in REV'), _('REV')),
4642 _('search the repository as it is in REV'), _('REV')),
4643 ('0', 'print0', None,
4643 ('0', 'print0', None,
4644 _('end filenames with NUL, for use with xargs')),
4644 _('end filenames with NUL, for use with xargs')),
4645 ('f', 'fullpath', None,
4645 ('f', 'fullpath', None,
4646 _('print complete paths from the filesystem root')),
4646 _('print complete paths from the filesystem root')),
4647 ] + walkopts,
4647 ] + walkopts,
4648 _('[OPTION]... [PATTERN]...')),
4648 _('[OPTION]... [PATTERN]...')),
4649 "^log|history":
4649 "^log|history":
4650 (log,
4650 (log,
4651 [('f', 'follow', None,
4651 [('f', 'follow', None,
4652 _('follow changeset history,'
4652 _('follow changeset history,'
4653 ' or file history across copies and renames')),
4653 ' or file history across copies and renames')),
4654 ('', 'follow-first', None,
4654 ('', 'follow-first', None,
4655 _('only follow the first parent of merge changesets')),
4655 _('only follow the first parent of merge changesets')),
4656 ('d', 'date', '',
4656 ('d', 'date', '',
4657 _('show revisions matching date spec'), _('DATE')),
4657 _('show revisions matching date spec'), _('DATE')),
4658 ('C', 'copies', None, _('show copied files')),
4658 ('C', 'copies', None, _('show copied files')),
4659 ('k', 'keyword', [],
4659 ('k', 'keyword', [],
4660 _('do case-insensitive search for a given text'), _('TEXT')),
4660 _('do case-insensitive search for a given text'), _('TEXT')),
4661 ('r', 'rev', [],
4661 ('r', 'rev', [],
4662 _('show the specified revision or range'), _('REV')),
4662 _('show the specified revision or range'), _('REV')),
4663 ('', 'removed', None, _('include revisions where files were removed')),
4663 ('', 'removed', None, _('include revisions where files were removed')),
4664 ('m', 'only-merges', None, _('show only merges')),
4664 ('m', 'only-merges', None, _('show only merges')),
4665 ('u', 'user', [],
4665 ('u', 'user', [],
4666 _('revisions committed by user'), _('USER')),
4666 _('revisions committed by user'), _('USER')),
4667 ('', 'only-branch', [],
4667 ('', 'only-branch', [],
4668 _('show only changesets within the given named branch (DEPRECATED)'),
4668 _('show only changesets within the given named branch (DEPRECATED)'),
4669 _('BRANCH')),
4669 _('BRANCH')),
4670 ('b', 'branch', [],
4670 ('b', 'branch', [],
4671 _('show changesets within the given named branch'), _('BRANCH')),
4671 _('show changesets within the given named branch'), _('BRANCH')),
4672 ('P', 'prune', [],
4672 ('P', 'prune', [],
4673 _('do not display revision or any of its ancestors'), _('REV')),
4673 _('do not display revision or any of its ancestors'), _('REV')),
4674 ] + logopts + walkopts,
4674 ] + logopts + walkopts,
4675 _('[OPTION]... [FILE]')),
4675 _('[OPTION]... [FILE]')),
4676 "manifest":
4676 "manifest":
4677 (manifest,
4677 (manifest,
4678 [('r', 'rev', '',
4678 [('r', 'rev', '',
4679 _('revision to display'), _('REV'))],
4679 _('revision to display'), _('REV'))],
4680 _('[-r REV]')),
4680 _('[-r REV]')),
4681 "^merge":
4681 "^merge":
4682 (merge,
4682 (merge,
4683 [('f', 'force', None, _('force a merge with outstanding changes')),
4683 [('f', 'force', None, _('force a merge with outstanding changes')),
4684 ('t', 'tool', '', _('specify merge tool')),
4684 ('t', 'tool', '', _('specify merge tool')),
4685 ('r', 'rev', '',
4685 ('r', 'rev', '',
4686 _('revision to merge'), _('REV')),
4686 _('revision to merge'), _('REV')),
4687 ('P', 'preview', None,
4687 ('P', 'preview', None,
4688 _('review revisions to merge (no merge is performed)'))],
4688 _('review revisions to merge (no merge is performed)'))],
4689 _('[-P] [-f] [[-r] REV]')),
4689 _('[-P] [-f] [[-r] REV]')),
4690 "outgoing|out":
4690 "outgoing|out":
4691 (outgoing,
4691 (outgoing,
4692 [('f', 'force', None,
4692 [('f', 'force', None,
4693 _('run even when the destination is unrelated')),
4693 _('run even when the destination is unrelated')),
4694 ('r', 'rev', [],
4694 ('r', 'rev', [],
4695 _('a changeset intended to be included in the destination'),
4695 _('a changeset intended to be included in the destination'),
4696 _('REV')),
4696 _('REV')),
4697 ('n', 'newest-first', None, _('show newest record first')),
4697 ('n', 'newest-first', None, _('show newest record first')),
4698 ('B', 'bookmarks', False, _("compare bookmarks")),
4698 ('B', 'bookmarks', False, _("compare bookmarks")),
4699 ('b', 'branch', [],
4699 ('b', 'branch', [],
4700 _('a specific branch you would like to push'), _('BRANCH')),
4700 _('a specific branch you would like to push'), _('BRANCH')),
4701 ] + logopts + remoteopts + subrepoopts,
4701 ] + logopts + remoteopts + subrepoopts,
4702 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4702 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4703 "parents":
4703 "parents":
4704 (parents,
4704 (parents,
4705 [('r', 'rev', '',
4705 [('r', 'rev', '',
4706 _('show parents of the specified revision'), _('REV')),
4706 _('show parents of the specified revision'), _('REV')),
4707 ] + templateopts,
4707 ] + templateopts,
4708 _('[-r REV] [FILE]')),
4708 _('[-r REV] [FILE]')),
4709 "paths": (paths, [], _('[NAME]')),
4709 "paths": (paths, [], _('[NAME]')),
4710 "^pull":
4710 "^pull":
4711 (pull,
4711 (pull,
4712 [('u', 'update', None,
4712 [('u', 'update', None,
4713 _('update to new branch head if changesets were pulled')),
4713 _('update to new branch head if changesets were pulled')),
4714 ('f', 'force', None,
4714 ('f', 'force', None,
4715 _('run even when remote repository is unrelated')),
4715 _('run even when remote repository is unrelated')),
4716 ('r', 'rev', [],
4716 ('r', 'rev', [],
4717 _('a remote changeset intended to be added'), _('REV')),
4717 _('a remote changeset intended to be added'), _('REV')),
4718 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4718 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4719 ('b', 'branch', [],
4719 ('b', 'branch', [],
4720 _('a specific branch you would like to pull'), _('BRANCH')),
4720 _('a specific branch you would like to pull'), _('BRANCH')),
4721 ] + remoteopts,
4721 ] + remoteopts,
4722 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4722 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4723 "^push":
4723 "^push":
4724 (push,
4724 (push,
4725 [('f', 'force', None, _('force push')),
4725 [('f', 'force', None, _('force push')),
4726 ('r', 'rev', [],
4726 ('r', 'rev', [],
4727 _('a changeset intended to be included in the destination'),
4727 _('a changeset intended to be included in the destination'),
4728 _('REV')),
4728 _('REV')),
4729 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4729 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4730 ('b', 'branch', [],
4730 ('b', 'branch', [],
4731 _('a specific branch you would like to push'), _('BRANCH')),
4731 _('a specific branch you would like to push'), _('BRANCH')),
4732 ('', 'new-branch', False, _('allow pushing a new branch')),
4732 ('', 'new-branch', False, _('allow pushing a new branch')),
4733 ] + remoteopts,
4733 ] + remoteopts,
4734 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4734 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4735 "recover": (recover, []),
4735 "recover": (recover, []),
4736 "^remove|rm":
4736 "^remove|rm":
4737 (remove,
4737 (remove,
4738 [('A', 'after', None, _('record delete for missing files')),
4738 [('A', 'after', None, _('record delete for missing files')),
4739 ('f', 'force', None,
4739 ('f', 'force', None,
4740 _('remove (and delete) file even if added or modified')),
4740 _('remove (and delete) file even if added or modified')),
4741 ] + walkopts,
4741 ] + walkopts,
4742 _('[OPTION]... FILE...')),
4742 _('[OPTION]... FILE...')),
4743 "rename|move|mv":
4743 "rename|move|mv":
4744 (rename,
4744 (rename,
4745 [('A', 'after', None, _('record a rename that has already occurred')),
4745 [('A', 'after', None, _('record a rename that has already occurred')),
4746 ('f', 'force', None,
4746 ('f', 'force', None,
4747 _('forcibly copy over an existing managed file')),
4747 _('forcibly copy over an existing managed file')),
4748 ] + walkopts + dryrunopts,
4748 ] + walkopts + dryrunopts,
4749 _('[OPTION]... SOURCE... DEST')),
4749 _('[OPTION]... SOURCE... DEST')),
4750 "resolve":
4750 "resolve":
4751 (resolve,
4751 (resolve,
4752 [('a', 'all', None, _('select all unresolved files')),
4752 [('a', 'all', None, _('select all unresolved files')),
4753 ('l', 'list', None, _('list state of files needing merge')),
4753 ('l', 'list', None, _('list state of files needing merge')),
4754 ('m', 'mark', None, _('mark files as resolved')),
4754 ('m', 'mark', None, _('mark files as resolved')),
4755 ('u', 'unmark', None, _('mark files as unresolved')),
4755 ('u', 'unmark', None, _('mark files as unresolved')),
4756 ('t', 'tool', '', _('specify merge tool')),
4756 ('t', 'tool', '', _('specify merge tool')),
4757 ('n', 'no-status', None, _('hide status prefix'))]
4757 ('n', 'no-status', None, _('hide status prefix'))]
4758 + walkopts,
4758 + walkopts,
4759 _('[OPTION]... [FILE]...')),
4759 _('[OPTION]... [FILE]...')),
4760 "revert":
4760 "revert":
4761 (revert,
4761 (revert,
4762 [('a', 'all', None, _('revert all changes when no arguments given')),
4762 [('a', 'all', None, _('revert all changes when no arguments given')),
4763 ('d', 'date', '',
4763 ('d', 'date', '',
4764 _('tipmost revision matching date'), _('DATE')),
4764 _('tipmost revision matching date'), _('DATE')),
4765 ('r', 'rev', '',
4765 ('r', 'rev', '',
4766 _('revert to the specified revision'), _('REV')),
4766 _('revert to the specified revision'), _('REV')),
4767 ('', 'no-backup', None, _('do not save backup copies of files')),
4767 ('', 'no-backup', None, _('do not save backup copies of files')),
4768 ] + walkopts + dryrunopts,
4768 ] + walkopts + dryrunopts,
4769 _('[OPTION]... [-r REV] [NAME]...')),
4769 _('[OPTION]... [-r REV] [NAME]...')),
4770 "rollback": (rollback, dryrunopts),
4770 "rollback": (rollback, dryrunopts),
4771 "root": (root, []),
4771 "root": (root, []),
4772 "^serve":
4772 "^serve":
4773 (serve,
4773 (serve,
4774 [('A', 'accesslog', '',
4774 [('A', 'accesslog', '',
4775 _('name of access log file to write to'), _('FILE')),
4775 _('name of access log file to write to'), _('FILE')),
4776 ('d', 'daemon', None, _('run server in background')),
4776 ('d', 'daemon', None, _('run server in background')),
4777 ('', 'daemon-pipefds', '',
4777 ('', 'daemon-pipefds', '',
4778 _('used internally by daemon mode'), _('NUM')),
4778 _('used internally by daemon mode'), _('NUM')),
4779 ('E', 'errorlog', '',
4779 ('E', 'errorlog', '',
4780 _('name of error log file to write to'), _('FILE')),
4780 _('name of error log file to write to'), _('FILE')),
4781 # use string type, then we can check if something was passed
4781 # use string type, then we can check if something was passed
4782 ('p', 'port', '',
4782 ('p', 'port', '',
4783 _('port to listen on (default: 8000)'), _('PORT')),
4783 _('port to listen on (default: 8000)'), _('PORT')),
4784 ('a', 'address', '',
4784 ('a', 'address', '',
4785 _('address to listen on (default: all interfaces)'), _('ADDR')),
4785 _('address to listen on (default: all interfaces)'), _('ADDR')),
4786 ('', 'prefix', '',
4786 ('', 'prefix', '',
4787 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4787 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4788 ('n', 'name', '',
4788 ('n', 'name', '',
4789 _('name to show in web pages (default: working directory)'),
4789 _('name to show in web pages (default: working directory)'),
4790 _('NAME')),
4790 _('NAME')),
4791 ('', 'web-conf', '',
4791 ('', 'web-conf', '',
4792 _('name of the hgweb config file (see "hg help hgweb")'),
4792 _('name of the hgweb config file (see "hg help hgweb")'),
4793 _('FILE')),
4793 _('FILE')),
4794 ('', 'webdir-conf', '',
4794 ('', 'webdir-conf', '',
4795 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4795 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4796 ('', 'pid-file', '',
4796 ('', 'pid-file', '',
4797 _('name of file to write process ID to'), _('FILE')),
4797 _('name of file to write process ID to'), _('FILE')),
4798 ('', 'stdio', None, _('for remote clients')),
4798 ('', 'stdio', None, _('for remote clients')),
4799 ('t', 'templates', '',
4799 ('t', 'templates', '',
4800 _('web templates to use'), _('TEMPLATE')),
4800 _('web templates to use'), _('TEMPLATE')),
4801 ('', 'style', '',
4801 ('', 'style', '',
4802 _('template style to use'), _('STYLE')),
4802 _('template style to use'), _('STYLE')),
4803 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4803 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4804 ('', 'certificate', '',
4804 ('', 'certificate', '',
4805 _('SSL certificate file'), _('FILE'))],
4805 _('SSL certificate file'), _('FILE'))],
4806 _('[OPTION]...')),
4806 _('[OPTION]...')),
4807 "showconfig|debugconfig":
4807 "showconfig|debugconfig":
4808 (showconfig,
4808 (showconfig,
4809 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4809 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4810 _('[-u] [NAME]...')),
4810 _('[-u] [NAME]...')),
4811 "^summary|sum":
4811 "^summary|sum":
4812 (summary,
4812 (summary,
4813 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4813 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4814 "^status|st":
4814 "^status|st":
4815 (status,
4815 (status,
4816 [('A', 'all', None, _('show status of all files')),
4816 [('A', 'all', None, _('show status of all files')),
4817 ('m', 'modified', None, _('show only modified files')),
4817 ('m', 'modified', None, _('show only modified files')),
4818 ('a', 'added', None, _('show only added files')),
4818 ('a', 'added', None, _('show only added files')),
4819 ('r', 'removed', None, _('show only removed files')),
4819 ('r', 'removed', None, _('show only removed files')),
4820 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4820 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4821 ('c', 'clean', None, _('show only files without changes')),
4821 ('c', 'clean', None, _('show only files without changes')),
4822 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4822 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4823 ('i', 'ignored', None, _('show only ignored files')),
4823 ('i', 'ignored', None, _('show only ignored files')),
4824 ('n', 'no-status', None, _('hide status prefix')),
4824 ('n', 'no-status', None, _('hide status prefix')),
4825 ('C', 'copies', None, _('show source of copied files')),
4825 ('C', 'copies', None, _('show source of copied files')),
4826 ('0', 'print0', None,
4826 ('0', 'print0', None,
4827 _('end filenames with NUL, for use with xargs')),
4827 _('end filenames with NUL, for use with xargs')),
4828 ('', 'rev', [],
4828 ('', 'rev', [],
4829 _('show difference from revision'), _('REV')),
4829 _('show difference from revision'), _('REV')),
4830 ('', 'change', '',
4830 ('', 'change', '',
4831 _('list the changed files of a revision'), _('REV')),
4831 _('list the changed files of a revision'), _('REV')),
4832 ] + walkopts + subrepoopts,
4832 ] + walkopts + subrepoopts,
4833 _('[OPTION]... [FILE]...')),
4833 _('[OPTION]... [FILE]...')),
4834 "tag":
4834 "tag":
4835 (tag,
4835 (tag,
4836 [('f', 'force', None, _('force tag')),
4836 [('f', 'force', None, _('force tag')),
4837 ('l', 'local', None, _('make the tag local')),
4837 ('l', 'local', None, _('make the tag local')),
4838 ('r', 'rev', '',
4838 ('r', 'rev', '',
4839 _('revision to tag'), _('REV')),
4839 _('revision to tag'), _('REV')),
4840 ('', 'remove', None, _('remove a tag')),
4840 ('', 'remove', None, _('remove a tag')),
4841 # -l/--local is already there, commitopts cannot be used
4841 # -l/--local is already there, commitopts cannot be used
4842 ('e', 'edit', None, _('edit commit message')),
4842 ('e', 'edit', None, _('edit commit message')),
4843 ('m', 'message', '',
4843 ('m', 'message', '',
4844 _('use <text> as commit message'), _('TEXT')),
4844 _('use <text> as commit message'), _('TEXT')),
4845 ] + commitopts2,
4845 ] + commitopts2,
4846 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4846 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4847 "tags": (tags, [], ''),
4847 "tags": (tags, [], ''),
4848 "tip":
4848 "tip":
4849 (tip,
4849 (tip,
4850 [('p', 'patch', None, _('show patch')),
4850 [('p', 'patch', None, _('show patch')),
4851 ('g', 'git', None, _('use git extended diff format')),
4851 ('g', 'git', None, _('use git extended diff format')),
4852 ] + templateopts,
4852 ] + templateopts,
4853 _('[-p] [-g]')),
4853 _('[-p] [-g]')),
4854 "unbundle":
4854 "unbundle":
4855 (unbundle,
4855 (unbundle,
4856 [('u', 'update', None,
4856 [('u', 'update', None,
4857 _('update to new branch head if changesets were unbundled'))],
4857 _('update to new branch head if changesets were unbundled'))],
4858 _('[-u] FILE...')),
4858 _('[-u] FILE...')),
4859 "^update|up|checkout|co":
4859 "^update|up|checkout|co":
4860 (update,
4860 (update,
4861 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4861 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4862 ('c', 'check', None,
4862 ('c', 'check', None,
4863 _('update across branches if no uncommitted changes')),
4863 _('update across branches if no uncommitted changes')),
4864 ('d', 'date', '',
4864 ('d', 'date', '',
4865 _('tipmost revision matching date'), _('DATE')),
4865 _('tipmost revision matching date'), _('DATE')),
4866 ('r', 'rev', '',
4866 ('r', 'rev', '',
4867 _('revision'), _('REV'))],
4867 _('revision'), _('REV'))],
4868 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4868 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4869 "verify": (verify, []),
4869 "verify": (verify, []),
4870 "version": (version_, []),
4870 "version": (version_, []),
4871 }
4871 }
4872
4872
4873 norepo = ("clone init version help debugcommands debugcomplete"
4873 norepo = ("clone init version help debugcommands debugcomplete"
4874 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
4874 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
4875 " debugknown debuggetbundle debugbundle")
4875 " debugknown debuggetbundle debugbundle")
4876 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4876 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4877 " debugdata debugindex debugindexdot")
4877 " debugdata debugindex debugindexdot")
@@ -1,267 +1,267 b''
1 # copies.py - copy detection for Mercurial
1 # copies.py - copy detection for Mercurial
2 #
2 #
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2008 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 import util
8 import util
9 import heapq
9 import heapq
10
10
11 def _nonoverlap(d1, d2, d3):
11 def _nonoverlap(d1, d2, d3):
12 "Return list of elements in d1 not in d2 or d3"
12 "Return list of elements in d1 not in d2 or d3"
13 return sorted([d for d in d1 if d not in d3 and d not in d2])
13 return sorted([d for d in d1 if d not in d3 and d not in d2])
14
14
15 def _dirname(f):
15 def _dirname(f):
16 s = f.rfind("/")
16 s = f.rfind("/")
17 if s == -1:
17 if s == -1:
18 return ""
18 return ""
19 return f[:s]
19 return f[:s]
20
20
21 def _dirs(files):
21 def _dirs(files):
22 d = set()
22 d = set()
23 for f in files:
23 for f in files:
24 f = _dirname(f)
24 f = _dirname(f)
25 while f not in d:
25 while f not in d:
26 d.add(f)
26 d.add(f)
27 f = _dirname(f)
27 f = _dirname(f)
28 return d
28 return d
29
29
30 def _findlimit(repo, a, b):
30 def _findlimit(repo, a, b):
31 """Find the earliest revision that's an ancestor of a or b but not both,
31 """Find the earliest revision that's an ancestor of a or b but not both,
32 None if no such revision exists.
32 None if no such revision exists.
33 """
33 """
34 # basic idea:
34 # basic idea:
35 # - mark a and b with different sides
35 # - mark a and b with different sides
36 # - if a parent's children are all on the same side, the parent is
36 # - if a parent's children are all on the same side, the parent is
37 # on that side, otherwise it is on no side
37 # on that side, otherwise it is on no side
38 # - walk the graph in topological order with the help of a heap;
38 # - walk the graph in topological order with the help of a heap;
39 # - add unseen parents to side map
39 # - add unseen parents to side map
40 # - clear side of any parent that has children on different sides
40 # - clear side of any parent that has children on different sides
41 # - track number of interesting revs that might still be on a side
41 # - track number of interesting revs that might still be on a side
42 # - track the lowest interesting rev seen
42 # - track the lowest interesting rev seen
43 # - quit when interesting revs is zero
43 # - quit when interesting revs is zero
44
44
45 cl = repo.changelog
45 cl = repo.changelog
46 working = len(cl) # pseudo rev for the working directory
46 working = len(cl) # pseudo rev for the working directory
47 if a is None:
47 if a is None:
48 a = working
48 a = working
49 if b is None:
49 if b is None:
50 b = working
50 b = working
51
51
52 side = {a: -1, b: 1}
52 side = {a: -1, b: 1}
53 visit = [-a, -b]
53 visit = [-a, -b]
54 heapq.heapify(visit)
54 heapq.heapify(visit)
55 interesting = len(visit)
55 interesting = len(visit)
56 hascommonancestor = False
56 hascommonancestor = False
57 limit = working
57 limit = working
58
58
59 while interesting:
59 while interesting:
60 r = -heapq.heappop(visit)
60 r = -heapq.heappop(visit)
61 if r == working:
61 if r == working:
62 parents = [cl.rev(p) for p in repo.dirstate.parents()]
62 parents = [cl.rev(p) for p in repo.dirstate.parents()]
63 else:
63 else:
64 parents = cl.parentrevs(r)
64 parents = cl.parentrevs(r)
65 for p in parents:
65 for p in parents:
66 if p < 0:
66 if p < 0:
67 continue
67 continue
68 if p not in side:
68 if p not in side:
69 # first time we see p; add it to visit
69 # first time we see p; add it to visit
70 side[p] = side[r]
70 side[p] = side[r]
71 if side[p]:
71 if side[p]:
72 interesting += 1
72 interesting += 1
73 heapq.heappush(visit, -p)
73 heapq.heappush(visit, -p)
74 elif side[p] and side[p] != side[r]:
74 elif side[p] and side[p] != side[r]:
75 # p was interesting but now we know better
75 # p was interesting but now we know better
76 side[p] = 0
76 side[p] = 0
77 interesting -= 1
77 interesting -= 1
78 hascommonancestor = True
78 hascommonancestor = True
79 if side[r]:
79 if side[r]:
80 limit = r # lowest rev visited
80 limit = r # lowest rev visited
81 interesting -= 1
81 interesting -= 1
82
82
83 if not hascommonancestor:
83 if not hascommonancestor:
84 return None
84 return None
85 return limit
85 return limit
86
86
87 def copies(repo, c1, c2, ca, checkdirs=False):
87 def copies(repo, c1, c2, ca, checkdirs=False):
88 """
88 """
89 Find moves and copies between context c1 and c2
89 Find moves and copies between context c1 and c2
90 """
90 """
91 # avoid silly behavior for update from empty dir
91 # avoid silly behavior for update from empty dir
92 if not c1 or not c2 or c1 == c2:
92 if not c1 or not c2 or c1 == c2:
93 return {}, {}
93 return {}, {}
94
94
95 # avoid silly behavior for parent -> working dir
95 # avoid silly behavior for parent -> working dir
96 if c2.node() is None and c1.node() == repo.dirstate.parents()[0]:
96 if c2.node() is None and c1.node() == repo.dirstate.p1():
97 return repo.dirstate.copies(), {}
97 return repo.dirstate.copies(), {}
98
98
99 limit = _findlimit(repo, c1.rev(), c2.rev())
99 limit = _findlimit(repo, c1.rev(), c2.rev())
100 if limit is None:
100 if limit is None:
101 # no common ancestor, no copies
101 # no common ancestor, no copies
102 return {}, {}
102 return {}, {}
103 m1 = c1.manifest()
103 m1 = c1.manifest()
104 m2 = c2.manifest()
104 m2 = c2.manifest()
105 ma = ca.manifest()
105 ma = ca.manifest()
106
106
107 def makectx(f, n):
107 def makectx(f, n):
108 if len(n) != 20: # in a working context?
108 if len(n) != 20: # in a working context?
109 if c1.rev() is None:
109 if c1.rev() is None:
110 return c1.filectx(f)
110 return c1.filectx(f)
111 return c2.filectx(f)
111 return c2.filectx(f)
112 return repo.filectx(f, fileid=n)
112 return repo.filectx(f, fileid=n)
113
113
114 ctx = util.lrucachefunc(makectx)
114 ctx = util.lrucachefunc(makectx)
115 copy = {}
115 copy = {}
116 fullcopy = {}
116 fullcopy = {}
117 diverge = {}
117 diverge = {}
118
118
119 def related(f1, f2, limit):
119 def related(f1, f2, limit):
120 # Walk back to common ancestor to see if the two files originate
120 # Walk back to common ancestor to see if the two files originate
121 # from the same file. Since workingfilectx's rev() is None it messes
121 # from the same file. Since workingfilectx's rev() is None it messes
122 # up the integer comparison logic, hence the pre-step check for
122 # up the integer comparison logic, hence the pre-step check for
123 # None (f1 and f2 can only be workingfilectx's initially).
123 # None (f1 and f2 can only be workingfilectx's initially).
124
124
125 if f1 == f2:
125 if f1 == f2:
126 return f1 # a match
126 return f1 # a match
127
127
128 g1, g2 = f1.ancestors(), f2.ancestors()
128 g1, g2 = f1.ancestors(), f2.ancestors()
129 try:
129 try:
130 f1r, f2r = f1.rev(), f2.rev()
130 f1r, f2r = f1.rev(), f2.rev()
131
131
132 if f1r is None:
132 if f1r is None:
133 f1 = g1.next()
133 f1 = g1.next()
134 if f2r is None:
134 if f2r is None:
135 f2 = g2.next()
135 f2 = g2.next()
136
136
137 while 1:
137 while 1:
138 f1r, f2r = f1.rev(), f2.rev()
138 f1r, f2r = f1.rev(), f2.rev()
139 if f1r > f2r:
139 if f1r > f2r:
140 f1 = g1.next()
140 f1 = g1.next()
141 elif f2r > f1r:
141 elif f2r > f1r:
142 f2 = g2.next()
142 f2 = g2.next()
143 elif f1 == f2:
143 elif f1 == f2:
144 return f1 # a match
144 return f1 # a match
145 elif f1r == f2r or f1r < limit or f2r < limit:
145 elif f1r == f2r or f1r < limit or f2r < limit:
146 return False # copy no longer relevant
146 return False # copy no longer relevant
147 except StopIteration:
147 except StopIteration:
148 return False
148 return False
149
149
150 def checkcopies(f, m1, m2):
150 def checkcopies(f, m1, m2):
151 '''check possible copies of f from m1 to m2'''
151 '''check possible copies of f from m1 to m2'''
152 of = None
152 of = None
153 seen = set([f])
153 seen = set([f])
154 for oc in ctx(f, m1[f]).ancestors():
154 for oc in ctx(f, m1[f]).ancestors():
155 ocr = oc.rev()
155 ocr = oc.rev()
156 of = oc.path()
156 of = oc.path()
157 if of in seen:
157 if of in seen:
158 # check limit late - grab last rename before
158 # check limit late - grab last rename before
159 if ocr < limit:
159 if ocr < limit:
160 break
160 break
161 continue
161 continue
162 seen.add(of)
162 seen.add(of)
163
163
164 fullcopy[f] = of # remember for dir rename detection
164 fullcopy[f] = of # remember for dir rename detection
165 if of not in m2:
165 if of not in m2:
166 continue # no match, keep looking
166 continue # no match, keep looking
167 if m2[of] == ma.get(of):
167 if m2[of] == ma.get(of):
168 break # no merge needed, quit early
168 break # no merge needed, quit early
169 c2 = ctx(of, m2[of])
169 c2 = ctx(of, m2[of])
170 cr = related(oc, c2, ca.rev())
170 cr = related(oc, c2, ca.rev())
171 if cr and (of == f or of == c2.path()): # non-divergent
171 if cr and (of == f or of == c2.path()): # non-divergent
172 copy[f] = of
172 copy[f] = of
173 of = None
173 of = None
174 break
174 break
175
175
176 if of in ma:
176 if of in ma:
177 diverge.setdefault(of, []).append(f)
177 diverge.setdefault(of, []).append(f)
178
178
179 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
179 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
180
180
181 u1 = _nonoverlap(m1, m2, ma)
181 u1 = _nonoverlap(m1, m2, ma)
182 u2 = _nonoverlap(m2, m1, ma)
182 u2 = _nonoverlap(m2, m1, ma)
183
183
184 if u1:
184 if u1:
185 repo.ui.debug(" unmatched files in local:\n %s\n"
185 repo.ui.debug(" unmatched files in local:\n %s\n"
186 % "\n ".join(u1))
186 % "\n ".join(u1))
187 if u2:
187 if u2:
188 repo.ui.debug(" unmatched files in other:\n %s\n"
188 repo.ui.debug(" unmatched files in other:\n %s\n"
189 % "\n ".join(u2))
189 % "\n ".join(u2))
190
190
191 for f in u1:
191 for f in u1:
192 checkcopies(f, m1, m2)
192 checkcopies(f, m1, m2)
193 for f in u2:
193 for f in u2:
194 checkcopies(f, m2, m1)
194 checkcopies(f, m2, m1)
195
195
196 diverge2 = set()
196 diverge2 = set()
197 for of, fl in diverge.items():
197 for of, fl in diverge.items():
198 if len(fl) == 1 or of in c2:
198 if len(fl) == 1 or of in c2:
199 del diverge[of] # not actually divergent, or not a rename
199 del diverge[of] # not actually divergent, or not a rename
200 else:
200 else:
201 diverge2.update(fl) # reverse map for below
201 diverge2.update(fl) # reverse map for below
202
202
203 if fullcopy:
203 if fullcopy:
204 repo.ui.debug(" all copies found (* = to merge, ! = divergent):\n")
204 repo.ui.debug(" all copies found (* = to merge, ! = divergent):\n")
205 for f in fullcopy:
205 for f in fullcopy:
206 note = ""
206 note = ""
207 if f in copy:
207 if f in copy:
208 note += "*"
208 note += "*"
209 if f in diverge2:
209 if f in diverge2:
210 note += "!"
210 note += "!"
211 repo.ui.debug(" %s -> %s %s\n" % (f, fullcopy[f], note))
211 repo.ui.debug(" %s -> %s %s\n" % (f, fullcopy[f], note))
212 del diverge2
212 del diverge2
213
213
214 if not fullcopy or not checkdirs:
214 if not fullcopy or not checkdirs:
215 return copy, diverge
215 return copy, diverge
216
216
217 repo.ui.debug(" checking for directory renames\n")
217 repo.ui.debug(" checking for directory renames\n")
218
218
219 # generate a directory move map
219 # generate a directory move map
220 d1, d2 = _dirs(m1), _dirs(m2)
220 d1, d2 = _dirs(m1), _dirs(m2)
221 invalid = set()
221 invalid = set()
222 dirmove = {}
222 dirmove = {}
223
223
224 # examine each file copy for a potential directory move, which is
224 # examine each file copy for a potential directory move, which is
225 # when all the files in a directory are moved to a new directory
225 # when all the files in a directory are moved to a new directory
226 for dst, src in fullcopy.iteritems():
226 for dst, src in fullcopy.iteritems():
227 dsrc, ddst = _dirname(src), _dirname(dst)
227 dsrc, ddst = _dirname(src), _dirname(dst)
228 if dsrc in invalid:
228 if dsrc in invalid:
229 # already seen to be uninteresting
229 # already seen to be uninteresting
230 continue
230 continue
231 elif dsrc in d1 and ddst in d1:
231 elif dsrc in d1 and ddst in d1:
232 # directory wasn't entirely moved locally
232 # directory wasn't entirely moved locally
233 invalid.add(dsrc)
233 invalid.add(dsrc)
234 elif dsrc in d2 and ddst in d2:
234 elif dsrc in d2 and ddst in d2:
235 # directory wasn't entirely moved remotely
235 # directory wasn't entirely moved remotely
236 invalid.add(dsrc)
236 invalid.add(dsrc)
237 elif dsrc in dirmove and dirmove[dsrc] != ddst:
237 elif dsrc in dirmove and dirmove[dsrc] != ddst:
238 # files from the same directory moved to two different places
238 # files from the same directory moved to two different places
239 invalid.add(dsrc)
239 invalid.add(dsrc)
240 else:
240 else:
241 # looks good so far
241 # looks good so far
242 dirmove[dsrc + "/"] = ddst + "/"
242 dirmove[dsrc + "/"] = ddst + "/"
243
243
244 for i in invalid:
244 for i in invalid:
245 if i in dirmove:
245 if i in dirmove:
246 del dirmove[i]
246 del dirmove[i]
247 del d1, d2, invalid
247 del d1, d2, invalid
248
248
249 if not dirmove:
249 if not dirmove:
250 return copy, diverge
250 return copy, diverge
251
251
252 for d in dirmove:
252 for d in dirmove:
253 repo.ui.debug(" dir %s -> %s\n" % (d, dirmove[d]))
253 repo.ui.debug(" dir %s -> %s\n" % (d, dirmove[d]))
254
254
255 # check unaccounted nonoverlapping files against directory moves
255 # check unaccounted nonoverlapping files against directory moves
256 for f in u1 + u2:
256 for f in u1 + u2:
257 if f not in fullcopy:
257 if f not in fullcopy:
258 for d in dirmove:
258 for d in dirmove:
259 if f.startswith(d):
259 if f.startswith(d):
260 # new file added in a directory that was moved, move it
260 # new file added in a directory that was moved, move it
261 df = dirmove[d] + f[len(d):]
261 df = dirmove[d] + f[len(d):]
262 if df not in copy:
262 if df not in copy:
263 copy[f] = df
263 copy[f] = df
264 repo.ui.debug(" file %s -> %s\n" % (f, copy[f]))
264 repo.ui.debug(" file %s -> %s\n" % (f, copy[f]))
265 break
265 break
266
266
267 return copy, diverge
267 return copy, diverge
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now