##// END OF EJS Templates
match: remove files arg from repo.status and friends
Matt Mackall -
r6603:41eb20cc default
parent child Browse files
Show More
@@ -1,251 +1,251 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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 '''
8 '''
9 The `extdiff' Mercurial extension allows you to use external programs
9 The `extdiff' Mercurial extension allows you to use external programs
10 to compare revisions, or revision with working dir. The external diff
10 to compare revisions, or revision with working dir. The external diff
11 programs are called with a configurable set of options and two
11 programs are called with a configurable set of options and two
12 non-option arguments: paths to directories containing snapshots of
12 non-option arguments: paths to directories containing snapshots of
13 files to compare.
13 files to compare.
14
14
15 To enable this extension:
15 To enable this extension:
16
16
17 [extensions]
17 [extensions]
18 hgext.extdiff =
18 hgext.extdiff =
19
19
20 The `extdiff' extension also allows to configure new diff commands, so
20 The `extdiff' extension also allows to configure new diff commands, so
21 you do not need to type "hg extdiff -p kdiff3" always.
21 you do not need to type "hg extdiff -p kdiff3" always.
22
22
23 [extdiff]
23 [extdiff]
24 # add new command that runs GNU diff(1) in 'context diff' mode
24 # add new command that runs GNU diff(1) in 'context diff' mode
25 cdiff = gdiff -Nprc5
25 cdiff = gdiff -Nprc5
26 ## or the old way:
26 ## or the old way:
27 #cmd.cdiff = gdiff
27 #cmd.cdiff = gdiff
28 #opts.cdiff = -Nprc5
28 #opts.cdiff = -Nprc5
29
29
30 # add new command called vdiff, runs kdiff3
30 # add new command called vdiff, runs kdiff3
31 vdiff = kdiff3
31 vdiff = kdiff3
32
32
33 # add new command called meld, runs meld (no need to name twice)
33 # add new command called meld, runs meld (no need to name twice)
34 meld =
34 meld =
35
35
36 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
36 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
37 #(see http://www.vim.org/scripts/script.php?script_id=102)
37 #(see http://www.vim.org/scripts/script.php?script_id=102)
38 # Non english user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
38 # Non english user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
39 # your .vimrc
39 # your .vimrc
40 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
40 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
41
41
42 You can use -I/-X and list of file or directory names like normal
42 You can use -I/-X and list of file or directory names like normal
43 "hg diff" command. The `extdiff' extension makes snapshots of only
43 "hg diff" command. The `extdiff' extension makes snapshots of only
44 needed files, so running the external diff program will actually be
44 needed files, so running the external diff program will actually be
45 pretty fast (at least faster than having to compare the entire tree).
45 pretty fast (at least faster than having to compare the entire tree).
46 '''
46 '''
47
47
48 from mercurial.i18n import _
48 from mercurial.i18n import _
49 from mercurial.node import short
49 from mercurial.node import short
50 from mercurial import cmdutil, util, commands
50 from mercurial import cmdutil, util, commands
51 import os, shlex, shutil, tempfile
51 import os, shlex, shutil, tempfile
52
52
53 def snapshot_node(ui, repo, files, node, tmproot):
53 def snapshot_node(ui, repo, files, node, tmproot):
54 '''snapshot files as of some revision'''
54 '''snapshot files as of some revision'''
55 mf = repo.changectx(node).manifest()
55 mf = repo.changectx(node).manifest()
56 dirname = os.path.basename(repo.root)
56 dirname = os.path.basename(repo.root)
57 if dirname == "":
57 if dirname == "":
58 dirname = "root"
58 dirname = "root"
59 dirname = '%s.%s' % (dirname, short(node))
59 dirname = '%s.%s' % (dirname, short(node))
60 base = os.path.join(tmproot, dirname)
60 base = os.path.join(tmproot, dirname)
61 os.mkdir(base)
61 os.mkdir(base)
62 ui.note(_('making snapshot of %d files from rev %s\n') %
62 ui.note(_('making snapshot of %d files from rev %s\n') %
63 (len(files), short(node)))
63 (len(files), short(node)))
64 for fn in files:
64 for fn in files:
65 if not fn in mf:
65 if not fn in mf:
66 # skipping new file after a merge ?
66 # skipping new file after a merge ?
67 continue
67 continue
68 wfn = util.pconvert(fn)
68 wfn = util.pconvert(fn)
69 ui.note(' %s\n' % wfn)
69 ui.note(' %s\n' % wfn)
70 dest = os.path.join(base, wfn)
70 dest = os.path.join(base, wfn)
71 destdir = os.path.dirname(dest)
71 destdir = os.path.dirname(dest)
72 if not os.path.isdir(destdir):
72 if not os.path.isdir(destdir):
73 os.makedirs(destdir)
73 os.makedirs(destdir)
74 data = repo.wwritedata(wfn, repo.file(wfn).read(mf[wfn]))
74 data = repo.wwritedata(wfn, repo.file(wfn).read(mf[wfn]))
75 open(dest, 'wb').write(data)
75 open(dest, 'wb').write(data)
76 return dirname
76 return dirname
77
77
78
78
79 def snapshot_wdir(ui, repo, files, tmproot):
79 def snapshot_wdir(ui, repo, files, tmproot):
80 '''snapshot files from working directory.
80 '''snapshot files from working directory.
81 if not using snapshot, -I/-X does not work and recursive diff
81 if not using snapshot, -I/-X does not work and recursive diff
82 in tools like kdiff3 and meld displays too many files.'''
82 in tools like kdiff3 and meld displays too many files.'''
83 repo_root = repo.root
83 repo_root = repo.root
84
84
85 dirname = os.path.basename(repo_root)
85 dirname = os.path.basename(repo_root)
86 if dirname == "":
86 if dirname == "":
87 dirname = "root"
87 dirname = "root"
88 base = os.path.join(tmproot, dirname)
88 base = os.path.join(tmproot, dirname)
89 os.mkdir(base)
89 os.mkdir(base)
90 ui.note(_('making snapshot of %d files from working dir\n') %
90 ui.note(_('making snapshot of %d files from working dir\n') %
91 (len(files)))
91 (len(files)))
92
92
93 fns_and_mtime = []
93 fns_and_mtime = []
94
94
95 for fn in files:
95 for fn in files:
96 wfn = util.pconvert(fn)
96 wfn = util.pconvert(fn)
97 ui.note(' %s\n' % wfn)
97 ui.note(' %s\n' % wfn)
98 dest = os.path.join(base, wfn)
98 dest = os.path.join(base, wfn)
99 destdir = os.path.dirname(dest)
99 destdir = os.path.dirname(dest)
100 if not os.path.isdir(destdir):
100 if not os.path.isdir(destdir):
101 os.makedirs(destdir)
101 os.makedirs(destdir)
102
102
103 fp = open(dest, 'wb')
103 fp = open(dest, 'wb')
104 for chunk in util.filechunkiter(repo.wopener(wfn)):
104 for chunk in util.filechunkiter(repo.wopener(wfn)):
105 fp.write(chunk)
105 fp.write(chunk)
106 fp.close()
106 fp.close()
107
107
108 fns_and_mtime.append((dest, os.path.join(repo_root, fn),
108 fns_and_mtime.append((dest, os.path.join(repo_root, fn),
109 os.path.getmtime(dest)))
109 os.path.getmtime(dest)))
110
110
111
111
112 return dirname, fns_and_mtime
112 return dirname, fns_and_mtime
113
113
114
114
115 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
115 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
116 '''Do the actuall diff:
116 '''Do the actuall diff:
117
117
118 - copy to a temp structure if diffing 2 internal revisions
118 - copy to a temp structure if diffing 2 internal revisions
119 - copy to a temp structure if diffing working revision with
119 - copy to a temp structure if diffing working revision with
120 another one and more than 1 file is changed
120 another one and more than 1 file is changed
121 - just invoke the diff for a single file in the working dir
121 - just invoke the diff for a single file in the working dir
122 '''
122 '''
123 node1, node2 = cmdutil.revpair(repo, opts['rev'])
123 node1, node2 = cmdutil.revpair(repo, opts['rev'])
124 matcher = cmdutil.match(repo, pats, opts)
124 matcher = cmdutil.match(repo, pats, opts)
125 modified, added, removed, deleted, unknown = repo.status(
125 modified, added, removed, deleted, unknown = repo.status(
126 node1, node2, matcher.files(), match=matcher)[:5]
126 node1, node2, matcher)[:5]
127 if not (modified or added or removed):
127 if not (modified or added or removed):
128 return 0
128 return 0
129
129
130 tmproot = tempfile.mkdtemp(prefix='extdiff.')
130 tmproot = tempfile.mkdtemp(prefix='extdiff.')
131 dir2root = ''
131 dir2root = ''
132 try:
132 try:
133 # Always make a copy of node1
133 # Always make a copy of node1
134 dir1 = snapshot_node(ui, repo, modified + removed, node1, tmproot)
134 dir1 = snapshot_node(ui, repo, modified + removed, node1, tmproot)
135 changes = len(modified) + len(removed) + len(added)
135 changes = len(modified) + len(removed) + len(added)
136
136
137 fns_and_mtime = []
137 fns_and_mtime = []
138
138
139 # If node2 in not the wc or there is >1 change, copy it
139 # If node2 in not the wc or there is >1 change, copy it
140 if node2:
140 if node2:
141 dir2 = snapshot_node(ui, repo, modified + added, node2, tmproot)
141 dir2 = snapshot_node(ui, repo, modified + added, node2, tmproot)
142 elif changes > 1:
142 elif changes > 1:
143 #we only actually need to get the files to copy back to the working
143 #we only actually need to get the files to copy back to the working
144 #dir in this case (because the other cases are: diffing 2 revisions
144 #dir in this case (because the other cases are: diffing 2 revisions
145 #or single file -- in which case the file is already directly passed
145 #or single file -- in which case the file is already directly passed
146 #to the diff tool).
146 #to the diff tool).
147 dir2, fns_and_mtime = snapshot_wdir(ui, repo, modified + added, tmproot)
147 dir2, fns_and_mtime = snapshot_wdir(ui, repo, modified + added, tmproot)
148 else:
148 else:
149 # This lets the diff tool open the changed file directly
149 # This lets the diff tool open the changed file directly
150 dir2 = ''
150 dir2 = ''
151 dir2root = repo.root
151 dir2root = repo.root
152
152
153 # If only one change, diff the files instead of the directories
153 # If only one change, diff the files instead of the directories
154 if changes == 1 :
154 if changes == 1 :
155 if len(modified):
155 if len(modified):
156 dir1 = os.path.join(dir1, util.localpath(modified[0]))
156 dir1 = os.path.join(dir1, util.localpath(modified[0]))
157 dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0]))
157 dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0]))
158 elif len(removed) :
158 elif len(removed) :
159 dir1 = os.path.join(dir1, util.localpath(removed[0]))
159 dir1 = os.path.join(dir1, util.localpath(removed[0]))
160 dir2 = os.devnull
160 dir2 = os.devnull
161 else:
161 else:
162 dir1 = os.devnull
162 dir1 = os.devnull
163 dir2 = os.path.join(dir2root, dir2, util.localpath(added[0]))
163 dir2 = os.path.join(dir2root, dir2, util.localpath(added[0]))
164
164
165 cmdline = ('%s %s %s %s' %
165 cmdline = ('%s %s %s %s' %
166 (util.shellquote(diffcmd), ' '.join(diffopts),
166 (util.shellquote(diffcmd), ' '.join(diffopts),
167 util.shellquote(dir1), util.shellquote(dir2)))
167 util.shellquote(dir1), util.shellquote(dir2)))
168 ui.debug('running %r in %s\n' % (cmdline, tmproot))
168 ui.debug('running %r in %s\n' % (cmdline, tmproot))
169 util.system(cmdline, cwd=tmproot)
169 util.system(cmdline, cwd=tmproot)
170
170
171 for copy_fn, working_fn, mtime in fns_and_mtime:
171 for copy_fn, working_fn, mtime in fns_and_mtime:
172 if os.path.getmtime(copy_fn) != mtime:
172 if os.path.getmtime(copy_fn) != mtime:
173 ui.debug('File changed while diffing. '
173 ui.debug('File changed while diffing. '
174 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
174 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
175 util.copyfile(copy_fn, working_fn)
175 util.copyfile(copy_fn, working_fn)
176
176
177 return 1
177 return 1
178 finally:
178 finally:
179 ui.note(_('cleaning up temp directory\n'))
179 ui.note(_('cleaning up temp directory\n'))
180 shutil.rmtree(tmproot)
180 shutil.rmtree(tmproot)
181
181
182 def extdiff(ui, repo, *pats, **opts):
182 def extdiff(ui, repo, *pats, **opts):
183 '''use external program to diff repository (or selected files)
183 '''use external program to diff repository (or selected files)
184
184
185 Show differences between revisions for the specified files, using
185 Show differences between revisions for the specified files, using
186 an external program. The default program used is diff, with
186 an external program. The default program used is diff, with
187 default options "-Npru".
187 default options "-Npru".
188
188
189 To select a different program, use the -p option. The program
189 To select a different program, use the -p option. The program
190 will be passed the names of two directories to compare. To pass
190 will be passed the names of two directories to compare. To pass
191 additional options to the program, use the -o option. These will
191 additional options to the program, use the -o option. These will
192 be passed before the names of the directories to compare.
192 be passed before the names of the directories to compare.
193
193
194 When two revision arguments are given, then changes are
194 When two revision arguments are given, then changes are
195 shown between those revisions. If only one revision is
195 shown between those revisions. If only one revision is
196 specified then that revision is compared to the working
196 specified then that revision is compared to the working
197 directory, and, when no revisions are specified, the
197 directory, and, when no revisions are specified, the
198 working directory files are compared to its parent.'''
198 working directory files are compared to its parent.'''
199 program = opts['program'] or 'diff'
199 program = opts['program'] or 'diff'
200 if opts['program']:
200 if opts['program']:
201 option = opts['option']
201 option = opts['option']
202 else:
202 else:
203 option = opts['option'] or ['-Npru']
203 option = opts['option'] or ['-Npru']
204 return dodiff(ui, repo, program, option, pats, opts)
204 return dodiff(ui, repo, program, option, pats, opts)
205
205
206 cmdtable = {
206 cmdtable = {
207 "extdiff":
207 "extdiff":
208 (extdiff,
208 (extdiff,
209 [('p', 'program', '', _('comparison program to run')),
209 [('p', 'program', '', _('comparison program to run')),
210 ('o', 'option', [], _('pass option to comparison program')),
210 ('o', 'option', [], _('pass option to comparison program')),
211 ('r', 'rev', [], _('revision')),
211 ('r', 'rev', [], _('revision')),
212 ] + commands.walkopts,
212 ] + commands.walkopts,
213 _('hg extdiff [OPT]... [FILE]...')),
213 _('hg extdiff [OPT]... [FILE]...')),
214 }
214 }
215
215
216 def uisetup(ui):
216 def uisetup(ui):
217 for cmd, path in ui.configitems('extdiff'):
217 for cmd, path in ui.configitems('extdiff'):
218 if cmd.startswith('cmd.'):
218 if cmd.startswith('cmd.'):
219 cmd = cmd[4:]
219 cmd = cmd[4:]
220 if not path: path = cmd
220 if not path: path = cmd
221 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
221 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
222 diffopts = diffopts and [diffopts] or []
222 diffopts = diffopts and [diffopts] or []
223 elif cmd.startswith('opts.'):
223 elif cmd.startswith('opts.'):
224 continue
224 continue
225 else:
225 else:
226 # command = path opts
226 # command = path opts
227 if path:
227 if path:
228 diffopts = shlex.split(path)
228 diffopts = shlex.split(path)
229 path = diffopts.pop(0)
229 path = diffopts.pop(0)
230 else:
230 else:
231 path, diffopts = cmd, []
231 path, diffopts = cmd, []
232 def save(cmd, path, diffopts):
232 def save(cmd, path, diffopts):
233 '''use closure to save diff command to use'''
233 '''use closure to save diff command to use'''
234 def mydiff(ui, repo, *pats, **opts):
234 def mydiff(ui, repo, *pats, **opts):
235 return dodiff(ui, repo, path, diffopts, pats, opts)
235 return dodiff(ui, repo, path, diffopts, pats, opts)
236 mydiff.__doc__ = '''use %(path)s to diff repository (or selected files)
236 mydiff.__doc__ = '''use %(path)s to diff repository (or selected files)
237
237
238 Show differences between revisions for the specified
238 Show differences between revisions for the specified
239 files, using the %(path)s program.
239 files, using the %(path)s program.
240
240
241 When two revision arguments are given, then changes are
241 When two revision arguments are given, then changes are
242 shown between those revisions. If only one revision is
242 shown between those revisions. If only one revision is
243 specified then that revision is compared to the working
243 specified then that revision is compared to the working
244 directory, and, when no revisions are specified, the
244 directory, and, when no revisions are specified, the
245 working directory files are compared to its parent.''' % {
245 working directory files are compared to its parent.''' % {
246 'path': util.uirepr(path),
246 'path': util.uirepr(path),
247 }
247 }
248 return mydiff
248 return mydiff
249 cmdtable[cmd] = (save(cmd, path, diffopts),
249 cmdtable[cmd] = (save(cmd, path, diffopts),
250 cmdtable['extdiff'][1][1:],
250 cmdtable['extdiff'][1][1:],
251 _('hg %s [OPTION]... [FILE]...') % cmd)
251 _('hg %s [OPTION]... [FILE]...') % cmd)
@@ -1,358 +1,358 b''
1 # Minimal support for git commands on an hg repository
1 # Minimal support for git commands on an hg repository
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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7 #
7 #
8 # The hgk extension allows browsing the history of a repository in a
8 # The hgk extension allows browsing the history of a repository in a
9 # graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is
9 # graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is
10 # not distributed with Mercurial.)
10 # not distributed with Mercurial.)
11 #
11 #
12 # hgk consists of two parts: a Tcl script that does the displaying and
12 # hgk consists of two parts: a Tcl script that does the displaying and
13 # querying of information, and an extension to mercurial named hgk.py,
13 # querying of information, and an extension to mercurial named hgk.py,
14 # which provides hooks for hgk to get information. hgk can be found in
14 # which provides hooks for hgk to get information. hgk can be found in
15 # the contrib directory, and hgk.py can be found in the hgext
15 # the contrib directory, and hgk.py can be found in the hgext
16 # directory.
16 # directory.
17 #
17 #
18 # To load the hgext.py extension, add it to your .hgrc file (you have
18 # To load the hgext.py extension, add it to your .hgrc file (you have
19 # to use your global $HOME/.hgrc file, not one in a repository). You
19 # to use your global $HOME/.hgrc file, not one in a repository). You
20 # can specify an absolute path:
20 # can specify an absolute path:
21 #
21 #
22 # [extensions]
22 # [extensions]
23 # hgk=/usr/local/lib/hgk.py
23 # hgk=/usr/local/lib/hgk.py
24 #
24 #
25 # Mercurial can also scan the default python library path for a file
25 # Mercurial can also scan the default python library path for a file
26 # named 'hgk.py' if you set hgk empty:
26 # named 'hgk.py' if you set hgk empty:
27 #
27 #
28 # [extensions]
28 # [extensions]
29 # hgk=
29 # hgk=
30 #
30 #
31 # The hg view command will launch the hgk Tcl script. For this command
31 # The hg view command will launch the hgk Tcl script. For this command
32 # to work, hgk must be in your search path. Alternately, you can
32 # to work, hgk must be in your search path. Alternately, you can
33 # specify the path to hgk in your .hgrc file:
33 # specify the path to hgk in your .hgrc file:
34 #
34 #
35 # [hgk]
35 # [hgk]
36 # path=/location/of/hgk
36 # path=/location/of/hgk
37 #
37 #
38 # hgk can make use of the extdiff extension to visualize
38 # hgk can make use of the extdiff extension to visualize
39 # revisions. Assuming you had already configured extdiff vdiff
39 # revisions. Assuming you had already configured extdiff vdiff
40 # command, just add:
40 # command, just add:
41 #
41 #
42 # [hgk]
42 # [hgk]
43 # vdiff=vdiff
43 # vdiff=vdiff
44 #
44 #
45 # Revisions context menu will now display additional entries to fire
45 # Revisions context menu will now display additional entries to fire
46 # vdiff on hovered and selected revisions.
46 # vdiff on hovered and selected revisions.
47
47
48 import os
48 import os
49 from mercurial import commands, util, patch, revlog, cmdutil
49 from mercurial import commands, util, patch, revlog, cmdutil
50 from mercurial.node import nullid, nullrev, short
50 from mercurial.node import nullid, nullrev, short
51
51
52 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
52 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
53 """diff trees from two commits"""
53 """diff trees from two commits"""
54 def __difftree(repo, node1, node2, files=[]):
54 def __difftree(repo, node1, node2, files=[]):
55 assert node2 is not None
55 assert node2 is not None
56 mmap = repo.changectx(node1).manifest()
56 mmap = repo.changectx(node1).manifest()
57 mmap2 = repo.changectx(node2).manifest()
57 mmap2 = repo.changectx(node2).manifest()
58 m = cmdutil.matchfiles(repo, files)
58 m = cmdutil.matchfiles(repo, files)
59 status = repo.status(node1, node2, files=m.files(), match=m)[:5]
59 status = repo.status(node1, node2, match=m)[:5]
60 modified, added, removed, deleted, unknown = status
60 modified, added, removed, deleted, unknown = status
61
61
62 empty = short(nullid)
62 empty = short(nullid)
63
63
64 for f in modified:
64 for f in modified:
65 # TODO get file permissions
65 # TODO get file permissions
66 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
66 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
67 (short(mmap[f]), short(mmap2[f]), f, f))
67 (short(mmap[f]), short(mmap2[f]), f, f))
68 for f in added:
68 for f in added:
69 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
69 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
70 (empty, short(mmap2[f]), f, f))
70 (empty, short(mmap2[f]), f, f))
71 for f in removed:
71 for f in removed:
72 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
72 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
73 (short(mmap[f]), empty, f, f))
73 (short(mmap[f]), empty, f, f))
74 ##
74 ##
75
75
76 while True:
76 while True:
77 if opts['stdin']:
77 if opts['stdin']:
78 try:
78 try:
79 line = raw_input().split(' ')
79 line = raw_input().split(' ')
80 node1 = line[0]
80 node1 = line[0]
81 if len(line) > 1:
81 if len(line) > 1:
82 node2 = line[1]
82 node2 = line[1]
83 else:
83 else:
84 node2 = None
84 node2 = None
85 except EOFError:
85 except EOFError:
86 break
86 break
87 node1 = repo.lookup(node1)
87 node1 = repo.lookup(node1)
88 if node2:
88 if node2:
89 node2 = repo.lookup(node2)
89 node2 = repo.lookup(node2)
90 else:
90 else:
91 node2 = node1
91 node2 = node1
92 node1 = repo.changelog.parents(node1)[0]
92 node1 = repo.changelog.parents(node1)[0]
93 if opts['patch']:
93 if opts['patch']:
94 if opts['pretty']:
94 if opts['pretty']:
95 catcommit(ui, repo, node2, "")
95 catcommit(ui, repo, node2, "")
96 m = cmdutil.matchfiles(repo, files)
96 m = cmdutil.matchfiles(repo, files)
97 patch.diff(repo, node1, node2, match=m,
97 patch.diff(repo, node1, node2, match=m,
98 opts=patch.diffopts(ui, {'git': True}))
98 opts=patch.diffopts(ui, {'git': True}))
99 else:
99 else:
100 __difftree(repo, node1, node2, files=files)
100 __difftree(repo, node1, node2, files=files)
101 if not opts['stdin']:
101 if not opts['stdin']:
102 break
102 break
103
103
104 def catcommit(ui, repo, n, prefix, ctx=None):
104 def catcommit(ui, repo, n, prefix, ctx=None):
105 nlprefix = '\n' + prefix;
105 nlprefix = '\n' + prefix;
106 if ctx is None:
106 if ctx is None:
107 ctx = repo.changectx(n)
107 ctx = repo.changectx(n)
108 (p1, p2) = ctx.parents()
108 (p1, p2) = ctx.parents()
109 ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ??
109 ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ??
110 if p1: ui.write("parent %s\n" % short(p1.node()))
110 if p1: ui.write("parent %s\n" % short(p1.node()))
111 if p2: ui.write("parent %s\n" % short(p2.node()))
111 if p2: ui.write("parent %s\n" % short(p2.node()))
112 date = ctx.date()
112 date = ctx.date()
113 description = ctx.description().replace("\0", "")
113 description = ctx.description().replace("\0", "")
114 lines = description.splitlines()
114 lines = description.splitlines()
115 if lines and lines[-1].startswith('committer:'):
115 if lines and lines[-1].startswith('committer:'):
116 committer = lines[-1].split(': ')[1].rstrip()
116 committer = lines[-1].split(': ')[1].rstrip()
117 else:
117 else:
118 committer = ctx.user()
118 committer = ctx.user()
119
119
120 ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
120 ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
121 ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
121 ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
122 ui.write("revision %d\n" % ctx.rev())
122 ui.write("revision %d\n" % ctx.rev())
123 ui.write("branch %s\n\n" % ctx.branch())
123 ui.write("branch %s\n\n" % ctx.branch())
124
124
125 if prefix != "":
125 if prefix != "":
126 ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
126 ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
127 else:
127 else:
128 ui.write(description + "\n")
128 ui.write(description + "\n")
129 if prefix:
129 if prefix:
130 ui.write('\0')
130 ui.write('\0')
131
131
132 def base(ui, repo, node1, node2):
132 def base(ui, repo, node1, node2):
133 """Output common ancestor information"""
133 """Output common ancestor information"""
134 node1 = repo.lookup(node1)
134 node1 = repo.lookup(node1)
135 node2 = repo.lookup(node2)
135 node2 = repo.lookup(node2)
136 n = repo.changelog.ancestor(node1, node2)
136 n = repo.changelog.ancestor(node1, node2)
137 ui.write(short(n) + "\n")
137 ui.write(short(n) + "\n")
138
138
139 def catfile(ui, repo, type=None, r=None, **opts):
139 def catfile(ui, repo, type=None, r=None, **opts):
140 """cat a specific revision"""
140 """cat a specific revision"""
141 # in stdin mode, every line except the commit is prefixed with two
141 # in stdin mode, every line except the commit is prefixed with two
142 # spaces. This way the our caller can find the commit without magic
142 # spaces. This way the our caller can find the commit without magic
143 # strings
143 # strings
144 #
144 #
145 prefix = ""
145 prefix = ""
146 if opts['stdin']:
146 if opts['stdin']:
147 try:
147 try:
148 (type, r) = raw_input().split(' ');
148 (type, r) = raw_input().split(' ');
149 prefix = " "
149 prefix = " "
150 except EOFError:
150 except EOFError:
151 return
151 return
152
152
153 else:
153 else:
154 if not type or not r:
154 if not type or not r:
155 ui.warn("cat-file: type or revision not supplied\n")
155 ui.warn("cat-file: type or revision not supplied\n")
156 commands.help_(ui, 'cat-file')
156 commands.help_(ui, 'cat-file')
157
157
158 while r:
158 while r:
159 if type != "commit":
159 if type != "commit":
160 ui.warn("aborting hg cat-file only understands commits\n")
160 ui.warn("aborting hg cat-file only understands commits\n")
161 return 1;
161 return 1;
162 n = repo.lookup(r)
162 n = repo.lookup(r)
163 catcommit(ui, repo, n, prefix)
163 catcommit(ui, repo, n, prefix)
164 if opts['stdin']:
164 if opts['stdin']:
165 try:
165 try:
166 (type, r) = raw_input().split(' ');
166 (type, r) = raw_input().split(' ');
167 except EOFError:
167 except EOFError:
168 break
168 break
169 else:
169 else:
170 break
170 break
171
171
172 # git rev-tree is a confusing thing. You can supply a number of
172 # git rev-tree is a confusing thing. You can supply a number of
173 # commit sha1s on the command line, and it walks the commit history
173 # commit sha1s on the command line, and it walks the commit history
174 # telling you which commits are reachable from the supplied ones via
174 # telling you which commits are reachable from the supplied ones via
175 # a bitmask based on arg position.
175 # a bitmask based on arg position.
176 # you can specify a commit to stop at by starting the sha1 with ^
176 # you can specify a commit to stop at by starting the sha1 with ^
177 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
177 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
178 def chlogwalk():
178 def chlogwalk():
179 count = repo.changelog.count()
179 count = repo.changelog.count()
180 i = count
180 i = count
181 l = [0] * 100
181 l = [0] * 100
182 chunk = 100
182 chunk = 100
183 while True:
183 while True:
184 if chunk > i:
184 if chunk > i:
185 chunk = i
185 chunk = i
186 i = 0
186 i = 0
187 else:
187 else:
188 i -= chunk
188 i -= chunk
189
189
190 for x in xrange(0, chunk):
190 for x in xrange(0, chunk):
191 if i + x >= count:
191 if i + x >= count:
192 l[chunk - x:] = [0] * (chunk - x)
192 l[chunk - x:] = [0] * (chunk - x)
193 break
193 break
194 if full != None:
194 if full != None:
195 l[x] = repo.changectx(i + x)
195 l[x] = repo.changectx(i + x)
196 l[x].changeset() # force reading
196 l[x].changeset() # force reading
197 else:
197 else:
198 l[x] = 1
198 l[x] = 1
199 for x in xrange(chunk-1, -1, -1):
199 for x in xrange(chunk-1, -1, -1):
200 if l[x] != 0:
200 if l[x] != 0:
201 yield (i + x, full != None and l[x] or None)
201 yield (i + x, full != None and l[x] or None)
202 if i == 0:
202 if i == 0:
203 break
203 break
204
204
205 # calculate and return the reachability bitmask for sha
205 # calculate and return the reachability bitmask for sha
206 def is_reachable(ar, reachable, sha):
206 def is_reachable(ar, reachable, sha):
207 if len(ar) == 0:
207 if len(ar) == 0:
208 return 1
208 return 1
209 mask = 0
209 mask = 0
210 for i in xrange(len(ar)):
210 for i in xrange(len(ar)):
211 if sha in reachable[i]:
211 if sha in reachable[i]:
212 mask |= 1 << i
212 mask |= 1 << i
213
213
214 return mask
214 return mask
215
215
216 reachable = []
216 reachable = []
217 stop_sha1 = []
217 stop_sha1 = []
218 want_sha1 = []
218 want_sha1 = []
219 count = 0
219 count = 0
220
220
221 # figure out which commits they are asking for and which ones they
221 # figure out which commits they are asking for and which ones they
222 # want us to stop on
222 # want us to stop on
223 for i in xrange(len(args)):
223 for i in xrange(len(args)):
224 if args[i].startswith('^'):
224 if args[i].startswith('^'):
225 s = repo.lookup(args[i][1:])
225 s = repo.lookup(args[i][1:])
226 stop_sha1.append(s)
226 stop_sha1.append(s)
227 want_sha1.append(s)
227 want_sha1.append(s)
228 elif args[i] != 'HEAD':
228 elif args[i] != 'HEAD':
229 want_sha1.append(repo.lookup(args[i]))
229 want_sha1.append(repo.lookup(args[i]))
230
230
231 # calculate the graph for the supplied commits
231 # calculate the graph for the supplied commits
232 for i in xrange(len(want_sha1)):
232 for i in xrange(len(want_sha1)):
233 reachable.append({});
233 reachable.append({});
234 n = want_sha1[i];
234 n = want_sha1[i];
235 visit = [n];
235 visit = [n];
236 reachable[i][n] = 1
236 reachable[i][n] = 1
237 while visit:
237 while visit:
238 n = visit.pop(0)
238 n = visit.pop(0)
239 if n in stop_sha1:
239 if n in stop_sha1:
240 continue
240 continue
241 for p in repo.changelog.parents(n):
241 for p in repo.changelog.parents(n):
242 if p not in reachable[i]:
242 if p not in reachable[i]:
243 reachable[i][p] = 1
243 reachable[i][p] = 1
244 visit.append(p)
244 visit.append(p)
245 if p in stop_sha1:
245 if p in stop_sha1:
246 continue
246 continue
247
247
248 # walk the repository looking for commits that are in our
248 # walk the repository looking for commits that are in our
249 # reachability graph
249 # reachability graph
250 for i, ctx in chlogwalk():
250 for i, ctx in chlogwalk():
251 n = repo.changelog.node(i)
251 n = repo.changelog.node(i)
252 mask = is_reachable(want_sha1, reachable, n)
252 mask = is_reachable(want_sha1, reachable, n)
253 if mask:
253 if mask:
254 parentstr = ""
254 parentstr = ""
255 if parents:
255 if parents:
256 pp = repo.changelog.parents(n)
256 pp = repo.changelog.parents(n)
257 if pp[0] != nullid:
257 if pp[0] != nullid:
258 parentstr += " " + short(pp[0])
258 parentstr += " " + short(pp[0])
259 if pp[1] != nullid:
259 if pp[1] != nullid:
260 parentstr += " " + short(pp[1])
260 parentstr += " " + short(pp[1])
261 if not full:
261 if not full:
262 ui.write("%s%s\n" % (short(n), parentstr))
262 ui.write("%s%s\n" % (short(n), parentstr))
263 elif full == "commit":
263 elif full == "commit":
264 ui.write("%s%s\n" % (short(n), parentstr))
264 ui.write("%s%s\n" % (short(n), parentstr))
265 catcommit(ui, repo, n, ' ', ctx)
265 catcommit(ui, repo, n, ' ', ctx)
266 else:
266 else:
267 (p1, p2) = repo.changelog.parents(n)
267 (p1, p2) = repo.changelog.parents(n)
268 (h, h1, h2) = map(short, (n, p1, p2))
268 (h, h1, h2) = map(short, (n, p1, p2))
269 (i1, i2) = map(repo.changelog.rev, (p1, p2))
269 (i1, i2) = map(repo.changelog.rev, (p1, p2))
270
270
271 date = ctx.date()[0]
271 date = ctx.date()[0]
272 ui.write("%s %s:%s" % (date, h, mask))
272 ui.write("%s %s:%s" % (date, h, mask))
273 mask = is_reachable(want_sha1, reachable, p1)
273 mask = is_reachable(want_sha1, reachable, p1)
274 if i1 != nullrev and mask > 0:
274 if i1 != nullrev and mask > 0:
275 ui.write("%s:%s " % (h1, mask)),
275 ui.write("%s:%s " % (h1, mask)),
276 mask = is_reachable(want_sha1, reachable, p2)
276 mask = is_reachable(want_sha1, reachable, p2)
277 if i2 != nullrev and mask > 0:
277 if i2 != nullrev and mask > 0:
278 ui.write("%s:%s " % (h2, mask))
278 ui.write("%s:%s " % (h2, mask))
279 ui.write("\n")
279 ui.write("\n")
280 if maxnr and count >= maxnr:
280 if maxnr and count >= maxnr:
281 break
281 break
282 count += 1
282 count += 1
283
283
284 def revparse(ui, repo, *revs, **opts):
284 def revparse(ui, repo, *revs, **opts):
285 """Parse given revisions"""
285 """Parse given revisions"""
286 def revstr(rev):
286 def revstr(rev):
287 if rev == 'HEAD':
287 if rev == 'HEAD':
288 rev = 'tip'
288 rev = 'tip'
289 return revlog.hex(repo.lookup(rev))
289 return revlog.hex(repo.lookup(rev))
290
290
291 for r in revs:
291 for r in revs:
292 revrange = r.split(':', 1)
292 revrange = r.split(':', 1)
293 ui.write('%s\n' % revstr(revrange[0]))
293 ui.write('%s\n' % revstr(revrange[0]))
294 if len(revrange) == 2:
294 if len(revrange) == 2:
295 ui.write('^%s\n' % revstr(revrange[1]))
295 ui.write('^%s\n' % revstr(revrange[1]))
296
296
297 # git rev-list tries to order things by date, and has the ability to stop
297 # git rev-list tries to order things by date, and has the ability to stop
298 # at a given commit without walking the whole repo. TODO add the stop
298 # at a given commit without walking the whole repo. TODO add the stop
299 # parameter
299 # parameter
300 def revlist(ui, repo, *revs, **opts):
300 def revlist(ui, repo, *revs, **opts):
301 """print revisions"""
301 """print revisions"""
302 if opts['header']:
302 if opts['header']:
303 full = "commit"
303 full = "commit"
304 else:
304 else:
305 full = None
305 full = None
306 copy = [x for x in revs]
306 copy = [x for x in revs]
307 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
307 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
308
308
309 def config(ui, repo, **opts):
309 def config(ui, repo, **opts):
310 """print extension options"""
310 """print extension options"""
311 def writeopt(name, value):
311 def writeopt(name, value):
312 ui.write('k=%s\nv=%s\n' % (name, value))
312 ui.write('k=%s\nv=%s\n' % (name, value))
313
313
314 writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
314 writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
315
315
316
316
317 def view(ui, repo, *etc, **opts):
317 def view(ui, repo, *etc, **opts):
318 "start interactive history viewer"
318 "start interactive history viewer"
319 os.chdir(repo.root)
319 os.chdir(repo.root)
320 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
320 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
321 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
321 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
322 ui.debug("running %s\n" % cmd)
322 ui.debug("running %s\n" % cmd)
323 util.system(cmd)
323 util.system(cmd)
324
324
325 cmdtable = {
325 cmdtable = {
326 "^view":
326 "^view":
327 (view,
327 (view,
328 [('l', 'limit', '', 'limit number of changes displayed')],
328 [('l', 'limit', '', 'limit number of changes displayed')],
329 'hg view [-l LIMIT] [REVRANGE]'),
329 'hg view [-l LIMIT] [REVRANGE]'),
330 "debug-diff-tree":
330 "debug-diff-tree":
331 (difftree,
331 (difftree,
332 [('p', 'patch', None, 'generate patch'),
332 [('p', 'patch', None, 'generate patch'),
333 ('r', 'recursive', None, 'recursive'),
333 ('r', 'recursive', None, 'recursive'),
334 ('P', 'pretty', None, 'pretty'),
334 ('P', 'pretty', None, 'pretty'),
335 ('s', 'stdin', None, 'stdin'),
335 ('s', 'stdin', None, 'stdin'),
336 ('C', 'copy', None, 'detect copies'),
336 ('C', 'copy', None, 'detect copies'),
337 ('S', 'search', "", 'search')],
337 ('S', 'search', "", 'search')],
338 'hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...'),
338 'hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...'),
339 "debug-cat-file":
339 "debug-cat-file":
340 (catfile,
340 (catfile,
341 [('s', 'stdin', None, 'stdin')],
341 [('s', 'stdin', None, 'stdin')],
342 'hg debug-cat-file [OPTION]... TYPE FILE'),
342 'hg debug-cat-file [OPTION]... TYPE FILE'),
343 "debug-config":
343 "debug-config":
344 (config, [], 'hg debug-config'),
344 (config, [], 'hg debug-config'),
345 "debug-merge-base":
345 "debug-merge-base":
346 (base, [], 'hg debug-merge-base node node'),
346 (base, [], 'hg debug-merge-base node node'),
347 "debug-rev-parse":
347 "debug-rev-parse":
348 (revparse,
348 (revparse,
349 [('', 'default', '', 'ignored')],
349 [('', 'default', '', 'ignored')],
350 'hg debug-rev-parse REV'),
350 'hg debug-rev-parse REV'),
351 "debug-rev-list":
351 "debug-rev-list":
352 (revlist,
352 (revlist,
353 [('H', 'header', None, 'header'),
353 [('H', 'header', None, 'header'),
354 ('t', 'topo-order', None, 'topo-order'),
354 ('t', 'topo-order', None, 'topo-order'),
355 ('p', 'parents', None, 'parents'),
355 ('p', 'parents', None, 'parents'),
356 ('n', 'max-count', 0, 'max-count')],
356 ('n', 'max-count', 0, 'max-count')],
357 'hg debug-rev-list [options] revs'),
357 'hg debug-rev-list [options] revs'),
358 }
358 }
@@ -1,104 +1,105 b''
1 # __init__.py - inotify-based status acceleration for Linux
1 # __init__.py - inotify-based status acceleration for Linux
2 #
2 #
3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 '''inotify-based status acceleration for Linux systems
9 '''inotify-based status acceleration for Linux systems
10 '''
10 '''
11
11
12 # todo: socket permissions
12 # todo: socket permissions
13
13
14 from mercurial.i18n import gettext as _
14 from mercurial.i18n import gettext as _
15 from mercurial import cmdutil, util
15 from mercurial import cmdutil, util
16 import client, errno, os, server, socket
16 import client, errno, os, server, socket
17 from weakref import proxy
17 from weakref import proxy
18
18
19 def serve(ui, repo, **opts):
19 def serve(ui, repo, **opts):
20 '''start an inotify server for this repository'''
20 '''start an inotify server for this repository'''
21 timeout = opts.get('timeout')
21 timeout = opts.get('timeout')
22 if timeout:
22 if timeout:
23 timeout = float(timeout) * 1e3
23 timeout = float(timeout) * 1e3
24
24
25 class service:
25 class service:
26 def init(self):
26 def init(self):
27 self.master = server.Master(ui, repo, timeout)
27 self.master = server.Master(ui, repo, timeout)
28
28
29 def run(self):
29 def run(self):
30 try:
30 try:
31 self.master.run()
31 self.master.run()
32 finally:
32 finally:
33 self.master.shutdown()
33 self.master.shutdown()
34
34
35 service = service()
35 service = service()
36 cmdutil.service(opts, initfn=service.init, runfn=service.run)
36 cmdutil.service(opts, initfn=service.init, runfn=service.run)
37
37
38 def reposetup(ui, repo):
38 def reposetup(ui, repo):
39 if not repo.local():
39 if not repo.local():
40 return
40 return
41
41
42 # XXX: weakref until hg stops relying on __del__
42 # XXX: weakref until hg stops relying on __del__
43 repo = proxy(repo)
43 repo = proxy(repo)
44
44
45 class inotifydirstate(repo.dirstate.__class__):
45 class inotifydirstate(repo.dirstate.__class__):
46 # Set to True if we're the inotify server, so we don't attempt
46 # Set to True if we're the inotify server, so we don't attempt
47 # to recurse.
47 # to recurse.
48 inotifyserver = False
48 inotifyserver = False
49
49
50 def status(self, files, match, list_ignored, list_clean,
50 def status(self, match, list_ignored, list_clean,
51 list_unknown=True):
51 list_unknown=True):
52 files = match.files()
52 try:
53 try:
53 if not list_ignored and not self.inotifyserver:
54 if not list_ignored and not self.inotifyserver:
54 result = client.query(ui, repo, files, match, False,
55 result = client.query(ui, repo, files, match, False,
55 list_clean, list_unknown)
56 list_clean, list_unknown)
56 if result is not None:
57 if result is not None:
57 return result
58 return result
58 except socket.error, err:
59 except socket.error, err:
59 if err[0] == errno.ECONNREFUSED:
60 if err[0] == errno.ECONNREFUSED:
60 ui.warn(_('(found dead inotify server socket; '
61 ui.warn(_('(found dead inotify server socket; '
61 'removing it)\n'))
62 'removing it)\n'))
62 os.unlink(repo.join('inotify.sock'))
63 os.unlink(repo.join('inotify.sock'))
63 elif err[0] != errno.ENOENT:
64 elif err[0] != errno.ENOENT:
64 raise
65 raise
65 if ui.configbool('inotify', 'autostart'):
66 if ui.configbool('inotify', 'autostart'):
66 query = None
67 query = None
67 ui.debug(_('(starting inotify server)\n'))
68 ui.debug(_('(starting inotify server)\n'))
68 try:
69 try:
69 server.start(ui, repo)
70 server.start(ui, repo)
70 query = client.query
71 query = client.query
71 except server.AlreadyStartedException, inst:
72 except server.AlreadyStartedException, inst:
72 # another process may have started its own
73 # another process may have started its own
73 # inotify server while this one was starting.
74 # inotify server while this one was starting.
74 ui.debug(str(inst))
75 ui.debug(str(inst))
75 query = client.query
76 query = client.query
76 except Exception, inst:
77 except Exception, inst:
77 ui.warn(_('could not start inotify server: '
78 ui.warn(_('could not start inotify server: '
78 '%s\n') % inst)
79 '%s\n') % inst)
79 ui.print_exc()
80 ui.print_exc()
80
81
81 if query:
82 if query:
82 try:
83 try:
83 return query(ui, repo, files or [], match,
84 return query(ui, repo, files or [], match,
84 list_ignored, list_clean, list_unknown)
85 list_ignored, list_clean, list_unknown)
85 except socket.error, err:
86 except socket.error, err:
86 ui.warn(_('could not talk to new inotify '
87 ui.warn(_('could not talk to new inotify '
87 'server: %s\n') % err[1])
88 'server: %s\n') % err[1])
88 ui.print_exc()
89 ui.print_exc()
89
90
90 return super(inotifydirstate, self).status(
91 return super(inotifydirstate, self).status(
91 files, match or util.always, list_ignored, list_clean,
92 match, list_ignored, list_clean,
92 list_unknown)
93 list_unknown)
93
94
94 repo.dirstate.__class__ = inotifydirstate
95 repo.dirstate.__class__ = inotifydirstate
95
96
96 cmdtable = {
97 cmdtable = {
97 '^inserve':
98 '^inserve':
98 (serve,
99 (serve,
99 [('d', 'daemon', None, _('run server in background')),
100 [('d', 'daemon', None, _('run server in background')),
100 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
101 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
101 ('t', 'idle-timeout', '', _('minutes to sit idle before exiting')),
102 ('t', 'idle-timeout', '', _('minutes to sit idle before exiting')),
102 ('', 'pid-file', '', _('name of file to write process ID to'))],
103 ('', 'pid-file', '', _('name of file to write process ID to'))],
103 _('hg inserve [OPT]...')),
104 _('hg inserve [OPT]...')),
104 }
105 }
@@ -1,556 +1,556 b''
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7 #
7 #
8 # $Id$
8 # $Id$
9 #
9 #
10 # Keyword expansion hack against the grain of a DSCM
10 # Keyword expansion hack against the grain of a DSCM
11 #
11 #
12 # There are many good reasons why this is not needed in a distributed
12 # There are many good reasons why this is not needed in a distributed
13 # SCM, still it may be useful in very small projects based on single
13 # SCM, still it may be useful in very small projects based on single
14 # files (like LaTeX packages), that are mostly addressed to an audience
14 # files (like LaTeX packages), that are mostly addressed to an audience
15 # not running a version control system.
15 # not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.
18 # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.
19 #
19 #
20 # Keyword expansion is based on Mercurial's changeset template mappings.
20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 #
21 #
22 # Binary files are not touched.
22 # Binary files are not touched.
23 #
23 #
24 # Setup in hgrc:
24 # Setup in hgrc:
25 #
25 #
26 # [extensions]
26 # [extensions]
27 # # enable extension
27 # # enable extension
28 # hgext.keyword =
28 # hgext.keyword =
29 #
29 #
30 # Files to act upon/ignore are specified in the [keyword] section.
30 # Files to act upon/ignore are specified in the [keyword] section.
31 # Customized keyword template mappings in the [keywordmaps] section.
31 # Customized keyword template mappings in the [keywordmaps] section.
32 #
32 #
33 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
33 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
34
34
35 '''keyword expansion in local repositories
35 '''keyword expansion in local repositories
36
36
37 This extension expands RCS/CVS-like or self-customized $Keywords$
37 This extension expands RCS/CVS-like or self-customized $Keywords$
38 in tracked text files selected by your configuration.
38 in tracked text files selected by your configuration.
39
39
40 Keywords are only expanded in local repositories and not stored in
40 Keywords are only expanded in local repositories and not stored in
41 the change history. The mechanism can be regarded as a convenience
41 the change history. The mechanism can be regarded as a convenience
42 for the current user or for archive distribution.
42 for the current user or for archive distribution.
43
43
44 Configuration is done in the [keyword] and [keywordmaps] sections
44 Configuration is done in the [keyword] and [keywordmaps] sections
45 of hgrc files.
45 of hgrc files.
46
46
47 Example:
47 Example:
48
48
49 [keyword]
49 [keyword]
50 # expand keywords in every python file except those matching "x*"
50 # expand keywords in every python file except those matching "x*"
51 **.py =
51 **.py =
52 x* = ignore
52 x* = ignore
53
53
54 Note: the more specific you are in your filename patterns
54 Note: the more specific you are in your filename patterns
55 the less you lose speed in huge repos.
55 the less you lose speed in huge repos.
56
56
57 For [keywordmaps] template mapping and expansion demonstration and
57 For [keywordmaps] template mapping and expansion demonstration and
58 control run "hg kwdemo".
58 control run "hg kwdemo".
59
59
60 An additional date template filter {date|utcdate} is provided.
60 An additional date template filter {date|utcdate} is provided.
61
61
62 The default template mappings (view with "hg kwdemo -d") can be replaced
62 The default template mappings (view with "hg kwdemo -d") can be replaced
63 with customized keywords and templates.
63 with customized keywords and templates.
64 Again, run "hg kwdemo" to control the results of your config changes.
64 Again, run "hg kwdemo" to control the results of your config changes.
65
65
66 Before changing/disabling active keywords, run "hg kwshrink" to avoid
66 Before changing/disabling active keywords, run "hg kwshrink" to avoid
67 the risk of inadvertedly storing expanded keywords in the change history.
67 the risk of inadvertedly storing expanded keywords in the change history.
68
68
69 To force expansion after enabling it, or a configuration change, run
69 To force expansion after enabling it, or a configuration change, run
70 "hg kwexpand".
70 "hg kwexpand".
71
71
72 Also, when committing with the record extension or using mq's qrecord, be aware
72 Also, when committing with the record extension or using mq's qrecord, be aware
73 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
73 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
74 question to update keyword expansions after all changes have been checked in.
74 question to update keyword expansions after all changes have been checked in.
75
75
76 Expansions spanning more than one line and incremental expansions,
76 Expansions spanning more than one line and incremental expansions,
77 like CVS' $Log$, are not supported. A keyword template map
77 like CVS' $Log$, are not supported. A keyword template map
78 "Log = {desc}" expands to the first line of the changeset description.
78 "Log = {desc}" expands to the first line of the changeset description.
79 '''
79 '''
80
80
81 from mercurial import commands, cmdutil, context, dispatch, filelog, revlog
81 from mercurial import commands, cmdutil, context, dispatch, filelog, revlog
82 from mercurial import patch, localrepo, templater, templatefilters, util
82 from mercurial import patch, localrepo, templater, templatefilters, util
83 from mercurial.hgweb import webcommands
83 from mercurial.hgweb import webcommands
84 from mercurial.node import nullid, hex
84 from mercurial.node import nullid, hex
85 from mercurial.i18n import _
85 from mercurial.i18n import _
86 import re, shutil, tempfile, time
86 import re, shutil, tempfile, time
87
87
88 commands.optionalrepo += ' kwdemo'
88 commands.optionalrepo += ' kwdemo'
89
89
90 # hg commands that do not act on keywords
90 # hg commands that do not act on keywords
91 nokwcommands = ('add addremove bundle copy export grep incoming init'
91 nokwcommands = ('add addremove bundle copy export grep incoming init'
92 ' log outgoing push rename rollback tip'
92 ' log outgoing push rename rollback tip'
93 ' convert email glog')
93 ' convert email glog')
94
94
95 # hg commands that trigger expansion only when writing to working dir,
95 # hg commands that trigger expansion only when writing to working dir,
96 # not when reading filelog, and unexpand when reading from working dir
96 # not when reading filelog, and unexpand when reading from working dir
97 restricted = 'record qfold qimport qnew qpush qrefresh qrecord'
97 restricted = 'record qfold qimport qnew qpush qrefresh qrecord'
98
98
99 def utcdate(date):
99 def utcdate(date):
100 '''Returns hgdate in cvs-like UTC format.'''
100 '''Returns hgdate in cvs-like UTC format.'''
101 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
101 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
102
102
103 # make keyword tools accessible
103 # make keyword tools accessible
104 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
104 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
105
105
106
106
107 class kwtemplater(object):
107 class kwtemplater(object):
108 '''
108 '''
109 Sets up keyword templates, corresponding keyword regex, and
109 Sets up keyword templates, corresponding keyword regex, and
110 provides keyword substitution functions.
110 provides keyword substitution functions.
111 '''
111 '''
112 templates = {
112 templates = {
113 'Revision': '{node|short}',
113 'Revision': '{node|short}',
114 'Author': '{author|user}',
114 'Author': '{author|user}',
115 'Date': '{date|utcdate}',
115 'Date': '{date|utcdate}',
116 'RCSFile': '{file|basename},v',
116 'RCSFile': '{file|basename},v',
117 'Source': '{root}/{file},v',
117 'Source': '{root}/{file},v',
118 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
118 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
119 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
119 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
120 }
120 }
121
121
122 def __init__(self, ui, repo):
122 def __init__(self, ui, repo):
123 self.ui = ui
123 self.ui = ui
124 self.repo = repo
124 self.repo = repo
125 self.matcher = util.matcher(repo.root,
125 self.matcher = util.matcher(repo.root,
126 inc=kwtools['inc'], exc=kwtools['exc'])[1]
126 inc=kwtools['inc'], exc=kwtools['exc'])[1]
127 self.restrict = kwtools['hgcmd'] in restricted.split()
127 self.restrict = kwtools['hgcmd'] in restricted.split()
128
128
129 kwmaps = self.ui.configitems('keywordmaps')
129 kwmaps = self.ui.configitems('keywordmaps')
130 if kwmaps: # override default templates
130 if kwmaps: # override default templates
131 kwmaps = [(k, templater.parsestring(v, False))
131 kwmaps = [(k, templater.parsestring(v, False))
132 for (k, v) in kwmaps]
132 for (k, v) in kwmaps]
133 self.templates = dict(kwmaps)
133 self.templates = dict(kwmaps)
134 escaped = map(re.escape, self.templates.keys())
134 escaped = map(re.escape, self.templates.keys())
135 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
135 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
136 self.re_kw = re.compile(kwpat)
136 self.re_kw = re.compile(kwpat)
137
137
138 templatefilters.filters['utcdate'] = utcdate
138 templatefilters.filters['utcdate'] = utcdate
139 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
139 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
140 False, '', False)
140 False, '', False)
141
141
142 def getnode(self, path, fnode):
142 def getnode(self, path, fnode):
143 '''Derives changenode from file path and filenode.'''
143 '''Derives changenode from file path and filenode.'''
144 # used by kwfilelog.read and kwexpand
144 # used by kwfilelog.read and kwexpand
145 c = context.filectx(self.repo, path, fileid=fnode)
145 c = context.filectx(self.repo, path, fileid=fnode)
146 return c.node()
146 return c.node()
147
147
148 def substitute(self, data, path, node, subfunc):
148 def substitute(self, data, path, node, subfunc):
149 '''Replaces keywords in data with expanded template.'''
149 '''Replaces keywords in data with expanded template.'''
150 def kwsub(mobj):
150 def kwsub(mobj):
151 kw = mobj.group(1)
151 kw = mobj.group(1)
152 self.ct.use_template(self.templates[kw])
152 self.ct.use_template(self.templates[kw])
153 self.ui.pushbuffer()
153 self.ui.pushbuffer()
154 self.ct.show(changenode=node, root=self.repo.root, file=path)
154 self.ct.show(changenode=node, root=self.repo.root, file=path)
155 ekw = templatefilters.firstline(self.ui.popbuffer())
155 ekw = templatefilters.firstline(self.ui.popbuffer())
156 return '$%s: %s $' % (kw, ekw)
156 return '$%s: %s $' % (kw, ekw)
157 return subfunc(kwsub, data)
157 return subfunc(kwsub, data)
158
158
159 def expand(self, path, node, data):
159 def expand(self, path, node, data):
160 '''Returns data with keywords expanded.'''
160 '''Returns data with keywords expanded.'''
161 if not self.restrict and self.matcher(path) and not util.binary(data):
161 if not self.restrict and self.matcher(path) and not util.binary(data):
162 changenode = self.getnode(path, node)
162 changenode = self.getnode(path, node)
163 return self.substitute(data, path, changenode, self.re_kw.sub)
163 return self.substitute(data, path, changenode, self.re_kw.sub)
164 return data
164 return data
165
165
166 def iskwfile(self, path, islink):
166 def iskwfile(self, path, islink):
167 '''Returns true if path matches [keyword] pattern
167 '''Returns true if path matches [keyword] pattern
168 and is not a symbolic link.
168 and is not a symbolic link.
169 Caveat: localrepository._link fails on Windows.'''
169 Caveat: localrepository._link fails on Windows.'''
170 return self.matcher(path) and not islink(path)
170 return self.matcher(path) and not islink(path)
171
171
172 def overwrite(self, node, expand, files):
172 def overwrite(self, node, expand, files):
173 '''Overwrites selected files expanding/shrinking keywords.'''
173 '''Overwrites selected files expanding/shrinking keywords.'''
174 ctx = self.repo.changectx(node)
174 ctx = self.repo.changectx(node)
175 mf = ctx.manifest()
175 mf = ctx.manifest()
176 if node is not None: # commit
176 if node is not None: # commit
177 files = [f for f in ctx.files() if f in mf]
177 files = [f for f in ctx.files() if f in mf]
178 notify = self.ui.debug
178 notify = self.ui.debug
179 else: # kwexpand/kwshrink
179 else: # kwexpand/kwshrink
180 notify = self.ui.note
180 notify = self.ui.note
181 candidates = [f for f in files if self.iskwfile(f, mf.linkf)]
181 candidates = [f for f in files if self.iskwfile(f, mf.linkf)]
182 if candidates:
182 if candidates:
183 self.restrict = True # do not expand when reading
183 self.restrict = True # do not expand when reading
184 candidates.sort()
184 candidates.sort()
185 action = expand and 'expanding' or 'shrinking'
185 action = expand and 'expanding' or 'shrinking'
186 for f in candidates:
186 for f in candidates:
187 fp = self.repo.file(f)
187 fp = self.repo.file(f)
188 data = fp.read(mf[f])
188 data = fp.read(mf[f])
189 if util.binary(data):
189 if util.binary(data):
190 continue
190 continue
191 if expand:
191 if expand:
192 changenode = node or self.getnode(f, mf[f])
192 changenode = node or self.getnode(f, mf[f])
193 data, found = self.substitute(data, f, changenode,
193 data, found = self.substitute(data, f, changenode,
194 self.re_kw.subn)
194 self.re_kw.subn)
195 else:
195 else:
196 found = self.re_kw.search(data)
196 found = self.re_kw.search(data)
197 if found:
197 if found:
198 notify(_('overwriting %s %s keywords\n') % (f, action))
198 notify(_('overwriting %s %s keywords\n') % (f, action))
199 self.repo.wwrite(f, data, mf.flags(f))
199 self.repo.wwrite(f, data, mf.flags(f))
200 self.repo.dirstate.normal(f)
200 self.repo.dirstate.normal(f)
201 self.restrict = False
201 self.restrict = False
202
202
203 def shrinktext(self, text):
203 def shrinktext(self, text):
204 '''Unconditionally removes all keyword substitutions from text.'''
204 '''Unconditionally removes all keyword substitutions from text.'''
205 return self.re_kw.sub(r'$\1$', text)
205 return self.re_kw.sub(r'$\1$', text)
206
206
207 def shrink(self, fname, text):
207 def shrink(self, fname, text):
208 '''Returns text with all keyword substitutions removed.'''
208 '''Returns text with all keyword substitutions removed.'''
209 if self.matcher(fname) and not util.binary(text):
209 if self.matcher(fname) and not util.binary(text):
210 return self.shrinktext(text)
210 return self.shrinktext(text)
211 return text
211 return text
212
212
213 def shrinklines(self, fname, lines):
213 def shrinklines(self, fname, lines):
214 '''Returns lines with keyword substitutions removed.'''
214 '''Returns lines with keyword substitutions removed.'''
215 if self.matcher(fname):
215 if self.matcher(fname):
216 text = ''.join(lines)
216 text = ''.join(lines)
217 if not util.binary(text):
217 if not util.binary(text):
218 return self.shrinktext(text).splitlines(True)
218 return self.shrinktext(text).splitlines(True)
219 return lines
219 return lines
220
220
221 def wread(self, fname, data):
221 def wread(self, fname, data):
222 '''If in restricted mode returns data read from wdir with
222 '''If in restricted mode returns data read from wdir with
223 keyword substitutions removed.'''
223 keyword substitutions removed.'''
224 return self.restrict and self.shrink(fname, data) or data
224 return self.restrict and self.shrink(fname, data) or data
225
225
226 class kwfilelog(filelog.filelog):
226 class kwfilelog(filelog.filelog):
227 '''
227 '''
228 Subclass of filelog to hook into its read, add, cmp methods.
228 Subclass of filelog to hook into its read, add, cmp methods.
229 Keywords are "stored" unexpanded, and processed on reading.
229 Keywords are "stored" unexpanded, and processed on reading.
230 '''
230 '''
231 def __init__(self, opener, kwt, path):
231 def __init__(self, opener, kwt, path):
232 super(kwfilelog, self).__init__(opener, path)
232 super(kwfilelog, self).__init__(opener, path)
233 self.kwt = kwt
233 self.kwt = kwt
234 self.path = path
234 self.path = path
235
235
236 def read(self, node):
236 def read(self, node):
237 '''Expands keywords when reading filelog.'''
237 '''Expands keywords when reading filelog.'''
238 data = super(kwfilelog, self).read(node)
238 data = super(kwfilelog, self).read(node)
239 return self.kwt.expand(self.path, node, data)
239 return self.kwt.expand(self.path, node, data)
240
240
241 def add(self, text, meta, tr, link, p1=None, p2=None):
241 def add(self, text, meta, tr, link, p1=None, p2=None):
242 '''Removes keyword substitutions when adding to filelog.'''
242 '''Removes keyword substitutions when adding to filelog.'''
243 text = self.kwt.shrink(self.path, text)
243 text = self.kwt.shrink(self.path, text)
244 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
244 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
245
245
246 def cmp(self, node, text):
246 def cmp(self, node, text):
247 '''Removes keyword substitutions for comparison.'''
247 '''Removes keyword substitutions for comparison.'''
248 text = self.kwt.shrink(self.path, text)
248 text = self.kwt.shrink(self.path, text)
249 if self.renamed(node):
249 if self.renamed(node):
250 t2 = super(kwfilelog, self).read(node)
250 t2 = super(kwfilelog, self).read(node)
251 return t2 != text
251 return t2 != text
252 return revlog.revlog.cmp(self, node, text)
252 return revlog.revlog.cmp(self, node, text)
253
253
254 def _status(ui, repo, kwt, *pats, **opts):
254 def _status(ui, repo, kwt, *pats, **opts):
255 '''Bails out if [keyword] configuration is not active.
255 '''Bails out if [keyword] configuration is not active.
256 Returns status of working directory.'''
256 Returns status of working directory.'''
257 if kwt:
257 if kwt:
258 matcher = cmdutil.match(repo, pats, opts)
258 matcher = cmdutil.match(repo, pats, opts)
259 return repo.status(files=matcher.files(), match=matcher, list_clean=True)
259 return repo.status(match=matcher, list_clean=True)
260 if ui.configitems('keyword'):
260 if ui.configitems('keyword'):
261 raise util.Abort(_('[keyword] patterns cannot match'))
261 raise util.Abort(_('[keyword] patterns cannot match'))
262 raise util.Abort(_('no [keyword] patterns configured'))
262 raise util.Abort(_('no [keyword] patterns configured'))
263
263
264 def _kwfwrite(ui, repo, expand, *pats, **opts):
264 def _kwfwrite(ui, repo, expand, *pats, **opts):
265 '''Selects files and passes them to kwtemplater.overwrite.'''
265 '''Selects files and passes them to kwtemplater.overwrite.'''
266 kwt = kwtools['templater']
266 kwt = kwtools['templater']
267 status = _status(ui, repo, kwt, *pats, **opts)
267 status = _status(ui, repo, kwt, *pats, **opts)
268 modified, added, removed, deleted, unknown, ignored, clean = status
268 modified, added, removed, deleted, unknown, ignored, clean = status
269 if modified or added or removed or deleted:
269 if modified or added or removed or deleted:
270 raise util.Abort(_('outstanding uncommitted changes in given files'))
270 raise util.Abort(_('outstanding uncommitted changes in given files'))
271 wlock = lock = None
271 wlock = lock = None
272 try:
272 try:
273 wlock = repo.wlock()
273 wlock = repo.wlock()
274 lock = repo.lock()
274 lock = repo.lock()
275 kwt.overwrite(None, expand, clean)
275 kwt.overwrite(None, expand, clean)
276 finally:
276 finally:
277 del wlock, lock
277 del wlock, lock
278
278
279
279
280 def demo(ui, repo, *args, **opts):
280 def demo(ui, repo, *args, **opts):
281 '''print [keywordmaps] configuration and an expansion example
281 '''print [keywordmaps] configuration and an expansion example
282
282
283 Show current, custom, or default keyword template maps
283 Show current, custom, or default keyword template maps
284 and their expansion.
284 and their expansion.
285
285
286 Extend current configuration by specifying maps as arguments
286 Extend current configuration by specifying maps as arguments
287 and optionally by reading from an additional hgrc file.
287 and optionally by reading from an additional hgrc file.
288
288
289 Override current keyword template maps with "default" option.
289 Override current keyword template maps with "default" option.
290 '''
290 '''
291 def demostatus(stat):
291 def demostatus(stat):
292 ui.status(_('\n\t%s\n') % stat)
292 ui.status(_('\n\t%s\n') % stat)
293
293
294 def demoitems(section, items):
294 def demoitems(section, items):
295 ui.write('[%s]\n' % section)
295 ui.write('[%s]\n' % section)
296 for k, v in items:
296 for k, v in items:
297 ui.write('%s = %s\n' % (k, v))
297 ui.write('%s = %s\n' % (k, v))
298
298
299 msg = 'hg keyword config and expansion example'
299 msg = 'hg keyword config and expansion example'
300 kwstatus = 'current'
300 kwstatus = 'current'
301 fn = 'demo.txt'
301 fn = 'demo.txt'
302 branchname = 'demobranch'
302 branchname = 'demobranch'
303 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
303 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
304 ui.note(_('creating temporary repo at %s\n') % tmpdir)
304 ui.note(_('creating temporary repo at %s\n') % tmpdir)
305 repo = localrepo.localrepository(ui, tmpdir, True)
305 repo = localrepo.localrepository(ui, tmpdir, True)
306 ui.setconfig('keyword', fn, '')
306 ui.setconfig('keyword', fn, '')
307 if args or opts.get('rcfile'):
307 if args or opts.get('rcfile'):
308 kwstatus = 'custom'
308 kwstatus = 'custom'
309 if opts.get('rcfile'):
309 if opts.get('rcfile'):
310 ui.readconfig(opts.get('rcfile'))
310 ui.readconfig(opts.get('rcfile'))
311 if opts.get('default'):
311 if opts.get('default'):
312 kwstatus = 'default'
312 kwstatus = 'default'
313 kwmaps = kwtemplater.templates
313 kwmaps = kwtemplater.templates
314 if ui.configitems('keywordmaps'):
314 if ui.configitems('keywordmaps'):
315 # override maps from optional rcfile
315 # override maps from optional rcfile
316 for k, v in kwmaps.iteritems():
316 for k, v in kwmaps.iteritems():
317 ui.setconfig('keywordmaps', k, v)
317 ui.setconfig('keywordmaps', k, v)
318 elif args:
318 elif args:
319 # simulate hgrc parsing
319 # simulate hgrc parsing
320 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
320 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
321 fp = repo.opener('hgrc', 'w')
321 fp = repo.opener('hgrc', 'w')
322 fp.writelines(rcmaps)
322 fp.writelines(rcmaps)
323 fp.close()
323 fp.close()
324 ui.readconfig(repo.join('hgrc'))
324 ui.readconfig(repo.join('hgrc'))
325 if not opts.get('default'):
325 if not opts.get('default'):
326 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
326 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
327 uisetup(ui)
327 uisetup(ui)
328 reposetup(ui, repo)
328 reposetup(ui, repo)
329 for k, v in ui.configitems('extensions'):
329 for k, v in ui.configitems('extensions'):
330 if k.endswith('keyword'):
330 if k.endswith('keyword'):
331 extension = '%s = %s' % (k, v)
331 extension = '%s = %s' % (k, v)
332 break
332 break
333 demostatus('config using %s keyword template maps' % kwstatus)
333 demostatus('config using %s keyword template maps' % kwstatus)
334 ui.write('[extensions]\n%s\n' % extension)
334 ui.write('[extensions]\n%s\n' % extension)
335 demoitems('keyword', ui.configitems('keyword'))
335 demoitems('keyword', ui.configitems('keyword'))
336 demoitems('keywordmaps', kwmaps.iteritems())
336 demoitems('keywordmaps', kwmaps.iteritems())
337 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
337 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
338 repo.wopener(fn, 'w').write(keywords)
338 repo.wopener(fn, 'w').write(keywords)
339 repo.add([fn])
339 repo.add([fn])
340 path = repo.wjoin(fn)
340 path = repo.wjoin(fn)
341 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
341 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
342 ui.note(keywords)
342 ui.note(keywords)
343 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
343 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
344 # silence branch command if not verbose
344 # silence branch command if not verbose
345 quiet = ui.quiet
345 quiet = ui.quiet
346 ui.quiet = not ui.verbose
346 ui.quiet = not ui.verbose
347 commands.branch(ui, repo, branchname)
347 commands.branch(ui, repo, branchname)
348 ui.quiet = quiet
348 ui.quiet = quiet
349 for name, cmd in ui.configitems('hooks'):
349 for name, cmd in ui.configitems('hooks'):
350 if name.split('.', 1)[0].find('commit') > -1:
350 if name.split('.', 1)[0].find('commit') > -1:
351 repo.ui.setconfig('hooks', name, '')
351 repo.ui.setconfig('hooks', name, '')
352 ui.note(_('unhooked all commit hooks\n'))
352 ui.note(_('unhooked all commit hooks\n'))
353 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
353 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
354 repo.commit(text=msg)
354 repo.commit(text=msg)
355 format = ui.verbose and ' in %s' % path or ''
355 format = ui.verbose and ' in %s' % path or ''
356 demostatus('%s keywords expanded%s' % (kwstatus, format))
356 demostatus('%s keywords expanded%s' % (kwstatus, format))
357 ui.write(repo.wread(fn))
357 ui.write(repo.wread(fn))
358 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
358 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
359 shutil.rmtree(tmpdir, ignore_errors=True)
359 shutil.rmtree(tmpdir, ignore_errors=True)
360
360
361 def expand(ui, repo, *pats, **opts):
361 def expand(ui, repo, *pats, **opts):
362 '''expand keywords in working directory
362 '''expand keywords in working directory
363
363
364 Run after (re)enabling keyword expansion.
364 Run after (re)enabling keyword expansion.
365
365
366 kwexpand refuses to run if given files contain local changes.
366 kwexpand refuses to run if given files contain local changes.
367 '''
367 '''
368 # 3rd argument sets expansion to True
368 # 3rd argument sets expansion to True
369 _kwfwrite(ui, repo, True, *pats, **opts)
369 _kwfwrite(ui, repo, True, *pats, **opts)
370
370
371 def files(ui, repo, *pats, **opts):
371 def files(ui, repo, *pats, **opts):
372 '''print files currently configured for keyword expansion
372 '''print files currently configured for keyword expansion
373
373
374 Crosscheck which files in working directory are potential targets for
374 Crosscheck which files in working directory are potential targets for
375 keyword expansion.
375 keyword expansion.
376 That is, files matched by [keyword] config patterns but not symlinks.
376 That is, files matched by [keyword] config patterns but not symlinks.
377 '''
377 '''
378 kwt = kwtools['templater']
378 kwt = kwtools['templater']
379 status = _status(ui, repo, kwt, *pats, **opts)
379 status = _status(ui, repo, kwt, *pats, **opts)
380 modified, added, removed, deleted, unknown, ignored, clean = status
380 modified, added, removed, deleted, unknown, ignored, clean = status
381 files = modified + added + clean
381 files = modified + added + clean
382 if opts.get('untracked'):
382 if opts.get('untracked'):
383 files += unknown
383 files += unknown
384 files.sort()
384 files.sort()
385 wctx = repo.workingctx()
385 wctx = repo.workingctx()
386 islink = lambda p: 'l' in wctx.fileflags(p)
386 islink = lambda p: 'l' in wctx.fileflags(p)
387 kwfiles = [f for f in files if kwt.iskwfile(f, islink)]
387 kwfiles = [f for f in files if kwt.iskwfile(f, islink)]
388 cwd = pats and repo.getcwd() or ''
388 cwd = pats and repo.getcwd() or ''
389 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
389 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
390 if opts.get('all') or opts.get('ignore'):
390 if opts.get('all') or opts.get('ignore'):
391 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
391 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
392 for char, filenames in kwfstats:
392 for char, filenames in kwfstats:
393 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
393 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
394 for f in filenames:
394 for f in filenames:
395 ui.write(format % repo.pathto(f, cwd))
395 ui.write(format % repo.pathto(f, cwd))
396
396
397 def shrink(ui, repo, *pats, **opts):
397 def shrink(ui, repo, *pats, **opts):
398 '''revert expanded keywords in working directory
398 '''revert expanded keywords in working directory
399
399
400 Run before changing/disabling active keywords
400 Run before changing/disabling active keywords
401 or if you experience problems with "hg import" or "hg merge".
401 or if you experience problems with "hg import" or "hg merge".
402
402
403 kwshrink refuses to run if given files contain local changes.
403 kwshrink refuses to run if given files contain local changes.
404 '''
404 '''
405 # 3rd argument sets expansion to False
405 # 3rd argument sets expansion to False
406 _kwfwrite(ui, repo, False, *pats, **opts)
406 _kwfwrite(ui, repo, False, *pats, **opts)
407
407
408
408
409 def uisetup(ui):
409 def uisetup(ui):
410 '''Collects [keyword] config in kwtools.
410 '''Collects [keyword] config in kwtools.
411 Monkeypatches dispatch._parse if needed.'''
411 Monkeypatches dispatch._parse if needed.'''
412
412
413 for pat, opt in ui.configitems('keyword'):
413 for pat, opt in ui.configitems('keyword'):
414 if opt != 'ignore':
414 if opt != 'ignore':
415 kwtools['inc'].append(pat)
415 kwtools['inc'].append(pat)
416 else:
416 else:
417 kwtools['exc'].append(pat)
417 kwtools['exc'].append(pat)
418
418
419 if kwtools['inc']:
419 if kwtools['inc']:
420 def kwdispatch_parse(ui, args):
420 def kwdispatch_parse(ui, args):
421 '''Monkeypatch dispatch._parse to obtain running hg command.'''
421 '''Monkeypatch dispatch._parse to obtain running hg command.'''
422 cmd, func, args, options, cmdoptions = dispatch_parse(ui, args)
422 cmd, func, args, options, cmdoptions = dispatch_parse(ui, args)
423 kwtools['hgcmd'] = cmd
423 kwtools['hgcmd'] = cmd
424 return cmd, func, args, options, cmdoptions
424 return cmd, func, args, options, cmdoptions
425
425
426 dispatch_parse = dispatch._parse
426 dispatch_parse = dispatch._parse
427 dispatch._parse = kwdispatch_parse
427 dispatch._parse = kwdispatch_parse
428
428
429 def reposetup(ui, repo):
429 def reposetup(ui, repo):
430 '''Sets up repo as kwrepo for keyword substitution.
430 '''Sets up repo as kwrepo for keyword substitution.
431 Overrides file method to return kwfilelog instead of filelog
431 Overrides file method to return kwfilelog instead of filelog
432 if file matches user configuration.
432 if file matches user configuration.
433 Wraps commit to overwrite configured files with updated
433 Wraps commit to overwrite configured files with updated
434 keyword substitutions.
434 keyword substitutions.
435 Monkeypatches patch and webcommands.'''
435 Monkeypatches patch and webcommands.'''
436
436
437 try:
437 try:
438 if (not repo.local() or not kwtools['inc']
438 if (not repo.local() or not kwtools['inc']
439 or kwtools['hgcmd'] in nokwcommands.split()
439 or kwtools['hgcmd'] in nokwcommands.split()
440 or '.hg' in util.splitpath(repo.root)
440 or '.hg' in util.splitpath(repo.root)
441 or repo._url.startswith('bundle:')):
441 or repo._url.startswith('bundle:')):
442 return
442 return
443 except AttributeError:
443 except AttributeError:
444 pass
444 pass
445
445
446 kwtools['templater'] = kwt = kwtemplater(ui, repo)
446 kwtools['templater'] = kwt = kwtemplater(ui, repo)
447
447
448 class kwrepo(repo.__class__):
448 class kwrepo(repo.__class__):
449 def file(self, f):
449 def file(self, f):
450 if f[0] == '/':
450 if f[0] == '/':
451 f = f[1:]
451 f = f[1:]
452 return kwfilelog(self.sopener, kwt, f)
452 return kwfilelog(self.sopener, kwt, f)
453
453
454 def wread(self, filename):
454 def wread(self, filename):
455 data = super(kwrepo, self).wread(filename)
455 data = super(kwrepo, self).wread(filename)
456 return kwt.wread(filename, data)
456 return kwt.wread(filename, data)
457
457
458 def commit(self, files=None, text='', user=None, date=None,
458 def commit(self, files=None, text='', user=None, date=None,
459 match=util.always, force=False, force_editor=False,
459 match=None, force=False, force_editor=False,
460 p1=None, p2=None, extra={}, empty_ok=False):
460 p1=None, p2=None, extra={}, empty_ok=False):
461 wlock = lock = None
461 wlock = lock = None
462 _p1 = _p2 = None
462 _p1 = _p2 = None
463 try:
463 try:
464 wlock = self.wlock()
464 wlock = self.wlock()
465 lock = self.lock()
465 lock = self.lock()
466 # store and postpone commit hooks
466 # store and postpone commit hooks
467 commithooks = {}
467 commithooks = {}
468 for name, cmd in ui.configitems('hooks'):
468 for name, cmd in ui.configitems('hooks'):
469 if name.split('.', 1)[0] == 'commit':
469 if name.split('.', 1)[0] == 'commit':
470 commithooks[name] = cmd
470 commithooks[name] = cmd
471 ui.setconfig('hooks', name, None)
471 ui.setconfig('hooks', name, None)
472 if commithooks:
472 if commithooks:
473 # store parents for commit hook environment
473 # store parents for commit hook environment
474 if p1 is None:
474 if p1 is None:
475 _p1, _p2 = repo.dirstate.parents()
475 _p1, _p2 = repo.dirstate.parents()
476 else:
476 else:
477 _p1, _p2 = p1, p2 or nullid
477 _p1, _p2 = p1, p2 or nullid
478 _p1 = hex(_p1)
478 _p1 = hex(_p1)
479 if _p2 == nullid:
479 if _p2 == nullid:
480 _p2 = ''
480 _p2 = ''
481 else:
481 else:
482 _p2 = hex(_p2)
482 _p2 = hex(_p2)
483
483
484 n = super(kwrepo, self).commit(files, text, user, date, match,
484 n = super(kwrepo, self).commit(files, text, user, date, match,
485 force, force_editor, p1, p2,
485 force, force_editor, p1, p2,
486 extra, empty_ok)
486 extra, empty_ok)
487
487
488 # restore commit hooks
488 # restore commit hooks
489 for name, cmd in commithooks.iteritems():
489 for name, cmd in commithooks.iteritems():
490 ui.setconfig('hooks', name, cmd)
490 ui.setconfig('hooks', name, cmd)
491 if n is not None:
491 if n is not None:
492 kwt.overwrite(n, True, None)
492 kwt.overwrite(n, True, None)
493 repo.hook('commit', node=n, parent1=_p1, parent2=_p2)
493 repo.hook('commit', node=n, parent1=_p1, parent2=_p2)
494 return n
494 return n
495 finally:
495 finally:
496 del wlock, lock
496 del wlock, lock
497
497
498 # monkeypatches
498 # monkeypatches
499 def kwpatchfile_init(self, ui, fname, missing=False):
499 def kwpatchfile_init(self, ui, fname, missing=False):
500 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
500 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
501 rejects or conflicts due to expanded keywords in working dir.'''
501 rejects or conflicts due to expanded keywords in working dir.'''
502 patchfile_init(self, ui, fname, missing)
502 patchfile_init(self, ui, fname, missing)
503 # shrink keywords read from working dir
503 # shrink keywords read from working dir
504 self.lines = kwt.shrinklines(self.fname, self.lines)
504 self.lines = kwt.shrinklines(self.fname, self.lines)
505
505
506 def kw_diff(repo, node1=None, node2=None, match=None,
506 def kw_diff(repo, node1=None, node2=None, match=None,
507 fp=None, changes=None, opts=None):
507 fp=None, changes=None, opts=None):
508 '''Monkeypatch patch.diff to avoid expansion except when
508 '''Monkeypatch patch.diff to avoid expansion except when
509 comparing against working dir.'''
509 comparing against working dir.'''
510 if node2 is not None:
510 if node2 is not None:
511 kwt.matcher = util.never
511 kwt.matcher = util.never
512 elif node1 is not None and node1 != repo.changectx().node():
512 elif node1 is not None and node1 != repo.changectx().node():
513 kwt.restrict = True
513 kwt.restrict = True
514 patch_diff(repo, node1, node2, match, fp, changes, opts)
514 patch_diff(repo, node1, node2, match, fp, changes, opts)
515
515
516 def kwweb_changeset(web, req, tmpl):
516 def kwweb_changeset(web, req, tmpl):
517 '''Wraps webcommands.changeset turning off keyword expansion.'''
517 '''Wraps webcommands.changeset turning off keyword expansion.'''
518 kwt.matcher = util.never
518 kwt.matcher = util.never
519 return webcommands_changeset(web, req, tmpl)
519 return webcommands_changeset(web, req, tmpl)
520
520
521 def kwweb_filediff(web, req, tmpl):
521 def kwweb_filediff(web, req, tmpl):
522 '''Wraps webcommands.filediff turning off keyword expansion.'''
522 '''Wraps webcommands.filediff turning off keyword expansion.'''
523 kwt.matcher = util.never
523 kwt.matcher = util.never
524 return webcommands_filediff(web, req, tmpl)
524 return webcommands_filediff(web, req, tmpl)
525
525
526 repo.__class__ = kwrepo
526 repo.__class__ = kwrepo
527
527
528 patchfile_init = patch.patchfile.__init__
528 patchfile_init = patch.patchfile.__init__
529 patch_diff = patch.diff
529 patch_diff = patch.diff
530 webcommands_changeset = webcommands.changeset
530 webcommands_changeset = webcommands.changeset
531 webcommands_filediff = webcommands.filediff
531 webcommands_filediff = webcommands.filediff
532
532
533 patch.patchfile.__init__ = kwpatchfile_init
533 patch.patchfile.__init__ = kwpatchfile_init
534 patch.diff = kw_diff
534 patch.diff = kw_diff
535 webcommands.changeset = webcommands.rev = kwweb_changeset
535 webcommands.changeset = webcommands.rev = kwweb_changeset
536 webcommands.filediff = webcommands.diff = kwweb_filediff
536 webcommands.filediff = webcommands.diff = kwweb_filediff
537
537
538
538
539 cmdtable = {
539 cmdtable = {
540 'kwdemo':
540 'kwdemo':
541 (demo,
541 (demo,
542 [('d', 'default', None, _('show default keyword template maps')),
542 [('d', 'default', None, _('show default keyword template maps')),
543 ('f', 'rcfile', [], _('read maps from rcfile'))],
543 ('f', 'rcfile', [], _('read maps from rcfile'))],
544 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
544 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
545 'kwexpand': (expand, commands.walkopts,
545 'kwexpand': (expand, commands.walkopts,
546 _('hg kwexpand [OPTION]... [FILE]...')),
546 _('hg kwexpand [OPTION]... [FILE]...')),
547 'kwfiles':
547 'kwfiles':
548 (files,
548 (files,
549 [('a', 'all', None, _('show keyword status flags of all files')),
549 [('a', 'all', None, _('show keyword status flags of all files')),
550 ('i', 'ignore', None, _('show files excluded from expansion')),
550 ('i', 'ignore', None, _('show files excluded from expansion')),
551 ('u', 'untracked', None, _('additionally show untracked files')),
551 ('u', 'untracked', None, _('additionally show untracked files')),
552 ] + commands.walkopts,
552 ] + commands.walkopts,
553 _('hg kwfiles [OPTION]... [FILE]...')),
553 _('hg kwfiles [OPTION]... [FILE]...')),
554 'kwshrink': (shrink, commands.walkopts,
554 'kwshrink': (shrink, commands.walkopts,
555 _('hg kwshrink [OPTION]... [FILE]...')),
555 _('hg kwshrink [OPTION]... [FILE]...')),
556 }
556 }
@@ -1,2363 +1,2365 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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 '''patch management and development
8 '''patch management and development
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 prepare repository to work with patches qinit
19 prepare repository to work with patches qinit
20 create new patch qnew
20 create new patch qnew
21 import existing patch qimport
21 import existing patch qimport
22
22
23 print patch series qseries
23 print patch series qseries
24 print applied patches qapplied
24 print applied patches qapplied
25 print name of top applied patch qtop
25 print name of top applied patch qtop
26
26
27 add known patch to applied stack qpush
27 add known patch to applied stack qpush
28 remove patch from applied stack qpop
28 remove patch from applied stack qpop
29 refresh contents of top applied patch qrefresh
29 refresh contents of top applied patch qrefresh
30 '''
30 '''
31
31
32 from mercurial.i18n import _
32 from mercurial.i18n import _
33 from mercurial.node import bin, hex, short
33 from mercurial.node import bin, hex, short
34 from mercurial.repo import RepoError
34 from mercurial.repo import RepoError
35 from mercurial import commands, cmdutil, hg, patch, revlog, util
35 from mercurial import commands, cmdutil, hg, patch, revlog, util
36 from mercurial import repair
36 from mercurial import repair
37 import os, sys, re, errno
37 import os, sys, re, errno
38
38
39 commands.norepo += " qclone"
39 commands.norepo += " qclone"
40
40
41 # Patch names looks like unix-file names.
41 # Patch names looks like unix-file names.
42 # They must be joinable with queue directory and result in the patch path.
42 # They must be joinable with queue directory and result in the patch path.
43 normname = util.normpath
43 normname = util.normpath
44
44
45 class statusentry:
45 class statusentry:
46 def __init__(self, rev, name=None):
46 def __init__(self, rev, name=None):
47 if not name:
47 if not name:
48 fields = rev.split(':', 1)
48 fields = rev.split(':', 1)
49 if len(fields) == 2:
49 if len(fields) == 2:
50 self.rev, self.name = fields
50 self.rev, self.name = fields
51 else:
51 else:
52 self.rev, self.name = None, None
52 self.rev, self.name = None, None
53 else:
53 else:
54 self.rev, self.name = rev, name
54 self.rev, self.name = rev, name
55
55
56 def __str__(self):
56 def __str__(self):
57 return self.rev + ':' + self.name
57 return self.rev + ':' + self.name
58
58
59 class queue:
59 class queue:
60 def __init__(self, ui, path, patchdir=None):
60 def __init__(self, ui, path, patchdir=None):
61 self.basepath = path
61 self.basepath = path
62 self.path = patchdir or os.path.join(path, "patches")
62 self.path = patchdir or os.path.join(path, "patches")
63 self.opener = util.opener(self.path)
63 self.opener = util.opener(self.path)
64 self.ui = ui
64 self.ui = ui
65 self.applied = []
65 self.applied = []
66 self.full_series = []
66 self.full_series = []
67 self.applied_dirty = 0
67 self.applied_dirty = 0
68 self.series_dirty = 0
68 self.series_dirty = 0
69 self.series_path = "series"
69 self.series_path = "series"
70 self.status_path = "status"
70 self.status_path = "status"
71 self.guards_path = "guards"
71 self.guards_path = "guards"
72 self.active_guards = None
72 self.active_guards = None
73 self.guards_dirty = False
73 self.guards_dirty = False
74 self._diffopts = None
74 self._diffopts = None
75
75
76 if os.path.exists(self.join(self.series_path)):
76 if os.path.exists(self.join(self.series_path)):
77 self.full_series = self.opener(self.series_path).read().splitlines()
77 self.full_series = self.opener(self.series_path).read().splitlines()
78 self.parse_series()
78 self.parse_series()
79
79
80 if os.path.exists(self.join(self.status_path)):
80 if os.path.exists(self.join(self.status_path)):
81 lines = self.opener(self.status_path).read().splitlines()
81 lines = self.opener(self.status_path).read().splitlines()
82 self.applied = [statusentry(l) for l in lines]
82 self.applied = [statusentry(l) for l in lines]
83
83
84 def diffopts(self):
84 def diffopts(self):
85 if self._diffopts is None:
85 if self._diffopts is None:
86 self._diffopts = patch.diffopts(self.ui)
86 self._diffopts = patch.diffopts(self.ui)
87 return self._diffopts
87 return self._diffopts
88
88
89 def join(self, *p):
89 def join(self, *p):
90 return os.path.join(self.path, *p)
90 return os.path.join(self.path, *p)
91
91
92 def find_series(self, patch):
92 def find_series(self, patch):
93 pre = re.compile("(\s*)([^#]+)")
93 pre = re.compile("(\s*)([^#]+)")
94 index = 0
94 index = 0
95 for l in self.full_series:
95 for l in self.full_series:
96 m = pre.match(l)
96 m = pre.match(l)
97 if m:
97 if m:
98 s = m.group(2)
98 s = m.group(2)
99 s = s.rstrip()
99 s = s.rstrip()
100 if s == patch:
100 if s == patch:
101 return index
101 return index
102 index += 1
102 index += 1
103 return None
103 return None
104
104
105 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
105 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
106
106
107 def parse_series(self):
107 def parse_series(self):
108 self.series = []
108 self.series = []
109 self.series_guards = []
109 self.series_guards = []
110 for l in self.full_series:
110 for l in self.full_series:
111 h = l.find('#')
111 h = l.find('#')
112 if h == -1:
112 if h == -1:
113 patch = l
113 patch = l
114 comment = ''
114 comment = ''
115 elif h == 0:
115 elif h == 0:
116 continue
116 continue
117 else:
117 else:
118 patch = l[:h]
118 patch = l[:h]
119 comment = l[h:]
119 comment = l[h:]
120 patch = patch.strip()
120 patch = patch.strip()
121 if patch:
121 if patch:
122 if patch in self.series:
122 if patch in self.series:
123 raise util.Abort(_('%s appears more than once in %s') %
123 raise util.Abort(_('%s appears more than once in %s') %
124 (patch, self.join(self.series_path)))
124 (patch, self.join(self.series_path)))
125 self.series.append(patch)
125 self.series.append(patch)
126 self.series_guards.append(self.guard_re.findall(comment))
126 self.series_guards.append(self.guard_re.findall(comment))
127
127
128 def check_guard(self, guard):
128 def check_guard(self, guard):
129 bad_chars = '# \t\r\n\f'
129 bad_chars = '# \t\r\n\f'
130 first = guard[0]
130 first = guard[0]
131 for c in '-+':
131 for c in '-+':
132 if first == c:
132 if first == c:
133 return (_('guard %r starts with invalid character: %r') %
133 return (_('guard %r starts with invalid character: %r') %
134 (guard, c))
134 (guard, c))
135 for c in bad_chars:
135 for c in bad_chars:
136 if c in guard:
136 if c in guard:
137 return _('invalid character in guard %r: %r') % (guard, c)
137 return _('invalid character in guard %r: %r') % (guard, c)
138
138
139 def set_active(self, guards):
139 def set_active(self, guards):
140 for guard in guards:
140 for guard in guards:
141 bad = self.check_guard(guard)
141 bad = self.check_guard(guard)
142 if bad:
142 if bad:
143 raise util.Abort(bad)
143 raise util.Abort(bad)
144 guards = dict.fromkeys(guards).keys()
144 guards = dict.fromkeys(guards).keys()
145 guards.sort()
145 guards.sort()
146 self.ui.debug('active guards: %s\n' % ' '.join(guards))
146 self.ui.debug('active guards: %s\n' % ' '.join(guards))
147 self.active_guards = guards
147 self.active_guards = guards
148 self.guards_dirty = True
148 self.guards_dirty = True
149
149
150 def active(self):
150 def active(self):
151 if self.active_guards is None:
151 if self.active_guards is None:
152 self.active_guards = []
152 self.active_guards = []
153 try:
153 try:
154 guards = self.opener(self.guards_path).read().split()
154 guards = self.opener(self.guards_path).read().split()
155 except IOError, err:
155 except IOError, err:
156 if err.errno != errno.ENOENT: raise
156 if err.errno != errno.ENOENT: raise
157 guards = []
157 guards = []
158 for i, guard in enumerate(guards):
158 for i, guard in enumerate(guards):
159 bad = self.check_guard(guard)
159 bad = self.check_guard(guard)
160 if bad:
160 if bad:
161 self.ui.warn('%s:%d: %s\n' %
161 self.ui.warn('%s:%d: %s\n' %
162 (self.join(self.guards_path), i + 1, bad))
162 (self.join(self.guards_path), i + 1, bad))
163 else:
163 else:
164 self.active_guards.append(guard)
164 self.active_guards.append(guard)
165 return self.active_guards
165 return self.active_guards
166
166
167 def set_guards(self, idx, guards):
167 def set_guards(self, idx, guards):
168 for g in guards:
168 for g in guards:
169 if len(g) < 2:
169 if len(g) < 2:
170 raise util.Abort(_('guard %r too short') % g)
170 raise util.Abort(_('guard %r too short') % g)
171 if g[0] not in '-+':
171 if g[0] not in '-+':
172 raise util.Abort(_('guard %r starts with invalid char') % g)
172 raise util.Abort(_('guard %r starts with invalid char') % g)
173 bad = self.check_guard(g[1:])
173 bad = self.check_guard(g[1:])
174 if bad:
174 if bad:
175 raise util.Abort(bad)
175 raise util.Abort(bad)
176 drop = self.guard_re.sub('', self.full_series[idx])
176 drop = self.guard_re.sub('', self.full_series[idx])
177 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
177 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
178 self.parse_series()
178 self.parse_series()
179 self.series_dirty = True
179 self.series_dirty = True
180
180
181 def pushable(self, idx):
181 def pushable(self, idx):
182 if isinstance(idx, str):
182 if isinstance(idx, str):
183 idx = self.series.index(idx)
183 idx = self.series.index(idx)
184 patchguards = self.series_guards[idx]
184 patchguards = self.series_guards[idx]
185 if not patchguards:
185 if not patchguards:
186 return True, None
186 return True, None
187 default = False
187 default = False
188 guards = self.active()
188 guards = self.active()
189 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
189 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
190 if exactneg:
190 if exactneg:
191 return False, exactneg[0]
191 return False, exactneg[0]
192 pos = [g for g in patchguards if g[0] == '+']
192 pos = [g for g in patchguards if g[0] == '+']
193 exactpos = [g for g in pos if g[1:] in guards]
193 exactpos = [g for g in pos if g[1:] in guards]
194 if pos:
194 if pos:
195 if exactpos:
195 if exactpos:
196 return True, exactpos[0]
196 return True, exactpos[0]
197 return False, pos
197 return False, pos
198 return True, ''
198 return True, ''
199
199
200 def explain_pushable(self, idx, all_patches=False):
200 def explain_pushable(self, idx, all_patches=False):
201 write = all_patches and self.ui.write or self.ui.warn
201 write = all_patches and self.ui.write or self.ui.warn
202 if all_patches or self.ui.verbose:
202 if all_patches or self.ui.verbose:
203 if isinstance(idx, str):
203 if isinstance(idx, str):
204 idx = self.series.index(idx)
204 idx = self.series.index(idx)
205 pushable, why = self.pushable(idx)
205 pushable, why = self.pushable(idx)
206 if all_patches and pushable:
206 if all_patches and pushable:
207 if why is None:
207 if why is None:
208 write(_('allowing %s - no guards in effect\n') %
208 write(_('allowing %s - no guards in effect\n') %
209 self.series[idx])
209 self.series[idx])
210 else:
210 else:
211 if not why:
211 if not why:
212 write(_('allowing %s - no matching negative guards\n') %
212 write(_('allowing %s - no matching negative guards\n') %
213 self.series[idx])
213 self.series[idx])
214 else:
214 else:
215 write(_('allowing %s - guarded by %r\n') %
215 write(_('allowing %s - guarded by %r\n') %
216 (self.series[idx], why))
216 (self.series[idx], why))
217 if not pushable:
217 if not pushable:
218 if why:
218 if why:
219 write(_('skipping %s - guarded by %r\n') %
219 write(_('skipping %s - guarded by %r\n') %
220 (self.series[idx], why))
220 (self.series[idx], why))
221 else:
221 else:
222 write(_('skipping %s - no matching guards\n') %
222 write(_('skipping %s - no matching guards\n') %
223 self.series[idx])
223 self.series[idx])
224
224
225 def save_dirty(self):
225 def save_dirty(self):
226 def write_list(items, path):
226 def write_list(items, path):
227 fp = self.opener(path, 'w')
227 fp = self.opener(path, 'w')
228 for i in items:
228 for i in items:
229 fp.write("%s\n" % i)
229 fp.write("%s\n" % i)
230 fp.close()
230 fp.close()
231 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
231 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
232 if self.series_dirty: write_list(self.full_series, self.series_path)
232 if self.series_dirty: write_list(self.full_series, self.series_path)
233 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
233 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
234
234
235 def readheaders(self, patch):
235 def readheaders(self, patch):
236 def eatdiff(lines):
236 def eatdiff(lines):
237 while lines:
237 while lines:
238 l = lines[-1]
238 l = lines[-1]
239 if (l.startswith("diff -") or
239 if (l.startswith("diff -") or
240 l.startswith("Index:") or
240 l.startswith("Index:") or
241 l.startswith("===========")):
241 l.startswith("===========")):
242 del lines[-1]
242 del lines[-1]
243 else:
243 else:
244 break
244 break
245 def eatempty(lines):
245 def eatempty(lines):
246 while lines:
246 while lines:
247 l = lines[-1]
247 l = lines[-1]
248 if re.match('\s*$', l):
248 if re.match('\s*$', l):
249 del lines[-1]
249 del lines[-1]
250 else:
250 else:
251 break
251 break
252
252
253 pf = self.join(patch)
253 pf = self.join(patch)
254 message = []
254 message = []
255 comments = []
255 comments = []
256 user = None
256 user = None
257 date = None
257 date = None
258 format = None
258 format = None
259 subject = None
259 subject = None
260 diffstart = 0
260 diffstart = 0
261
261
262 for line in file(pf):
262 for line in file(pf):
263 line = line.rstrip()
263 line = line.rstrip()
264 if line.startswith('diff --git'):
264 if line.startswith('diff --git'):
265 diffstart = 2
265 diffstart = 2
266 break
266 break
267 if diffstart:
267 if diffstart:
268 if line.startswith('+++ '):
268 if line.startswith('+++ '):
269 diffstart = 2
269 diffstart = 2
270 break
270 break
271 if line.startswith("--- "):
271 if line.startswith("--- "):
272 diffstart = 1
272 diffstart = 1
273 continue
273 continue
274 elif format == "hgpatch":
274 elif format == "hgpatch":
275 # parse values when importing the result of an hg export
275 # parse values when importing the result of an hg export
276 if line.startswith("# User "):
276 if line.startswith("# User "):
277 user = line[7:]
277 user = line[7:]
278 elif line.startswith("# Date "):
278 elif line.startswith("# Date "):
279 date = line[7:]
279 date = line[7:]
280 elif not line.startswith("# ") and line:
280 elif not line.startswith("# ") and line:
281 message.append(line)
281 message.append(line)
282 format = None
282 format = None
283 elif line == '# HG changeset patch':
283 elif line == '# HG changeset patch':
284 format = "hgpatch"
284 format = "hgpatch"
285 elif (format != "tagdone" and (line.startswith("Subject: ") or
285 elif (format != "tagdone" and (line.startswith("Subject: ") or
286 line.startswith("subject: "))):
286 line.startswith("subject: "))):
287 subject = line[9:]
287 subject = line[9:]
288 format = "tag"
288 format = "tag"
289 elif (format != "tagdone" and (line.startswith("From: ") or
289 elif (format != "tagdone" and (line.startswith("From: ") or
290 line.startswith("from: "))):
290 line.startswith("from: "))):
291 user = line[6:]
291 user = line[6:]
292 format = "tag"
292 format = "tag"
293 elif format == "tag" and line == "":
293 elif format == "tag" and line == "":
294 # when looking for tags (subject: from: etc) they
294 # when looking for tags (subject: from: etc) they
295 # end once you find a blank line in the source
295 # end once you find a blank line in the source
296 format = "tagdone"
296 format = "tagdone"
297 elif message or line:
297 elif message or line:
298 message.append(line)
298 message.append(line)
299 comments.append(line)
299 comments.append(line)
300
300
301 eatdiff(message)
301 eatdiff(message)
302 eatdiff(comments)
302 eatdiff(comments)
303 eatempty(message)
303 eatempty(message)
304 eatempty(comments)
304 eatempty(comments)
305
305
306 # make sure message isn't empty
306 # make sure message isn't empty
307 if format and format.startswith("tag") and subject:
307 if format and format.startswith("tag") and subject:
308 message.insert(0, "")
308 message.insert(0, "")
309 message.insert(0, subject)
309 message.insert(0, subject)
310 return (message, comments, user, date, diffstart > 1)
310 return (message, comments, user, date, diffstart > 1)
311
311
312 def removeundo(self, repo):
312 def removeundo(self, repo):
313 undo = repo.sjoin('undo')
313 undo = repo.sjoin('undo')
314 if not os.path.exists(undo):
314 if not os.path.exists(undo):
315 return
315 return
316 try:
316 try:
317 os.unlink(undo)
317 os.unlink(undo)
318 except OSError, inst:
318 except OSError, inst:
319 self.ui.warn('error removing undo: %s\n' % str(inst))
319 self.ui.warn('error removing undo: %s\n' % str(inst))
320
320
321 def printdiff(self, repo, node1, node2=None, files=None,
321 def printdiff(self, repo, node1, node2=None, files=None,
322 fp=None, changes=None, opts={}):
322 fp=None, changes=None, opts={}):
323 m = cmdutil.match(repo, files, opts)
323 m = cmdutil.match(repo, files, opts)
324 patch.diff(repo, node1, node2, m, fp, changes, self.diffopts())
324 patch.diff(repo, node1, node2, m, fp, changes, self.diffopts())
325
325
326 def mergeone(self, repo, mergeq, head, patch, rev):
326 def mergeone(self, repo, mergeq, head, patch, rev):
327 # first try just applying the patch
327 # first try just applying the patch
328 (err, n) = self.apply(repo, [ patch ], update_status=False,
328 (err, n) = self.apply(repo, [ patch ], update_status=False,
329 strict=True, merge=rev)
329 strict=True, merge=rev)
330
330
331 if err == 0:
331 if err == 0:
332 return (err, n)
332 return (err, n)
333
333
334 if n is None:
334 if n is None:
335 raise util.Abort(_("apply failed for patch %s") % patch)
335 raise util.Abort(_("apply failed for patch %s") % patch)
336
336
337 self.ui.warn("patch didn't work out, merging %s\n" % patch)
337 self.ui.warn("patch didn't work out, merging %s\n" % patch)
338
338
339 # apply failed, strip away that rev and merge.
339 # apply failed, strip away that rev and merge.
340 hg.clean(repo, head)
340 hg.clean(repo, head)
341 self.strip(repo, n, update=False, backup='strip')
341 self.strip(repo, n, update=False, backup='strip')
342
342
343 ctx = repo.changectx(rev)
343 ctx = repo.changectx(rev)
344 ret = hg.merge(repo, rev)
344 ret = hg.merge(repo, rev)
345 if ret:
345 if ret:
346 raise util.Abort(_("update returned %d") % ret)
346 raise util.Abort(_("update returned %d") % ret)
347 n = repo.commit(None, ctx.description(), ctx.user(), force=1)
347 n = repo.commit(None, ctx.description(), ctx.user(), force=1)
348 if n == None:
348 if n == None:
349 raise util.Abort(_("repo commit failed"))
349 raise util.Abort(_("repo commit failed"))
350 try:
350 try:
351 message, comments, user, date, patchfound = mergeq.readheaders(patch)
351 message, comments, user, date, patchfound = mergeq.readheaders(patch)
352 except:
352 except:
353 raise util.Abort(_("unable to read %s") % patch)
353 raise util.Abort(_("unable to read %s") % patch)
354
354
355 patchf = self.opener(patch, "w")
355 patchf = self.opener(patch, "w")
356 if comments:
356 if comments:
357 comments = "\n".join(comments) + '\n\n'
357 comments = "\n".join(comments) + '\n\n'
358 patchf.write(comments)
358 patchf.write(comments)
359 self.printdiff(repo, head, n, fp=patchf)
359 self.printdiff(repo, head, n, fp=patchf)
360 patchf.close()
360 patchf.close()
361 self.removeundo(repo)
361 self.removeundo(repo)
362 return (0, n)
362 return (0, n)
363
363
364 def qparents(self, repo, rev=None):
364 def qparents(self, repo, rev=None):
365 if rev is None:
365 if rev is None:
366 (p1, p2) = repo.dirstate.parents()
366 (p1, p2) = repo.dirstate.parents()
367 if p2 == revlog.nullid:
367 if p2 == revlog.nullid:
368 return p1
368 return p1
369 if len(self.applied) == 0:
369 if len(self.applied) == 0:
370 return None
370 return None
371 return revlog.bin(self.applied[-1].rev)
371 return revlog.bin(self.applied[-1].rev)
372 pp = repo.changelog.parents(rev)
372 pp = repo.changelog.parents(rev)
373 if pp[1] != revlog.nullid:
373 if pp[1] != revlog.nullid:
374 arevs = [ x.rev for x in self.applied ]
374 arevs = [ x.rev for x in self.applied ]
375 p0 = revlog.hex(pp[0])
375 p0 = revlog.hex(pp[0])
376 p1 = revlog.hex(pp[1])
376 p1 = revlog.hex(pp[1])
377 if p0 in arevs:
377 if p0 in arevs:
378 return pp[0]
378 return pp[0]
379 if p1 in arevs:
379 if p1 in arevs:
380 return pp[1]
380 return pp[1]
381 return pp[0]
381 return pp[0]
382
382
383 def mergepatch(self, repo, mergeq, series):
383 def mergepatch(self, repo, mergeq, series):
384 if len(self.applied) == 0:
384 if len(self.applied) == 0:
385 # each of the patches merged in will have two parents. This
385 # each of the patches merged in will have two parents. This
386 # can confuse the qrefresh, qdiff, and strip code because it
386 # can confuse the qrefresh, qdiff, and strip code because it
387 # needs to know which parent is actually in the patch queue.
387 # needs to know which parent is actually in the patch queue.
388 # so, we insert a merge marker with only one parent. This way
388 # so, we insert a merge marker with only one parent. This way
389 # the first patch in the queue is never a merge patch
389 # the first patch in the queue is never a merge patch
390 #
390 #
391 pname = ".hg.patches.merge.marker"
391 pname = ".hg.patches.merge.marker"
392 n = repo.commit(None, '[mq]: merge marker', user=None, force=1)
392 n = repo.commit(None, '[mq]: merge marker', user=None, force=1)
393 self.removeundo(repo)
393 self.removeundo(repo)
394 self.applied.append(statusentry(revlog.hex(n), pname))
394 self.applied.append(statusentry(revlog.hex(n), pname))
395 self.applied_dirty = 1
395 self.applied_dirty = 1
396
396
397 head = self.qparents(repo)
397 head = self.qparents(repo)
398
398
399 for patch in series:
399 for patch in series:
400 patch = mergeq.lookup(patch, strict=True)
400 patch = mergeq.lookup(patch, strict=True)
401 if not patch:
401 if not patch:
402 self.ui.warn("patch %s does not exist\n" % patch)
402 self.ui.warn("patch %s does not exist\n" % patch)
403 return (1, None)
403 return (1, None)
404 pushable, reason = self.pushable(patch)
404 pushable, reason = self.pushable(patch)
405 if not pushable:
405 if not pushable:
406 self.explain_pushable(patch, all_patches=True)
406 self.explain_pushable(patch, all_patches=True)
407 continue
407 continue
408 info = mergeq.isapplied(patch)
408 info = mergeq.isapplied(patch)
409 if not info:
409 if not info:
410 self.ui.warn("patch %s is not applied\n" % patch)
410 self.ui.warn("patch %s is not applied\n" % patch)
411 return (1, None)
411 return (1, None)
412 rev = revlog.bin(info[1])
412 rev = revlog.bin(info[1])
413 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
413 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
414 if head:
414 if head:
415 self.applied.append(statusentry(revlog.hex(head), patch))
415 self.applied.append(statusentry(revlog.hex(head), patch))
416 self.applied_dirty = 1
416 self.applied_dirty = 1
417 if err:
417 if err:
418 return (err, head)
418 return (err, head)
419 self.save_dirty()
419 self.save_dirty()
420 return (0, head)
420 return (0, head)
421
421
422 def patch(self, repo, patchfile):
422 def patch(self, repo, patchfile):
423 '''Apply patchfile to the working directory.
423 '''Apply patchfile to the working directory.
424 patchfile: file name of patch'''
424 patchfile: file name of patch'''
425 files = {}
425 files = {}
426 try:
426 try:
427 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
427 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
428 files=files)
428 files=files)
429 except Exception, inst:
429 except Exception, inst:
430 self.ui.note(str(inst) + '\n')
430 self.ui.note(str(inst) + '\n')
431 if not self.ui.verbose:
431 if not self.ui.verbose:
432 self.ui.warn("patch failed, unable to continue (try -v)\n")
432 self.ui.warn("patch failed, unable to continue (try -v)\n")
433 return (False, files, False)
433 return (False, files, False)
434
434
435 return (True, files, fuzz)
435 return (True, files, fuzz)
436
436
437 def apply(self, repo, series, list=False, update_status=True,
437 def apply(self, repo, series, list=False, update_status=True,
438 strict=False, patchdir=None, merge=None, all_files={}):
438 strict=False, patchdir=None, merge=None, all_files={}):
439 wlock = lock = tr = None
439 wlock = lock = tr = None
440 try:
440 try:
441 wlock = repo.wlock()
441 wlock = repo.wlock()
442 lock = repo.lock()
442 lock = repo.lock()
443 tr = repo.transaction()
443 tr = repo.transaction()
444 try:
444 try:
445 ret = self._apply(repo, series, list, update_status,
445 ret = self._apply(repo, series, list, update_status,
446 strict, patchdir, merge, all_files=all_files)
446 strict, patchdir, merge, all_files=all_files)
447 tr.close()
447 tr.close()
448 self.save_dirty()
448 self.save_dirty()
449 return ret
449 return ret
450 except:
450 except:
451 try:
451 try:
452 tr.abort()
452 tr.abort()
453 finally:
453 finally:
454 repo.invalidate()
454 repo.invalidate()
455 repo.dirstate.invalidate()
455 repo.dirstate.invalidate()
456 raise
456 raise
457 finally:
457 finally:
458 del tr, lock, wlock
458 del tr, lock, wlock
459 self.removeundo(repo)
459 self.removeundo(repo)
460
460
461 def _apply(self, repo, series, list=False, update_status=True,
461 def _apply(self, repo, series, list=False, update_status=True,
462 strict=False, patchdir=None, merge=None, all_files={}):
462 strict=False, patchdir=None, merge=None, all_files={}):
463 # TODO unify with commands.py
463 # TODO unify with commands.py
464 if not patchdir:
464 if not patchdir:
465 patchdir = self.path
465 patchdir = self.path
466 err = 0
466 err = 0
467 n = None
467 n = None
468 for patchname in series:
468 for patchname in series:
469 pushable, reason = self.pushable(patchname)
469 pushable, reason = self.pushable(patchname)
470 if not pushable:
470 if not pushable:
471 self.explain_pushable(patchname, all_patches=True)
471 self.explain_pushable(patchname, all_patches=True)
472 continue
472 continue
473 self.ui.warn("applying %s\n" % patchname)
473 self.ui.warn("applying %s\n" % patchname)
474 pf = os.path.join(patchdir, patchname)
474 pf = os.path.join(patchdir, patchname)
475
475
476 try:
476 try:
477 message, comments, user, date, patchfound = self.readheaders(patchname)
477 message, comments, user, date, patchfound = self.readheaders(patchname)
478 except:
478 except:
479 self.ui.warn("Unable to read %s\n" % patchname)
479 self.ui.warn("Unable to read %s\n" % patchname)
480 err = 1
480 err = 1
481 break
481 break
482
482
483 if not message:
483 if not message:
484 message = "imported patch %s\n" % patchname
484 message = "imported patch %s\n" % patchname
485 else:
485 else:
486 if list:
486 if list:
487 message.append("\nimported patch %s" % patchname)
487 message.append("\nimported patch %s" % patchname)
488 message = '\n'.join(message)
488 message = '\n'.join(message)
489
489
490 (patcherr, files, fuzz) = self.patch(repo, pf)
490 (patcherr, files, fuzz) = self.patch(repo, pf)
491 all_files.update(files)
491 all_files.update(files)
492 patcherr = not patcherr
492 patcherr = not patcherr
493
493
494 if merge and files:
494 if merge and files:
495 # Mark as removed/merged and update dirstate parent info
495 # Mark as removed/merged and update dirstate parent info
496 removed = []
496 removed = []
497 merged = []
497 merged = []
498 for f in files:
498 for f in files:
499 if os.path.exists(repo.wjoin(f)):
499 if os.path.exists(repo.wjoin(f)):
500 merged.append(f)
500 merged.append(f)
501 else:
501 else:
502 removed.append(f)
502 removed.append(f)
503 for f in removed:
503 for f in removed:
504 repo.dirstate.remove(f)
504 repo.dirstate.remove(f)
505 for f in merged:
505 for f in merged:
506 repo.dirstate.merge(f)
506 repo.dirstate.merge(f)
507 p1, p2 = repo.dirstate.parents()
507 p1, p2 = repo.dirstate.parents()
508 repo.dirstate.setparents(p1, merge)
508 repo.dirstate.setparents(p1, merge)
509
509 files = patch.updatedir(self.ui, repo, files)
510 files = patch.updatedir(self.ui, repo, files)
510 n = repo.commit(files, message, user, date, match=util.never,
511 match = cmdutil.matchfiles(repo, files or [])
512 n = repo.commit(files, message, user, date, match=match,
511 force=True)
513 force=True)
512
514
513 if n == None:
515 if n == None:
514 raise util.Abort(_("repo commit failed"))
516 raise util.Abort(_("repo commit failed"))
515
517
516 if update_status:
518 if update_status:
517 self.applied.append(statusentry(revlog.hex(n), patchname))
519 self.applied.append(statusentry(revlog.hex(n), patchname))
518
520
519 if patcherr:
521 if patcherr:
520 if not patchfound:
522 if not patchfound:
521 self.ui.warn("patch %s is empty\n" % patchname)
523 self.ui.warn("patch %s is empty\n" % patchname)
522 err = 0
524 err = 0
523 else:
525 else:
524 self.ui.warn("patch failed, rejects left in working dir\n")
526 self.ui.warn("patch failed, rejects left in working dir\n")
525 err = 1
527 err = 1
526 break
528 break
527
529
528 if fuzz and strict:
530 if fuzz and strict:
529 self.ui.warn("fuzz found when applying patch, stopping\n")
531 self.ui.warn("fuzz found when applying patch, stopping\n")
530 err = 1
532 err = 1
531 break
533 break
532 return (err, n)
534 return (err, n)
533
535
534 def delete(self, repo, patches, opts):
536 def delete(self, repo, patches, opts):
535 if not patches and not opts.get('rev'):
537 if not patches and not opts.get('rev'):
536 raise util.Abort(_('qdelete requires at least one revision or '
538 raise util.Abort(_('qdelete requires at least one revision or '
537 'patch name'))
539 'patch name'))
538
540
539 realpatches = []
541 realpatches = []
540 for patch in patches:
542 for patch in patches:
541 patch = self.lookup(patch, strict=True)
543 patch = self.lookup(patch, strict=True)
542 info = self.isapplied(patch)
544 info = self.isapplied(patch)
543 if info:
545 if info:
544 raise util.Abort(_("cannot delete applied patch %s") % patch)
546 raise util.Abort(_("cannot delete applied patch %s") % patch)
545 if patch not in self.series:
547 if patch not in self.series:
546 raise util.Abort(_("patch %s not in series file") % patch)
548 raise util.Abort(_("patch %s not in series file") % patch)
547 realpatches.append(patch)
549 realpatches.append(patch)
548
550
549 appliedbase = 0
551 appliedbase = 0
550 if opts.get('rev'):
552 if opts.get('rev'):
551 if not self.applied:
553 if not self.applied:
552 raise util.Abort(_('no patches applied'))
554 raise util.Abort(_('no patches applied'))
553 revs = cmdutil.revrange(repo, opts['rev'])
555 revs = cmdutil.revrange(repo, opts['rev'])
554 if len(revs) > 1 and revs[0] > revs[1]:
556 if len(revs) > 1 and revs[0] > revs[1]:
555 revs.reverse()
557 revs.reverse()
556 for rev in revs:
558 for rev in revs:
557 if appliedbase >= len(self.applied):
559 if appliedbase >= len(self.applied):
558 raise util.Abort(_("revision %d is not managed") % rev)
560 raise util.Abort(_("revision %d is not managed") % rev)
559
561
560 base = revlog.bin(self.applied[appliedbase].rev)
562 base = revlog.bin(self.applied[appliedbase].rev)
561 node = repo.changelog.node(rev)
563 node = repo.changelog.node(rev)
562 if node != base:
564 if node != base:
563 raise util.Abort(_("cannot delete revision %d above "
565 raise util.Abort(_("cannot delete revision %d above "
564 "applied patches") % rev)
566 "applied patches") % rev)
565 realpatches.append(self.applied[appliedbase].name)
567 realpatches.append(self.applied[appliedbase].name)
566 appliedbase += 1
568 appliedbase += 1
567
569
568 if not opts.get('keep'):
570 if not opts.get('keep'):
569 r = self.qrepo()
571 r = self.qrepo()
570 if r:
572 if r:
571 r.remove(realpatches, True)
573 r.remove(realpatches, True)
572 else:
574 else:
573 for p in realpatches:
575 for p in realpatches:
574 os.unlink(self.join(p))
576 os.unlink(self.join(p))
575
577
576 if appliedbase:
578 if appliedbase:
577 del self.applied[:appliedbase]
579 del self.applied[:appliedbase]
578 self.applied_dirty = 1
580 self.applied_dirty = 1
579 indices = [self.find_series(p) for p in realpatches]
581 indices = [self.find_series(p) for p in realpatches]
580 indices.sort()
582 indices.sort()
581 for i in indices[-1::-1]:
583 for i in indices[-1::-1]:
582 del self.full_series[i]
584 del self.full_series[i]
583 self.parse_series()
585 self.parse_series()
584 self.series_dirty = 1
586 self.series_dirty = 1
585
587
586 def check_toppatch(self, repo):
588 def check_toppatch(self, repo):
587 if len(self.applied) > 0:
589 if len(self.applied) > 0:
588 top = revlog.bin(self.applied[-1].rev)
590 top = revlog.bin(self.applied[-1].rev)
589 pp = repo.dirstate.parents()
591 pp = repo.dirstate.parents()
590 if top not in pp:
592 if top not in pp:
591 raise util.Abort(_("working directory revision is not qtip"))
593 raise util.Abort(_("working directory revision is not qtip"))
592 return top
594 return top
593 return None
595 return None
594 def check_localchanges(self, repo, force=False, refresh=True):
596 def check_localchanges(self, repo, force=False, refresh=True):
595 m, a, r, d = repo.status()[:4]
597 m, a, r, d = repo.status()[:4]
596 if m or a or r or d:
598 if m or a or r or d:
597 if not force:
599 if not force:
598 if refresh:
600 if refresh:
599 raise util.Abort(_("local changes found, refresh first"))
601 raise util.Abort(_("local changes found, refresh first"))
600 else:
602 else:
601 raise util.Abort(_("local changes found"))
603 raise util.Abort(_("local changes found"))
602 return m, a, r, d
604 return m, a, r, d
603
605
604 _reserved = ('series', 'status', 'guards')
606 _reserved = ('series', 'status', 'guards')
605 def check_reserved_name(self, name):
607 def check_reserved_name(self, name):
606 if (name in self._reserved or name.startswith('.hg')
608 if (name in self._reserved or name.startswith('.hg')
607 or name.startswith('.mq')):
609 or name.startswith('.mq')):
608 raise util.Abort(_('"%s" cannot be used as the name of a patch')
610 raise util.Abort(_('"%s" cannot be used as the name of a patch')
609 % name)
611 % name)
610
612
611 def new(self, repo, patch, *pats, **opts):
613 def new(self, repo, patch, *pats, **opts):
612 msg = opts.get('msg')
614 msg = opts.get('msg')
613 force = opts.get('force')
615 force = opts.get('force')
614 user = opts.get('user')
616 user = opts.get('user')
615 date = opts.get('date')
617 date = opts.get('date')
616 if date:
618 if date:
617 date = util.parsedate(date)
619 date = util.parsedate(date)
618 self.check_reserved_name(patch)
620 self.check_reserved_name(patch)
619 if os.path.exists(self.join(patch)):
621 if os.path.exists(self.join(patch)):
620 raise util.Abort(_('patch "%s" already exists') % patch)
622 raise util.Abort(_('patch "%s" already exists') % patch)
621 if opts.get('include') or opts.get('exclude') or pats:
623 if opts.get('include') or opts.get('exclude') or pats:
622 match = cmdutil.match(repo, pats, opts)
624 match = cmdutil.match(repo, pats, opts)
623 m, a, r, d = repo.status(files=match.files(), match=match)[:4]
625 m, a, r, d = repo.status(match=match)[:4]
624 else:
626 else:
625 m, a, r, d = self.check_localchanges(repo, force)
627 m, a, r, d = self.check_localchanges(repo, force)
626 match = cmdutil.match(repo, m + a + r)
628 match = cmdutil.match(repo, m + a + r)
627 commitfiles = m + a + r
629 commitfiles = m + a + r
628 self.check_toppatch(repo)
630 self.check_toppatch(repo)
629 wlock = repo.wlock()
631 wlock = repo.wlock()
630 try:
632 try:
631 insert = self.full_series_end()
633 insert = self.full_series_end()
632 commitmsg = msg and msg or ("[mq]: %s" % patch)
634 commitmsg = msg and msg or ("[mq]: %s" % patch)
633 n = repo.commit(commitfiles, commitmsg, user, date, match=match, force=True)
635 n = repo.commit(commitfiles, commitmsg, user, date, match=match, force=True)
634 if n == None:
636 if n == None:
635 raise util.Abort(_("repo commit failed"))
637 raise util.Abort(_("repo commit failed"))
636 self.full_series[insert:insert] = [patch]
638 self.full_series[insert:insert] = [patch]
637 self.applied.append(statusentry(revlog.hex(n), patch))
639 self.applied.append(statusentry(revlog.hex(n), patch))
638 self.parse_series()
640 self.parse_series()
639 self.series_dirty = 1
641 self.series_dirty = 1
640 self.applied_dirty = 1
642 self.applied_dirty = 1
641 p = self.opener(patch, "w")
643 p = self.opener(patch, "w")
642 if date:
644 if date:
643 p.write("# HG changeset patch\n")
645 p.write("# HG changeset patch\n")
644 if user:
646 if user:
645 p.write("# User " + user + "\n")
647 p.write("# User " + user + "\n")
646 p.write("# Date %d %d\n" % date)
648 p.write("# Date %d %d\n" % date)
647 p.write("\n")
649 p.write("\n")
648 elif user:
650 elif user:
649 p.write("From: " + user + "\n")
651 p.write("From: " + user + "\n")
650 p.write("\n")
652 p.write("\n")
651 if msg:
653 if msg:
652 msg = msg + "\n"
654 msg = msg + "\n"
653 p.write(msg)
655 p.write(msg)
654 p.close()
656 p.close()
655 wlock = None
657 wlock = None
656 r = self.qrepo()
658 r = self.qrepo()
657 if r: r.add([patch])
659 if r: r.add([patch])
658 if commitfiles:
660 if commitfiles:
659 self.refresh(repo, short=True, git=opts.get('git'))
661 self.refresh(repo, short=True, git=opts.get('git'))
660 self.removeundo(repo)
662 self.removeundo(repo)
661 finally:
663 finally:
662 del wlock
664 del wlock
663
665
664 def strip(self, repo, rev, update=True, backup="all", force=None):
666 def strip(self, repo, rev, update=True, backup="all", force=None):
665 wlock = lock = None
667 wlock = lock = None
666 try:
668 try:
667 wlock = repo.wlock()
669 wlock = repo.wlock()
668 lock = repo.lock()
670 lock = repo.lock()
669
671
670 if update:
672 if update:
671 self.check_localchanges(repo, force=force, refresh=False)
673 self.check_localchanges(repo, force=force, refresh=False)
672 urev = self.qparents(repo, rev)
674 urev = self.qparents(repo, rev)
673 hg.clean(repo, urev)
675 hg.clean(repo, urev)
674 repo.dirstate.write()
676 repo.dirstate.write()
675
677
676 self.removeundo(repo)
678 self.removeundo(repo)
677 repair.strip(self.ui, repo, rev, backup)
679 repair.strip(self.ui, repo, rev, backup)
678 # strip may have unbundled a set of backed up revisions after
680 # strip may have unbundled a set of backed up revisions after
679 # the actual strip
681 # the actual strip
680 self.removeundo(repo)
682 self.removeundo(repo)
681 finally:
683 finally:
682 del lock, wlock
684 del lock, wlock
683
685
684 def isapplied(self, patch):
686 def isapplied(self, patch):
685 """returns (index, rev, patch)"""
687 """returns (index, rev, patch)"""
686 for i in xrange(len(self.applied)):
688 for i in xrange(len(self.applied)):
687 a = self.applied[i]
689 a = self.applied[i]
688 if a.name == patch:
690 if a.name == patch:
689 return (i, a.rev, a.name)
691 return (i, a.rev, a.name)
690 return None
692 return None
691
693
692 # if the exact patch name does not exist, we try a few
694 # if the exact patch name does not exist, we try a few
693 # variations. If strict is passed, we try only #1
695 # variations. If strict is passed, we try only #1
694 #
696 #
695 # 1) a number to indicate an offset in the series file
697 # 1) a number to indicate an offset in the series file
696 # 2) a unique substring of the patch name was given
698 # 2) a unique substring of the patch name was given
697 # 3) patchname[-+]num to indicate an offset in the series file
699 # 3) patchname[-+]num to indicate an offset in the series file
698 def lookup(self, patch, strict=False):
700 def lookup(self, patch, strict=False):
699 patch = patch and str(patch)
701 patch = patch and str(patch)
700
702
701 def partial_name(s):
703 def partial_name(s):
702 if s in self.series:
704 if s in self.series:
703 return s
705 return s
704 matches = [x for x in self.series if s in x]
706 matches = [x for x in self.series if s in x]
705 if len(matches) > 1:
707 if len(matches) > 1:
706 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
708 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
707 for m in matches:
709 for m in matches:
708 self.ui.warn(' %s\n' % m)
710 self.ui.warn(' %s\n' % m)
709 return None
711 return None
710 if matches:
712 if matches:
711 return matches[0]
713 return matches[0]
712 if len(self.series) > 0 and len(self.applied) > 0:
714 if len(self.series) > 0 and len(self.applied) > 0:
713 if s == 'qtip':
715 if s == 'qtip':
714 return self.series[self.series_end(True)-1]
716 return self.series[self.series_end(True)-1]
715 if s == 'qbase':
717 if s == 'qbase':
716 return self.series[0]
718 return self.series[0]
717 return None
719 return None
718 if patch == None:
720 if patch == None:
719 return None
721 return None
720
722
721 # we don't want to return a partial match until we make
723 # we don't want to return a partial match until we make
722 # sure the file name passed in does not exist (checked below)
724 # sure the file name passed in does not exist (checked below)
723 res = partial_name(patch)
725 res = partial_name(patch)
724 if res and res == patch:
726 if res and res == patch:
725 return res
727 return res
726
728
727 if not os.path.isfile(self.join(patch)):
729 if not os.path.isfile(self.join(patch)):
728 try:
730 try:
729 sno = int(patch)
731 sno = int(patch)
730 except(ValueError, OverflowError):
732 except(ValueError, OverflowError):
731 pass
733 pass
732 else:
734 else:
733 if sno < len(self.series):
735 if sno < len(self.series):
734 return self.series[sno]
736 return self.series[sno]
735 if not strict:
737 if not strict:
736 # return any partial match made above
738 # return any partial match made above
737 if res:
739 if res:
738 return res
740 return res
739 minus = patch.rfind('-')
741 minus = patch.rfind('-')
740 if minus >= 0:
742 if minus >= 0:
741 res = partial_name(patch[:minus])
743 res = partial_name(patch[:minus])
742 if res:
744 if res:
743 i = self.series.index(res)
745 i = self.series.index(res)
744 try:
746 try:
745 off = int(patch[minus+1:] or 1)
747 off = int(patch[minus+1:] or 1)
746 except(ValueError, OverflowError):
748 except(ValueError, OverflowError):
747 pass
749 pass
748 else:
750 else:
749 if i - off >= 0:
751 if i - off >= 0:
750 return self.series[i - off]
752 return self.series[i - off]
751 plus = patch.rfind('+')
753 plus = patch.rfind('+')
752 if plus >= 0:
754 if plus >= 0:
753 res = partial_name(patch[:plus])
755 res = partial_name(patch[:plus])
754 if res:
756 if res:
755 i = self.series.index(res)
757 i = self.series.index(res)
756 try:
758 try:
757 off = int(patch[plus+1:] or 1)
759 off = int(patch[plus+1:] or 1)
758 except(ValueError, OverflowError):
760 except(ValueError, OverflowError):
759 pass
761 pass
760 else:
762 else:
761 if i + off < len(self.series):
763 if i + off < len(self.series):
762 return self.series[i + off]
764 return self.series[i + off]
763 raise util.Abort(_("patch %s not in series") % patch)
765 raise util.Abort(_("patch %s not in series") % patch)
764
766
765 def push(self, repo, patch=None, force=False, list=False,
767 def push(self, repo, patch=None, force=False, list=False,
766 mergeq=None):
768 mergeq=None):
767 wlock = repo.wlock()
769 wlock = repo.wlock()
768 if repo.dirstate.parents()[0] != repo.changelog.tip():
770 if repo.dirstate.parents()[0] != repo.changelog.tip():
769 self.ui.status(_("(working directory not at tip)\n"))
771 self.ui.status(_("(working directory not at tip)\n"))
770
772
771 try:
773 try:
772 patch = self.lookup(patch)
774 patch = self.lookup(patch)
773 # Suppose our series file is: A B C and the current 'top'
775 # Suppose our series file is: A B C and the current 'top'
774 # patch is B. qpush C should be performed (moving forward)
776 # patch is B. qpush C should be performed (moving forward)
775 # qpush B is a NOP (no change) qpush A is an error (can't
777 # qpush B is a NOP (no change) qpush A is an error (can't
776 # go backwards with qpush)
778 # go backwards with qpush)
777 if patch:
779 if patch:
778 info = self.isapplied(patch)
780 info = self.isapplied(patch)
779 if info:
781 if info:
780 if info[0] < len(self.applied) - 1:
782 if info[0] < len(self.applied) - 1:
781 raise util.Abort(
783 raise util.Abort(
782 _("cannot push to a previous patch: %s") % patch)
784 _("cannot push to a previous patch: %s") % patch)
783 if info[0] < len(self.series) - 1:
785 if info[0] < len(self.series) - 1:
784 self.ui.warn(
786 self.ui.warn(
785 _('qpush: %s is already at the top\n') % patch)
787 _('qpush: %s is already at the top\n') % patch)
786 else:
788 else:
787 self.ui.warn(_('all patches are currently applied\n'))
789 self.ui.warn(_('all patches are currently applied\n'))
788 return
790 return
789
791
790 # Following the above example, starting at 'top' of B:
792 # Following the above example, starting at 'top' of B:
791 # qpush should be performed (pushes C), but a subsequent
793 # qpush should be performed (pushes C), but a subsequent
792 # qpush without an argument is an error (nothing to
794 # qpush without an argument is an error (nothing to
793 # apply). This allows a loop of "...while hg qpush..." to
795 # apply). This allows a loop of "...while hg qpush..." to
794 # work as it detects an error when done
796 # work as it detects an error when done
795 if self.series_end() == len(self.series):
797 if self.series_end() == len(self.series):
796 self.ui.warn(_('patch series already fully applied\n'))
798 self.ui.warn(_('patch series already fully applied\n'))
797 return 1
799 return 1
798 if not force:
800 if not force:
799 self.check_localchanges(repo)
801 self.check_localchanges(repo)
800
802
801 self.applied_dirty = 1;
803 self.applied_dirty = 1;
802 start = self.series_end()
804 start = self.series_end()
803 if start > 0:
805 if start > 0:
804 self.check_toppatch(repo)
806 self.check_toppatch(repo)
805 if not patch:
807 if not patch:
806 patch = self.series[start]
808 patch = self.series[start]
807 end = start + 1
809 end = start + 1
808 else:
810 else:
809 end = self.series.index(patch, start) + 1
811 end = self.series.index(patch, start) + 1
810 s = self.series[start:end]
812 s = self.series[start:end]
811 all_files = {}
813 all_files = {}
812 try:
814 try:
813 if mergeq:
815 if mergeq:
814 ret = self.mergepatch(repo, mergeq, s)
816 ret = self.mergepatch(repo, mergeq, s)
815 else:
817 else:
816 ret = self.apply(repo, s, list, all_files=all_files)
818 ret = self.apply(repo, s, list, all_files=all_files)
817 except:
819 except:
818 self.ui.warn(_('cleaning up working directory...'))
820 self.ui.warn(_('cleaning up working directory...'))
819 node = repo.dirstate.parents()[0]
821 node = repo.dirstate.parents()[0]
820 hg.revert(repo, node, None)
822 hg.revert(repo, node, None)
821 unknown = repo.status()[4]
823 unknown = repo.status()[4]
822 # only remove unknown files that we know we touched or
824 # only remove unknown files that we know we touched or
823 # created while patching
825 # created while patching
824 for f in unknown:
826 for f in unknown:
825 if f in all_files:
827 if f in all_files:
826 util.unlink(repo.wjoin(f))
828 util.unlink(repo.wjoin(f))
827 self.ui.warn(_('done\n'))
829 self.ui.warn(_('done\n'))
828 raise
830 raise
829 top = self.applied[-1].name
831 top = self.applied[-1].name
830 if ret[0]:
832 if ret[0]:
831 self.ui.write(
833 self.ui.write(
832 "Errors during apply, please fix and refresh %s\n" % top)
834 "Errors during apply, please fix and refresh %s\n" % top)
833 else:
835 else:
834 self.ui.write("Now at: %s\n" % top)
836 self.ui.write("Now at: %s\n" % top)
835 return ret[0]
837 return ret[0]
836 finally:
838 finally:
837 del wlock
839 del wlock
838
840
839 def pop(self, repo, patch=None, force=False, update=True, all=False):
841 def pop(self, repo, patch=None, force=False, update=True, all=False):
840 def getfile(f, rev, flags):
842 def getfile(f, rev, flags):
841 t = repo.file(f).read(rev)
843 t = repo.file(f).read(rev)
842 repo.wwrite(f, t, flags)
844 repo.wwrite(f, t, flags)
843
845
844 wlock = repo.wlock()
846 wlock = repo.wlock()
845 try:
847 try:
846 if patch:
848 if patch:
847 # index, rev, patch
849 # index, rev, patch
848 info = self.isapplied(patch)
850 info = self.isapplied(patch)
849 if not info:
851 if not info:
850 patch = self.lookup(patch)
852 patch = self.lookup(patch)
851 info = self.isapplied(patch)
853 info = self.isapplied(patch)
852 if not info:
854 if not info:
853 raise util.Abort(_("patch %s is not applied") % patch)
855 raise util.Abort(_("patch %s is not applied") % patch)
854
856
855 if len(self.applied) == 0:
857 if len(self.applied) == 0:
856 # Allow qpop -a to work repeatedly,
858 # Allow qpop -a to work repeatedly,
857 # but not qpop without an argument
859 # but not qpop without an argument
858 self.ui.warn(_("no patches applied\n"))
860 self.ui.warn(_("no patches applied\n"))
859 return not all
861 return not all
860
862
861 if not update:
863 if not update:
862 parents = repo.dirstate.parents()
864 parents = repo.dirstate.parents()
863 rr = [ revlog.bin(x.rev) for x in self.applied ]
865 rr = [ revlog.bin(x.rev) for x in self.applied ]
864 for p in parents:
866 for p in parents:
865 if p in rr:
867 if p in rr:
866 self.ui.warn("qpop: forcing dirstate update\n")
868 self.ui.warn("qpop: forcing dirstate update\n")
867 update = True
869 update = True
868
870
869 if not force and update:
871 if not force and update:
870 self.check_localchanges(repo)
872 self.check_localchanges(repo)
871
873
872 self.applied_dirty = 1;
874 self.applied_dirty = 1;
873 end = len(self.applied)
875 end = len(self.applied)
874 if not patch:
876 if not patch:
875 if all:
877 if all:
876 popi = 0
878 popi = 0
877 else:
879 else:
878 popi = len(self.applied) - 1
880 popi = len(self.applied) - 1
879 else:
881 else:
880 popi = info[0] + 1
882 popi = info[0] + 1
881 if popi >= end:
883 if popi >= end:
882 self.ui.warn("qpop: %s is already at the top\n" % patch)
884 self.ui.warn("qpop: %s is already at the top\n" % patch)
883 return
885 return
884 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
886 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
885
887
886 start = info[0]
888 start = info[0]
887 rev = revlog.bin(info[1])
889 rev = revlog.bin(info[1])
888
890
889 if update:
891 if update:
890 top = self.check_toppatch(repo)
892 top = self.check_toppatch(repo)
891
893
892 if repo.changelog.heads(rev) != [revlog.bin(self.applied[-1].rev)]:
894 if repo.changelog.heads(rev) != [revlog.bin(self.applied[-1].rev)]:
893 raise util.Abort("popping would remove a revision not "
895 raise util.Abort("popping would remove a revision not "
894 "managed by this patch queue")
896 "managed by this patch queue")
895
897
896 # we know there are no local changes, so we can make a simplified
898 # we know there are no local changes, so we can make a simplified
897 # form of hg.update.
899 # form of hg.update.
898 if update:
900 if update:
899 qp = self.qparents(repo, rev)
901 qp = self.qparents(repo, rev)
900 changes = repo.changelog.read(qp)
902 changes = repo.changelog.read(qp)
901 mmap = repo.manifest.read(changes[0])
903 mmap = repo.manifest.read(changes[0])
902 m, a, r, d, u = repo.status(qp, top)[:5]
904 m, a, r, d, u = repo.status(qp, top)[:5]
903 if d:
905 if d:
904 raise util.Abort("deletions found between repo revs")
906 raise util.Abort("deletions found between repo revs")
905 for f in m:
907 for f in m:
906 getfile(f, mmap[f], mmap.flags(f))
908 getfile(f, mmap[f], mmap.flags(f))
907 for f in r:
909 for f in r:
908 getfile(f, mmap[f], mmap.flags(f))
910 getfile(f, mmap[f], mmap.flags(f))
909 for f in m + r:
911 for f in m + r:
910 repo.dirstate.normal(f)
912 repo.dirstate.normal(f)
911 for f in a:
913 for f in a:
912 try:
914 try:
913 os.unlink(repo.wjoin(f))
915 os.unlink(repo.wjoin(f))
914 except OSError, e:
916 except OSError, e:
915 if e.errno != errno.ENOENT:
917 if e.errno != errno.ENOENT:
916 raise
918 raise
917 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
919 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
918 except: pass
920 except: pass
919 repo.dirstate.forget(f)
921 repo.dirstate.forget(f)
920 repo.dirstate.setparents(qp, revlog.nullid)
922 repo.dirstate.setparents(qp, revlog.nullid)
921 del self.applied[start:end]
923 del self.applied[start:end]
922 self.strip(repo, rev, update=False, backup='strip')
924 self.strip(repo, rev, update=False, backup='strip')
923 if len(self.applied):
925 if len(self.applied):
924 self.ui.write("Now at: %s\n" % self.applied[-1].name)
926 self.ui.write("Now at: %s\n" % self.applied[-1].name)
925 else:
927 else:
926 self.ui.write("Patch queue now empty\n")
928 self.ui.write("Patch queue now empty\n")
927 finally:
929 finally:
928 del wlock
930 del wlock
929
931
930 def diff(self, repo, pats, opts):
932 def diff(self, repo, pats, opts):
931 top = self.check_toppatch(repo)
933 top = self.check_toppatch(repo)
932 if not top:
934 if not top:
933 self.ui.write("No patches applied\n")
935 self.ui.write("No patches applied\n")
934 return
936 return
935 qp = self.qparents(repo, top)
937 qp = self.qparents(repo, top)
936 if opts.get('git'):
938 if opts.get('git'):
937 self.diffopts().git = True
939 self.diffopts().git = True
938 if opts.get('unified') is not None:
940 if opts.get('unified') is not None:
939 self.diffopts().context = opts['unified']
941 self.diffopts().context = opts['unified']
940 self.printdiff(repo, qp, files=pats, opts=opts)
942 self.printdiff(repo, qp, files=pats, opts=opts)
941
943
942 def refresh(self, repo, pats=None, **opts):
944 def refresh(self, repo, pats=None, **opts):
943 if len(self.applied) == 0:
945 if len(self.applied) == 0:
944 self.ui.write("No patches applied\n")
946 self.ui.write("No patches applied\n")
945 return 1
947 return 1
946 newdate = opts.get('date')
948 newdate = opts.get('date')
947 if newdate:
949 if newdate:
948 newdate = '%d %d' % util.parsedate(newdate)
950 newdate = '%d %d' % util.parsedate(newdate)
949 wlock = repo.wlock()
951 wlock = repo.wlock()
950 try:
952 try:
951 self.check_toppatch(repo)
953 self.check_toppatch(repo)
952 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
954 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
953 top = revlog.bin(top)
955 top = revlog.bin(top)
954 if repo.changelog.heads(top) != [top]:
956 if repo.changelog.heads(top) != [top]:
955 raise util.Abort("cannot refresh a revision with children")
957 raise util.Abort("cannot refresh a revision with children")
956 cparents = repo.changelog.parents(top)
958 cparents = repo.changelog.parents(top)
957 patchparent = self.qparents(repo, top)
959 patchparent = self.qparents(repo, top)
958 message, comments, user, date, patchfound = self.readheaders(patchfn)
960 message, comments, user, date, patchfound = self.readheaders(patchfn)
959
961
960 patchf = self.opener(patchfn, 'r+')
962 patchf = self.opener(patchfn, 'r+')
961
963
962 # if the patch was a git patch, refresh it as a git patch
964 # if the patch was a git patch, refresh it as a git patch
963 for line in patchf:
965 for line in patchf:
964 if line.startswith('diff --git'):
966 if line.startswith('diff --git'):
965 self.diffopts().git = True
967 self.diffopts().git = True
966 break
968 break
967
969
968 msg = opts.get('msg', '').rstrip()
970 msg = opts.get('msg', '').rstrip()
969 if msg and comments:
971 if msg and comments:
970 # Remove existing message, keeping the rest of the comments
972 # Remove existing message, keeping the rest of the comments
971 # fields.
973 # fields.
972 # If comments contains 'subject: ', message will prepend
974 # If comments contains 'subject: ', message will prepend
973 # the field and a blank line.
975 # the field and a blank line.
974 if message:
976 if message:
975 subj = 'subject: ' + message[0].lower()
977 subj = 'subject: ' + message[0].lower()
976 for i in xrange(len(comments)):
978 for i in xrange(len(comments)):
977 if subj == comments[i].lower():
979 if subj == comments[i].lower():
978 del comments[i]
980 del comments[i]
979 message = message[2:]
981 message = message[2:]
980 break
982 break
981 ci = 0
983 ci = 0
982 for mi in xrange(len(message)):
984 for mi in xrange(len(message)):
983 while message[mi] != comments[ci]:
985 while message[mi] != comments[ci]:
984 ci += 1
986 ci += 1
985 del comments[ci]
987 del comments[ci]
986
988
987 def setheaderfield(comments, prefixes, new):
989 def setheaderfield(comments, prefixes, new):
988 # Update all references to a field in the patch header.
990 # Update all references to a field in the patch header.
989 # If none found, add it email style.
991 # If none found, add it email style.
990 res = False
992 res = False
991 for prefix in prefixes:
993 for prefix in prefixes:
992 for i in xrange(len(comments)):
994 for i in xrange(len(comments)):
993 if comments[i].startswith(prefix):
995 if comments[i].startswith(prefix):
994 comments[i] = prefix + new
996 comments[i] = prefix + new
995 res = True
997 res = True
996 break
998 break
997 return res
999 return res
998
1000
999 newuser = opts.get('user')
1001 newuser = opts.get('user')
1000 if newuser:
1002 if newuser:
1001 if not setheaderfield(comments, ['From: ', '# User '], newuser):
1003 if not setheaderfield(comments, ['From: ', '# User '], newuser):
1002 try:
1004 try:
1003 patchheaderat = comments.index('# HG changeset patch')
1005 patchheaderat = comments.index('# HG changeset patch')
1004 comments.insert(patchheaderat + 1,'# User ' + newuser)
1006 comments.insert(patchheaderat + 1,'# User ' + newuser)
1005 except ValueError:
1007 except ValueError:
1006 comments = ['From: ' + newuser, ''] + comments
1008 comments = ['From: ' + newuser, ''] + comments
1007 user = newuser
1009 user = newuser
1008
1010
1009 if newdate:
1011 if newdate:
1010 if setheaderfield(comments, ['# Date '], newdate):
1012 if setheaderfield(comments, ['# Date '], newdate):
1011 date = newdate
1013 date = newdate
1012
1014
1013 if msg:
1015 if msg:
1014 comments.append(msg)
1016 comments.append(msg)
1015
1017
1016 patchf.seek(0)
1018 patchf.seek(0)
1017 patchf.truncate()
1019 patchf.truncate()
1018
1020
1019 if comments:
1021 if comments:
1020 comments = "\n".join(comments) + '\n\n'
1022 comments = "\n".join(comments) + '\n\n'
1021 patchf.write(comments)
1023 patchf.write(comments)
1022
1024
1023 if opts.get('git'):
1025 if opts.get('git'):
1024 self.diffopts().git = True
1026 self.diffopts().git = True
1025 matchfn = cmdutil.match(repo, pats, opts)
1027 matchfn = cmdutil.match(repo, pats, opts)
1026 tip = repo.changelog.tip()
1028 tip = repo.changelog.tip()
1027 if top == tip:
1029 if top == tip:
1028 # if the top of our patch queue is also the tip, there is an
1030 # if the top of our patch queue is also the tip, there is an
1029 # optimization here. We update the dirstate in place and strip
1031 # optimization here. We update the dirstate in place and strip
1030 # off the tip commit. Then just commit the current directory
1032 # off the tip commit. Then just commit the current directory
1031 # tree. We can also send repo.commit the list of files
1033 # tree. We can also send repo.commit the list of files
1032 # changed to speed up the diff
1034 # changed to speed up the diff
1033 #
1035 #
1034 # in short mode, we only diff the files included in the
1036 # in short mode, we only diff the files included in the
1035 # patch already
1037 # patch already
1036 #
1038 #
1037 # this should really read:
1039 # this should really read:
1038 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
1040 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
1039 # but we do it backwards to take advantage of manifest/chlog
1041 # but we do it backwards to take advantage of manifest/chlog
1040 # caching against the next repo.status call
1042 # caching against the next repo.status call
1041 #
1043 #
1042 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
1044 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
1043 changes = repo.changelog.read(tip)
1045 changes = repo.changelog.read(tip)
1044 man = repo.manifest.read(changes[0])
1046 man = repo.manifest.read(changes[0])
1045 aaa = aa[:]
1047 aaa = aa[:]
1046 if opts.get('short'):
1048 if opts.get('short'):
1047 match = cmdutil.matchfiles(repo, mm + aa + dd)
1049 match = cmdutil.matchfiles(repo, mm + aa + dd)
1048 else:
1050 else:
1049 match = cmdutil.matchall(repo)
1051 match = cmdutil.matchall(repo)
1050 m, a, r, d, u = repo.status(files=match.files(), match=match)[:5]
1052 m, a, r, d, u = repo.status(match=match)[:5]
1051
1053
1052 # we might end up with files that were added between
1054 # we might end up with files that were added between
1053 # tip and the dirstate parent, but then changed in the
1055 # tip and the dirstate parent, but then changed in the
1054 # local dirstate. in this case, we want them to only
1056 # local dirstate. in this case, we want them to only
1055 # show up in the added section
1057 # show up in the added section
1056 for x in m:
1058 for x in m:
1057 if x not in aa:
1059 if x not in aa:
1058 mm.append(x)
1060 mm.append(x)
1059 # we might end up with files added by the local dirstate that
1061 # we might end up with files added by the local dirstate that
1060 # were deleted by the patch. In this case, they should only
1062 # were deleted by the patch. In this case, they should only
1061 # show up in the changed section.
1063 # show up in the changed section.
1062 for x in a:
1064 for x in a:
1063 if x in dd:
1065 if x in dd:
1064 del dd[dd.index(x)]
1066 del dd[dd.index(x)]
1065 mm.append(x)
1067 mm.append(x)
1066 else:
1068 else:
1067 aa.append(x)
1069 aa.append(x)
1068 # make sure any files deleted in the local dirstate
1070 # make sure any files deleted in the local dirstate
1069 # are not in the add or change column of the patch
1071 # are not in the add or change column of the patch
1070 forget = []
1072 forget = []
1071 for x in d + r:
1073 for x in d + r:
1072 if x in aa:
1074 if x in aa:
1073 del aa[aa.index(x)]
1075 del aa[aa.index(x)]
1074 forget.append(x)
1076 forget.append(x)
1075 continue
1077 continue
1076 elif x in mm:
1078 elif x in mm:
1077 del mm[mm.index(x)]
1079 del mm[mm.index(x)]
1078 dd.append(x)
1080 dd.append(x)
1079
1081
1080 m = util.unique(mm)
1082 m = util.unique(mm)
1081 r = util.unique(dd)
1083 r = util.unique(dd)
1082 a = util.unique(aa)
1084 a = util.unique(aa)
1083 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1085 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1084 match = cmdutil.matchfiles(repo, util.unique(c[0] + c[1] + c[2]))
1086 match = cmdutil.matchfiles(repo, util.unique(c[0] + c[1] + c[2]))
1085 patch.diff(repo, patchparent, match=match,
1087 patch.diff(repo, patchparent, match=match,
1086 fp=patchf, changes=c, opts=self.diffopts())
1088 fp=patchf, changes=c, opts=self.diffopts())
1087 patchf.close()
1089 patchf.close()
1088
1090
1089 repo.dirstate.setparents(*cparents)
1091 repo.dirstate.setparents(*cparents)
1090 copies = {}
1092 copies = {}
1091 for dst in a:
1093 for dst in a:
1092 src = repo.dirstate.copied(dst)
1094 src = repo.dirstate.copied(dst)
1093 if src is not None:
1095 if src is not None:
1094 copies.setdefault(src, []).append(dst)
1096 copies.setdefault(src, []).append(dst)
1095 repo.dirstate.add(dst)
1097 repo.dirstate.add(dst)
1096 # remember the copies between patchparent and tip
1098 # remember the copies between patchparent and tip
1097 # this may be slow, so don't do it if we're not tracking copies
1099 # this may be slow, so don't do it if we're not tracking copies
1098 if self.diffopts().git:
1100 if self.diffopts().git:
1099 for dst in aaa:
1101 for dst in aaa:
1100 f = repo.file(dst)
1102 f = repo.file(dst)
1101 src = f.renamed(man[dst])
1103 src = f.renamed(man[dst])
1102 if src:
1104 if src:
1103 copies[src[0]] = copies.get(dst, [])
1105 copies[src[0]] = copies.get(dst, [])
1104 if dst in a:
1106 if dst in a:
1105 copies[src[0]].append(dst)
1107 copies[src[0]].append(dst)
1106 # we can't copy a file created by the patch itself
1108 # we can't copy a file created by the patch itself
1107 if dst in copies:
1109 if dst in copies:
1108 del copies[dst]
1110 del copies[dst]
1109 for src, dsts in copies.iteritems():
1111 for src, dsts in copies.iteritems():
1110 for dst in dsts:
1112 for dst in dsts:
1111 repo.dirstate.copy(src, dst)
1113 repo.dirstate.copy(src, dst)
1112 for f in r:
1114 for f in r:
1113 repo.dirstate.remove(f)
1115 repo.dirstate.remove(f)
1114 # if the patch excludes a modified file, mark that
1116 # if the patch excludes a modified file, mark that
1115 # file with mtime=0 so status can see it.
1117 # file with mtime=0 so status can see it.
1116 mm = []
1118 mm = []
1117 for i in xrange(len(m)-1, -1, -1):
1119 for i in xrange(len(m)-1, -1, -1):
1118 if not matchfn(m[i]):
1120 if not matchfn(m[i]):
1119 mm.append(m[i])
1121 mm.append(m[i])
1120 del m[i]
1122 del m[i]
1121 for f in m:
1123 for f in m:
1122 repo.dirstate.normal(f)
1124 repo.dirstate.normal(f)
1123 for f in mm:
1125 for f in mm:
1124 repo.dirstate.normallookup(f)
1126 repo.dirstate.normallookup(f)
1125 for f in forget:
1127 for f in forget:
1126 repo.dirstate.forget(f)
1128 repo.dirstate.forget(f)
1127
1129
1128 if not msg:
1130 if not msg:
1129 if not message:
1131 if not message:
1130 message = "[mq]: %s\n" % patchfn
1132 message = "[mq]: %s\n" % patchfn
1131 else:
1133 else:
1132 message = "\n".join(message)
1134 message = "\n".join(message)
1133 else:
1135 else:
1134 message = msg
1136 message = msg
1135
1137
1136 if not user:
1138 if not user:
1137 user = changes[1]
1139 user = changes[1]
1138
1140
1139 self.applied.pop()
1141 self.applied.pop()
1140 self.applied_dirty = 1
1142 self.applied_dirty = 1
1141 self.strip(repo, top, update=False,
1143 self.strip(repo, top, update=False,
1142 backup='strip')
1144 backup='strip')
1143 n = repo.commit(match.files(), message, user, date, match=match,
1145 n = repo.commit(match.files(), message, user, date, match=match,
1144 force=1)
1146 force=1)
1145 self.applied.append(statusentry(revlog.hex(n), patchfn))
1147 self.applied.append(statusentry(revlog.hex(n), patchfn))
1146 self.removeundo(repo)
1148 self.removeundo(repo)
1147 else:
1149 else:
1148 self.printdiff(repo, patchparent, fp=patchf)
1150 self.printdiff(repo, patchparent, fp=patchf)
1149 patchf.close()
1151 patchf.close()
1150 added = repo.status()[1]
1152 added = repo.status()[1]
1151 for a in added:
1153 for a in added:
1152 f = repo.wjoin(a)
1154 f = repo.wjoin(a)
1153 try:
1155 try:
1154 os.unlink(f)
1156 os.unlink(f)
1155 except OSError, e:
1157 except OSError, e:
1156 if e.errno != errno.ENOENT:
1158 if e.errno != errno.ENOENT:
1157 raise
1159 raise
1158 try: os.removedirs(os.path.dirname(f))
1160 try: os.removedirs(os.path.dirname(f))
1159 except: pass
1161 except: pass
1160 # forget the file copies in the dirstate
1162 # forget the file copies in the dirstate
1161 # push should readd the files later on
1163 # push should readd the files later on
1162 repo.dirstate.forget(a)
1164 repo.dirstate.forget(a)
1163 self.pop(repo, force=True)
1165 self.pop(repo, force=True)
1164 self.push(repo, force=True)
1166 self.push(repo, force=True)
1165 finally:
1167 finally:
1166 del wlock
1168 del wlock
1167
1169
1168 def init(self, repo, create=False):
1170 def init(self, repo, create=False):
1169 if not create and os.path.isdir(self.path):
1171 if not create and os.path.isdir(self.path):
1170 raise util.Abort(_("patch queue directory already exists"))
1172 raise util.Abort(_("patch queue directory already exists"))
1171 try:
1173 try:
1172 os.mkdir(self.path)
1174 os.mkdir(self.path)
1173 except OSError, inst:
1175 except OSError, inst:
1174 if inst.errno != errno.EEXIST or not create:
1176 if inst.errno != errno.EEXIST or not create:
1175 raise
1177 raise
1176 if create:
1178 if create:
1177 return self.qrepo(create=True)
1179 return self.qrepo(create=True)
1178
1180
1179 def unapplied(self, repo, patch=None):
1181 def unapplied(self, repo, patch=None):
1180 if patch and patch not in self.series:
1182 if patch and patch not in self.series:
1181 raise util.Abort(_("patch %s is not in series file") % patch)
1183 raise util.Abort(_("patch %s is not in series file") % patch)
1182 if not patch:
1184 if not patch:
1183 start = self.series_end()
1185 start = self.series_end()
1184 else:
1186 else:
1185 start = self.series.index(patch) + 1
1187 start = self.series.index(patch) + 1
1186 unapplied = []
1188 unapplied = []
1187 for i in xrange(start, len(self.series)):
1189 for i in xrange(start, len(self.series)):
1188 pushable, reason = self.pushable(i)
1190 pushable, reason = self.pushable(i)
1189 if pushable:
1191 if pushable:
1190 unapplied.append((i, self.series[i]))
1192 unapplied.append((i, self.series[i]))
1191 self.explain_pushable(i)
1193 self.explain_pushable(i)
1192 return unapplied
1194 return unapplied
1193
1195
1194 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1196 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1195 summary=False):
1197 summary=False):
1196 def displayname(patchname):
1198 def displayname(patchname):
1197 if summary:
1199 if summary:
1198 msg = self.readheaders(patchname)[0]
1200 msg = self.readheaders(patchname)[0]
1199 msg = msg and ': ' + msg[0] or ': '
1201 msg = msg and ': ' + msg[0] or ': '
1200 else:
1202 else:
1201 msg = ''
1203 msg = ''
1202 return '%s%s' % (patchname, msg)
1204 return '%s%s' % (patchname, msg)
1203
1205
1204 applied = dict.fromkeys([p.name for p in self.applied])
1206 applied = dict.fromkeys([p.name for p in self.applied])
1205 if length is None:
1207 if length is None:
1206 length = len(self.series) - start
1208 length = len(self.series) - start
1207 if not missing:
1209 if not missing:
1208 for i in xrange(start, start+length):
1210 for i in xrange(start, start+length):
1209 patch = self.series[i]
1211 patch = self.series[i]
1210 if patch in applied:
1212 if patch in applied:
1211 stat = 'A'
1213 stat = 'A'
1212 elif self.pushable(i)[0]:
1214 elif self.pushable(i)[0]:
1213 stat = 'U'
1215 stat = 'U'
1214 else:
1216 else:
1215 stat = 'G'
1217 stat = 'G'
1216 pfx = ''
1218 pfx = ''
1217 if self.ui.verbose:
1219 if self.ui.verbose:
1218 pfx = '%d %s ' % (i, stat)
1220 pfx = '%d %s ' % (i, stat)
1219 elif status and status != stat:
1221 elif status and status != stat:
1220 continue
1222 continue
1221 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1223 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1222 else:
1224 else:
1223 msng_list = []
1225 msng_list = []
1224 for root, dirs, files in os.walk(self.path):
1226 for root, dirs, files in os.walk(self.path):
1225 d = root[len(self.path) + 1:]
1227 d = root[len(self.path) + 1:]
1226 for f in files:
1228 for f in files:
1227 fl = os.path.join(d, f)
1229 fl = os.path.join(d, f)
1228 if (fl not in self.series and
1230 if (fl not in self.series and
1229 fl not in (self.status_path, self.series_path,
1231 fl not in (self.status_path, self.series_path,
1230 self.guards_path)
1232 self.guards_path)
1231 and not fl.startswith('.')):
1233 and not fl.startswith('.')):
1232 msng_list.append(fl)
1234 msng_list.append(fl)
1233 msng_list.sort()
1235 msng_list.sort()
1234 for x in msng_list:
1236 for x in msng_list:
1235 pfx = self.ui.verbose and ('D ') or ''
1237 pfx = self.ui.verbose and ('D ') or ''
1236 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1238 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1237
1239
1238 def issaveline(self, l):
1240 def issaveline(self, l):
1239 if l.name == '.hg.patches.save.line':
1241 if l.name == '.hg.patches.save.line':
1240 return True
1242 return True
1241
1243
1242 def qrepo(self, create=False):
1244 def qrepo(self, create=False):
1243 if create or os.path.isdir(self.join(".hg")):
1245 if create or os.path.isdir(self.join(".hg")):
1244 return hg.repository(self.ui, path=self.path, create=create)
1246 return hg.repository(self.ui, path=self.path, create=create)
1245
1247
1246 def restore(self, repo, rev, delete=None, qupdate=None):
1248 def restore(self, repo, rev, delete=None, qupdate=None):
1247 c = repo.changelog.read(rev)
1249 c = repo.changelog.read(rev)
1248 desc = c[4].strip()
1250 desc = c[4].strip()
1249 lines = desc.splitlines()
1251 lines = desc.splitlines()
1250 i = 0
1252 i = 0
1251 datastart = None
1253 datastart = None
1252 series = []
1254 series = []
1253 applied = []
1255 applied = []
1254 qpp = None
1256 qpp = None
1255 for i in xrange(0, len(lines)):
1257 for i in xrange(0, len(lines)):
1256 if lines[i] == 'Patch Data:':
1258 if lines[i] == 'Patch Data:':
1257 datastart = i + 1
1259 datastart = i + 1
1258 elif lines[i].startswith('Dirstate:'):
1260 elif lines[i].startswith('Dirstate:'):
1259 l = lines[i].rstrip()
1261 l = lines[i].rstrip()
1260 l = l[10:].split(' ')
1262 l = l[10:].split(' ')
1261 qpp = [ bin(x) for x in l ]
1263 qpp = [ bin(x) for x in l ]
1262 elif datastart != None:
1264 elif datastart != None:
1263 l = lines[i].rstrip()
1265 l = lines[i].rstrip()
1264 se = statusentry(l)
1266 se = statusentry(l)
1265 file_ = se.name
1267 file_ = se.name
1266 if se.rev:
1268 if se.rev:
1267 applied.append(se)
1269 applied.append(se)
1268 else:
1270 else:
1269 series.append(file_)
1271 series.append(file_)
1270 if datastart == None:
1272 if datastart == None:
1271 self.ui.warn("No saved patch data found\n")
1273 self.ui.warn("No saved patch data found\n")
1272 return 1
1274 return 1
1273 self.ui.warn("restoring status: %s\n" % lines[0])
1275 self.ui.warn("restoring status: %s\n" % lines[0])
1274 self.full_series = series
1276 self.full_series = series
1275 self.applied = applied
1277 self.applied = applied
1276 self.parse_series()
1278 self.parse_series()
1277 self.series_dirty = 1
1279 self.series_dirty = 1
1278 self.applied_dirty = 1
1280 self.applied_dirty = 1
1279 heads = repo.changelog.heads()
1281 heads = repo.changelog.heads()
1280 if delete:
1282 if delete:
1281 if rev not in heads:
1283 if rev not in heads:
1282 self.ui.warn("save entry has children, leaving it alone\n")
1284 self.ui.warn("save entry has children, leaving it alone\n")
1283 else:
1285 else:
1284 self.ui.warn("removing save entry %s\n" % short(rev))
1286 self.ui.warn("removing save entry %s\n" % short(rev))
1285 pp = repo.dirstate.parents()
1287 pp = repo.dirstate.parents()
1286 if rev in pp:
1288 if rev in pp:
1287 update = True
1289 update = True
1288 else:
1290 else:
1289 update = False
1291 update = False
1290 self.strip(repo, rev, update=update, backup='strip')
1292 self.strip(repo, rev, update=update, backup='strip')
1291 if qpp:
1293 if qpp:
1292 self.ui.warn("saved queue repository parents: %s %s\n" %
1294 self.ui.warn("saved queue repository parents: %s %s\n" %
1293 (short(qpp[0]), short(qpp[1])))
1295 (short(qpp[0]), short(qpp[1])))
1294 if qupdate:
1296 if qupdate:
1295 self.ui.status(_("queue directory updating\n"))
1297 self.ui.status(_("queue directory updating\n"))
1296 r = self.qrepo()
1298 r = self.qrepo()
1297 if not r:
1299 if not r:
1298 self.ui.warn("Unable to load queue repository\n")
1300 self.ui.warn("Unable to load queue repository\n")
1299 return 1
1301 return 1
1300 hg.clean(r, qpp[0])
1302 hg.clean(r, qpp[0])
1301
1303
1302 def save(self, repo, msg=None):
1304 def save(self, repo, msg=None):
1303 if len(self.applied) == 0:
1305 if len(self.applied) == 0:
1304 self.ui.warn("save: no patches applied, exiting\n")
1306 self.ui.warn("save: no patches applied, exiting\n")
1305 return 1
1307 return 1
1306 if self.issaveline(self.applied[-1]):
1308 if self.issaveline(self.applied[-1]):
1307 self.ui.warn("status is already saved\n")
1309 self.ui.warn("status is already saved\n")
1308 return 1
1310 return 1
1309
1311
1310 ar = [ ':' + x for x in self.full_series ]
1312 ar = [ ':' + x for x in self.full_series ]
1311 if not msg:
1313 if not msg:
1312 msg = "hg patches saved state"
1314 msg = "hg patches saved state"
1313 else:
1315 else:
1314 msg = "hg patches: " + msg.rstrip('\r\n')
1316 msg = "hg patches: " + msg.rstrip('\r\n')
1315 r = self.qrepo()
1317 r = self.qrepo()
1316 if r:
1318 if r:
1317 pp = r.dirstate.parents()
1319 pp = r.dirstate.parents()
1318 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1320 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1319 msg += "\n\nPatch Data:\n"
1321 msg += "\n\nPatch Data:\n"
1320 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1322 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1321 "\n".join(ar) + '\n' or "")
1323 "\n".join(ar) + '\n' or "")
1322 n = repo.commit(None, text, user=None, force=1)
1324 n = repo.commit(None, text, user=None, force=1)
1323 if not n:
1325 if not n:
1324 self.ui.warn("repo commit failed\n")
1326 self.ui.warn("repo commit failed\n")
1325 return 1
1327 return 1
1326 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1328 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1327 self.applied_dirty = 1
1329 self.applied_dirty = 1
1328 self.removeundo(repo)
1330 self.removeundo(repo)
1329
1331
1330 def full_series_end(self):
1332 def full_series_end(self):
1331 if len(self.applied) > 0:
1333 if len(self.applied) > 0:
1332 p = self.applied[-1].name
1334 p = self.applied[-1].name
1333 end = self.find_series(p)
1335 end = self.find_series(p)
1334 if end == None:
1336 if end == None:
1335 return len(self.full_series)
1337 return len(self.full_series)
1336 return end + 1
1338 return end + 1
1337 return 0
1339 return 0
1338
1340
1339 def series_end(self, all_patches=False):
1341 def series_end(self, all_patches=False):
1340 """If all_patches is False, return the index of the next pushable patch
1342 """If all_patches is False, return the index of the next pushable patch
1341 in the series, or the series length. If all_patches is True, return the
1343 in the series, or the series length. If all_patches is True, return the
1342 index of the first patch past the last applied one.
1344 index of the first patch past the last applied one.
1343 """
1345 """
1344 end = 0
1346 end = 0
1345 def next(start):
1347 def next(start):
1346 if all_patches:
1348 if all_patches:
1347 return start
1349 return start
1348 i = start
1350 i = start
1349 while i < len(self.series):
1351 while i < len(self.series):
1350 p, reason = self.pushable(i)
1352 p, reason = self.pushable(i)
1351 if p:
1353 if p:
1352 break
1354 break
1353 self.explain_pushable(i)
1355 self.explain_pushable(i)
1354 i += 1
1356 i += 1
1355 return i
1357 return i
1356 if len(self.applied) > 0:
1358 if len(self.applied) > 0:
1357 p = self.applied[-1].name
1359 p = self.applied[-1].name
1358 try:
1360 try:
1359 end = self.series.index(p)
1361 end = self.series.index(p)
1360 except ValueError:
1362 except ValueError:
1361 return 0
1363 return 0
1362 return next(end + 1)
1364 return next(end + 1)
1363 return next(end)
1365 return next(end)
1364
1366
1365 def appliedname(self, index):
1367 def appliedname(self, index):
1366 pname = self.applied[index].name
1368 pname = self.applied[index].name
1367 if not self.ui.verbose:
1369 if not self.ui.verbose:
1368 p = pname
1370 p = pname
1369 else:
1371 else:
1370 p = str(self.series.index(pname)) + " " + pname
1372 p = str(self.series.index(pname)) + " " + pname
1371 return p
1373 return p
1372
1374
1373 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1375 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1374 force=None, git=False):
1376 force=None, git=False):
1375 def checkseries(patchname):
1377 def checkseries(patchname):
1376 if patchname in self.series:
1378 if patchname in self.series:
1377 raise util.Abort(_('patch %s is already in the series file')
1379 raise util.Abort(_('patch %s is already in the series file')
1378 % patchname)
1380 % patchname)
1379 def checkfile(patchname):
1381 def checkfile(patchname):
1380 if not force and os.path.exists(self.join(patchname)):
1382 if not force and os.path.exists(self.join(patchname)):
1381 raise util.Abort(_('patch "%s" already exists')
1383 raise util.Abort(_('patch "%s" already exists')
1382 % patchname)
1384 % patchname)
1383
1385
1384 if rev:
1386 if rev:
1385 if files:
1387 if files:
1386 raise util.Abort(_('option "-r" not valid when importing '
1388 raise util.Abort(_('option "-r" not valid when importing '
1387 'files'))
1389 'files'))
1388 rev = cmdutil.revrange(repo, rev)
1390 rev = cmdutil.revrange(repo, rev)
1389 rev.sort(lambda x, y: cmp(y, x))
1391 rev.sort(lambda x, y: cmp(y, x))
1390 if (len(files) > 1 or len(rev) > 1) and patchname:
1392 if (len(files) > 1 or len(rev) > 1) and patchname:
1391 raise util.Abort(_('option "-n" not valid when importing multiple '
1393 raise util.Abort(_('option "-n" not valid when importing multiple '
1392 'patches'))
1394 'patches'))
1393 i = 0
1395 i = 0
1394 added = []
1396 added = []
1395 if rev:
1397 if rev:
1396 # If mq patches are applied, we can only import revisions
1398 # If mq patches are applied, we can only import revisions
1397 # that form a linear path to qbase.
1399 # that form a linear path to qbase.
1398 # Otherwise, they should form a linear path to a head.
1400 # Otherwise, they should form a linear path to a head.
1399 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1401 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1400 if len(heads) > 1:
1402 if len(heads) > 1:
1401 raise util.Abort(_('revision %d is the root of more than one '
1403 raise util.Abort(_('revision %d is the root of more than one '
1402 'branch') % rev[-1])
1404 'branch') % rev[-1])
1403 if self.applied:
1405 if self.applied:
1404 base = revlog.hex(repo.changelog.node(rev[0]))
1406 base = revlog.hex(repo.changelog.node(rev[0]))
1405 if base in [n.rev for n in self.applied]:
1407 if base in [n.rev for n in self.applied]:
1406 raise util.Abort(_('revision %d is already managed')
1408 raise util.Abort(_('revision %d is already managed')
1407 % rev[0])
1409 % rev[0])
1408 if heads != [revlog.bin(self.applied[-1].rev)]:
1410 if heads != [revlog.bin(self.applied[-1].rev)]:
1409 raise util.Abort(_('revision %d is not the parent of '
1411 raise util.Abort(_('revision %d is not the parent of '
1410 'the queue') % rev[0])
1412 'the queue') % rev[0])
1411 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1413 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1412 lastparent = repo.changelog.parentrevs(base)[0]
1414 lastparent = repo.changelog.parentrevs(base)[0]
1413 else:
1415 else:
1414 if heads != [repo.changelog.node(rev[0])]:
1416 if heads != [repo.changelog.node(rev[0])]:
1415 raise util.Abort(_('revision %d has unmanaged children')
1417 raise util.Abort(_('revision %d has unmanaged children')
1416 % rev[0])
1418 % rev[0])
1417 lastparent = None
1419 lastparent = None
1418
1420
1419 if git:
1421 if git:
1420 self.diffopts().git = True
1422 self.diffopts().git = True
1421
1423
1422 for r in rev:
1424 for r in rev:
1423 p1, p2 = repo.changelog.parentrevs(r)
1425 p1, p2 = repo.changelog.parentrevs(r)
1424 n = repo.changelog.node(r)
1426 n = repo.changelog.node(r)
1425 if p2 != revlog.nullrev:
1427 if p2 != revlog.nullrev:
1426 raise util.Abort(_('cannot import merge revision %d') % r)
1428 raise util.Abort(_('cannot import merge revision %d') % r)
1427 if lastparent and lastparent != r:
1429 if lastparent and lastparent != r:
1428 raise util.Abort(_('revision %d is not the parent of %d')
1430 raise util.Abort(_('revision %d is not the parent of %d')
1429 % (r, lastparent))
1431 % (r, lastparent))
1430 lastparent = p1
1432 lastparent = p1
1431
1433
1432 if not patchname:
1434 if not patchname:
1433 patchname = normname('%d.diff' % r)
1435 patchname = normname('%d.diff' % r)
1434 self.check_reserved_name(patchname)
1436 self.check_reserved_name(patchname)
1435 checkseries(patchname)
1437 checkseries(patchname)
1436 checkfile(patchname)
1438 checkfile(patchname)
1437 self.full_series.insert(0, patchname)
1439 self.full_series.insert(0, patchname)
1438
1440
1439 patchf = self.opener(patchname, "w")
1441 patchf = self.opener(patchname, "w")
1440 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1442 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1441 patchf.close()
1443 patchf.close()
1442
1444
1443 se = statusentry(revlog.hex(n), patchname)
1445 se = statusentry(revlog.hex(n), patchname)
1444 self.applied.insert(0, se)
1446 self.applied.insert(0, se)
1445
1447
1446 added.append(patchname)
1448 added.append(patchname)
1447 patchname = None
1449 patchname = None
1448 self.parse_series()
1450 self.parse_series()
1449 self.applied_dirty = 1
1451 self.applied_dirty = 1
1450
1452
1451 for filename in files:
1453 for filename in files:
1452 if existing:
1454 if existing:
1453 if filename == '-':
1455 if filename == '-':
1454 raise util.Abort(_('-e is incompatible with import from -'))
1456 raise util.Abort(_('-e is incompatible with import from -'))
1455 if not patchname:
1457 if not patchname:
1456 patchname = normname(filename)
1458 patchname = normname(filename)
1457 self.check_reserved_name(patchname)
1459 self.check_reserved_name(patchname)
1458 if not os.path.isfile(self.join(patchname)):
1460 if not os.path.isfile(self.join(patchname)):
1459 raise util.Abort(_("patch %s does not exist") % patchname)
1461 raise util.Abort(_("patch %s does not exist") % patchname)
1460 else:
1462 else:
1461 try:
1463 try:
1462 if filename == '-':
1464 if filename == '-':
1463 if not patchname:
1465 if not patchname:
1464 raise util.Abort(_('need --name to import a patch from -'))
1466 raise util.Abort(_('need --name to import a patch from -'))
1465 text = sys.stdin.read()
1467 text = sys.stdin.read()
1466 else:
1468 else:
1467 text = file(filename, 'rb').read()
1469 text = file(filename, 'rb').read()
1468 except IOError:
1470 except IOError:
1469 raise util.Abort(_("unable to read %s") % patchname)
1471 raise util.Abort(_("unable to read %s") % patchname)
1470 if not patchname:
1472 if not patchname:
1471 patchname = normname(os.path.basename(filename))
1473 patchname = normname(os.path.basename(filename))
1472 self.check_reserved_name(patchname)
1474 self.check_reserved_name(patchname)
1473 checkfile(patchname)
1475 checkfile(patchname)
1474 patchf = self.opener(patchname, "w")
1476 patchf = self.opener(patchname, "w")
1475 patchf.write(text)
1477 patchf.write(text)
1476 checkseries(patchname)
1478 checkseries(patchname)
1477 index = self.full_series_end() + i
1479 index = self.full_series_end() + i
1478 self.full_series[index:index] = [patchname]
1480 self.full_series[index:index] = [patchname]
1479 self.parse_series()
1481 self.parse_series()
1480 self.ui.warn("adding %s to series file\n" % patchname)
1482 self.ui.warn("adding %s to series file\n" % patchname)
1481 i += 1
1483 i += 1
1482 added.append(patchname)
1484 added.append(patchname)
1483 patchname = None
1485 patchname = None
1484 self.series_dirty = 1
1486 self.series_dirty = 1
1485 qrepo = self.qrepo()
1487 qrepo = self.qrepo()
1486 if qrepo:
1488 if qrepo:
1487 qrepo.add(added)
1489 qrepo.add(added)
1488
1490
1489 def delete(ui, repo, *patches, **opts):
1491 def delete(ui, repo, *patches, **opts):
1490 """remove patches from queue
1492 """remove patches from queue
1491
1493
1492 The patches must not be applied, unless they are arguments to
1494 The patches must not be applied, unless they are arguments to
1493 the --rev parameter. At least one patch or revision is required.
1495 the --rev parameter. At least one patch or revision is required.
1494
1496
1495 With --rev, mq will stop managing the named revisions (converting
1497 With --rev, mq will stop managing the named revisions (converting
1496 them to regular mercurial changesets). The patches must be applied
1498 them to regular mercurial changesets). The patches must be applied
1497 and at the base of the stack. This option is useful when the patches
1499 and at the base of the stack. This option is useful when the patches
1498 have been applied upstream.
1500 have been applied upstream.
1499
1501
1500 With --keep, the patch files are preserved in the patch directory."""
1502 With --keep, the patch files are preserved in the patch directory."""
1501 q = repo.mq
1503 q = repo.mq
1502 q.delete(repo, patches, opts)
1504 q.delete(repo, patches, opts)
1503 q.save_dirty()
1505 q.save_dirty()
1504 return 0
1506 return 0
1505
1507
1506 def applied(ui, repo, patch=None, **opts):
1508 def applied(ui, repo, patch=None, **opts):
1507 """print the patches already applied"""
1509 """print the patches already applied"""
1508 q = repo.mq
1510 q = repo.mq
1509 if patch:
1511 if patch:
1510 if patch not in q.series:
1512 if patch not in q.series:
1511 raise util.Abort(_("patch %s is not in series file") % patch)
1513 raise util.Abort(_("patch %s is not in series file") % patch)
1512 end = q.series.index(patch) + 1
1514 end = q.series.index(patch) + 1
1513 else:
1515 else:
1514 end = q.series_end(True)
1516 end = q.series_end(True)
1515 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1517 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1516
1518
1517 def unapplied(ui, repo, patch=None, **opts):
1519 def unapplied(ui, repo, patch=None, **opts):
1518 """print the patches not yet applied"""
1520 """print the patches not yet applied"""
1519 q = repo.mq
1521 q = repo.mq
1520 if patch:
1522 if patch:
1521 if patch not in q.series:
1523 if patch not in q.series:
1522 raise util.Abort(_("patch %s is not in series file") % patch)
1524 raise util.Abort(_("patch %s is not in series file") % patch)
1523 start = q.series.index(patch) + 1
1525 start = q.series.index(patch) + 1
1524 else:
1526 else:
1525 start = q.series_end(True)
1527 start = q.series_end(True)
1526 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1528 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1527
1529
1528 def qimport(ui, repo, *filename, **opts):
1530 def qimport(ui, repo, *filename, **opts):
1529 """import a patch
1531 """import a patch
1530
1532
1531 The patch will have the same name as its source file unless you
1533 The patch will have the same name as its source file unless you
1532 give it a new one with --name.
1534 give it a new one with --name.
1533
1535
1534 You can register an existing patch inside the patch directory
1536 You can register an existing patch inside the patch directory
1535 with the --existing flag.
1537 with the --existing flag.
1536
1538
1537 With --force, an existing patch of the same name will be overwritten.
1539 With --force, an existing patch of the same name will be overwritten.
1538
1540
1539 An existing changeset may be placed under mq control with --rev
1541 An existing changeset may be placed under mq control with --rev
1540 (e.g. qimport --rev tip -n patch will place tip under mq control).
1542 (e.g. qimport --rev tip -n patch will place tip under mq control).
1541 With --git, patches imported with --rev will use the git diff
1543 With --git, patches imported with --rev will use the git diff
1542 format.
1544 format.
1543 """
1545 """
1544 q = repo.mq
1546 q = repo.mq
1545 q.qimport(repo, filename, patchname=opts['name'],
1547 q.qimport(repo, filename, patchname=opts['name'],
1546 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1548 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1547 git=opts['git'])
1549 git=opts['git'])
1548 q.save_dirty()
1550 q.save_dirty()
1549 return 0
1551 return 0
1550
1552
1551 def init(ui, repo, **opts):
1553 def init(ui, repo, **opts):
1552 """init a new queue repository
1554 """init a new queue repository
1553
1555
1554 The queue repository is unversioned by default. If -c is
1556 The queue repository is unversioned by default. If -c is
1555 specified, qinit will create a separate nested repository
1557 specified, qinit will create a separate nested repository
1556 for patches (qinit -c may also be run later to convert
1558 for patches (qinit -c may also be run later to convert
1557 an unversioned patch repository into a versioned one).
1559 an unversioned patch repository into a versioned one).
1558 You can use qcommit to commit changes to this queue repository."""
1560 You can use qcommit to commit changes to this queue repository."""
1559 q = repo.mq
1561 q = repo.mq
1560 r = q.init(repo, create=opts['create_repo'])
1562 r = q.init(repo, create=opts['create_repo'])
1561 q.save_dirty()
1563 q.save_dirty()
1562 if r:
1564 if r:
1563 if not os.path.exists(r.wjoin('.hgignore')):
1565 if not os.path.exists(r.wjoin('.hgignore')):
1564 fp = r.wopener('.hgignore', 'w')
1566 fp = r.wopener('.hgignore', 'w')
1565 fp.write('^\\.hg\n')
1567 fp.write('^\\.hg\n')
1566 fp.write('^\\.mq\n')
1568 fp.write('^\\.mq\n')
1567 fp.write('syntax: glob\n')
1569 fp.write('syntax: glob\n')
1568 fp.write('status\n')
1570 fp.write('status\n')
1569 fp.write('guards\n')
1571 fp.write('guards\n')
1570 fp.close()
1572 fp.close()
1571 if not os.path.exists(r.wjoin('series')):
1573 if not os.path.exists(r.wjoin('series')):
1572 r.wopener('series', 'w').close()
1574 r.wopener('series', 'w').close()
1573 r.add(['.hgignore', 'series'])
1575 r.add(['.hgignore', 'series'])
1574 commands.add(ui, r)
1576 commands.add(ui, r)
1575 return 0
1577 return 0
1576
1578
1577 def clone(ui, source, dest=None, **opts):
1579 def clone(ui, source, dest=None, **opts):
1578 '''clone main and patch repository at same time
1580 '''clone main and patch repository at same time
1579
1581
1580 If source is local, destination will have no patches applied. If
1582 If source is local, destination will have no patches applied. If
1581 source is remote, this command can not check if patches are
1583 source is remote, this command can not check if patches are
1582 applied in source, so cannot guarantee that patches are not
1584 applied in source, so cannot guarantee that patches are not
1583 applied in destination. If you clone remote repository, be sure
1585 applied in destination. If you clone remote repository, be sure
1584 before that it has no patches applied.
1586 before that it has no patches applied.
1585
1587
1586 Source patch repository is looked for in <src>/.hg/patches by
1588 Source patch repository is looked for in <src>/.hg/patches by
1587 default. Use -p <url> to change.
1589 default. Use -p <url> to change.
1588
1590
1589 The patch directory must be a nested mercurial repository, as
1591 The patch directory must be a nested mercurial repository, as
1590 would be created by qinit -c.
1592 would be created by qinit -c.
1591 '''
1593 '''
1592 def patchdir(repo):
1594 def patchdir(repo):
1593 url = repo.url()
1595 url = repo.url()
1594 if url.endswith('/'):
1596 if url.endswith('/'):
1595 url = url[:-1]
1597 url = url[:-1]
1596 return url + '/.hg/patches'
1598 return url + '/.hg/patches'
1597 cmdutil.setremoteconfig(ui, opts)
1599 cmdutil.setremoteconfig(ui, opts)
1598 if dest is None:
1600 if dest is None:
1599 dest = hg.defaultdest(source)
1601 dest = hg.defaultdest(source)
1600 sr = hg.repository(ui, ui.expandpath(source))
1602 sr = hg.repository(ui, ui.expandpath(source))
1601 patchespath = opts['patches'] or patchdir(sr)
1603 patchespath = opts['patches'] or patchdir(sr)
1602 try:
1604 try:
1603 pr = hg.repository(ui, patchespath)
1605 pr = hg.repository(ui, patchespath)
1604 except RepoError:
1606 except RepoError:
1605 raise util.Abort(_('versioned patch repository not found'
1607 raise util.Abort(_('versioned patch repository not found'
1606 ' (see qinit -c)'))
1608 ' (see qinit -c)'))
1607 qbase, destrev = None, None
1609 qbase, destrev = None, None
1608 if sr.local():
1610 if sr.local():
1609 if sr.mq.applied:
1611 if sr.mq.applied:
1610 qbase = revlog.bin(sr.mq.applied[0].rev)
1612 qbase = revlog.bin(sr.mq.applied[0].rev)
1611 if not hg.islocal(dest):
1613 if not hg.islocal(dest):
1612 heads = dict.fromkeys(sr.heads())
1614 heads = dict.fromkeys(sr.heads())
1613 for h in sr.heads(qbase):
1615 for h in sr.heads(qbase):
1614 del heads[h]
1616 del heads[h]
1615 destrev = heads.keys()
1617 destrev = heads.keys()
1616 destrev.append(sr.changelog.parents(qbase)[0])
1618 destrev.append(sr.changelog.parents(qbase)[0])
1617 elif sr.capable('lookup'):
1619 elif sr.capable('lookup'):
1618 try:
1620 try:
1619 qbase = sr.lookup('qbase')
1621 qbase = sr.lookup('qbase')
1620 except RepoError:
1622 except RepoError:
1621 pass
1623 pass
1622 ui.note(_('cloning main repo\n'))
1624 ui.note(_('cloning main repo\n'))
1623 sr, dr = hg.clone(ui, sr.url(), dest,
1625 sr, dr = hg.clone(ui, sr.url(), dest,
1624 pull=opts['pull'],
1626 pull=opts['pull'],
1625 rev=destrev,
1627 rev=destrev,
1626 update=False,
1628 update=False,
1627 stream=opts['uncompressed'])
1629 stream=opts['uncompressed'])
1628 ui.note(_('cloning patch repo\n'))
1630 ui.note(_('cloning patch repo\n'))
1629 spr, dpr = hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1631 spr, dpr = hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1630 pull=opts['pull'], update=not opts['noupdate'],
1632 pull=opts['pull'], update=not opts['noupdate'],
1631 stream=opts['uncompressed'])
1633 stream=opts['uncompressed'])
1632 if dr.local():
1634 if dr.local():
1633 if qbase:
1635 if qbase:
1634 ui.note(_('stripping applied patches from destination repo\n'))
1636 ui.note(_('stripping applied patches from destination repo\n'))
1635 dr.mq.strip(dr, qbase, update=False, backup=None)
1637 dr.mq.strip(dr, qbase, update=False, backup=None)
1636 if not opts['noupdate']:
1638 if not opts['noupdate']:
1637 ui.note(_('updating destination repo\n'))
1639 ui.note(_('updating destination repo\n'))
1638 hg.update(dr, dr.changelog.tip())
1640 hg.update(dr, dr.changelog.tip())
1639
1641
1640 def commit(ui, repo, *pats, **opts):
1642 def commit(ui, repo, *pats, **opts):
1641 """commit changes in the queue repository"""
1643 """commit changes in the queue repository"""
1642 q = repo.mq
1644 q = repo.mq
1643 r = q.qrepo()
1645 r = q.qrepo()
1644 if not r: raise util.Abort('no queue repository')
1646 if not r: raise util.Abort('no queue repository')
1645 commands.commit(r.ui, r, *pats, **opts)
1647 commands.commit(r.ui, r, *pats, **opts)
1646
1648
1647 def series(ui, repo, **opts):
1649 def series(ui, repo, **opts):
1648 """print the entire series file"""
1650 """print the entire series file"""
1649 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1651 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1650 return 0
1652 return 0
1651
1653
1652 def top(ui, repo, **opts):
1654 def top(ui, repo, **opts):
1653 """print the name of the current patch"""
1655 """print the name of the current patch"""
1654 q = repo.mq
1656 q = repo.mq
1655 t = q.applied and q.series_end(True) or 0
1657 t = q.applied and q.series_end(True) or 0
1656 if t:
1658 if t:
1657 return q.qseries(repo, start=t-1, length=1, status='A',
1659 return q.qseries(repo, start=t-1, length=1, status='A',
1658 summary=opts.get('summary'))
1660 summary=opts.get('summary'))
1659 else:
1661 else:
1660 ui.write("No patches applied\n")
1662 ui.write("No patches applied\n")
1661 return 1
1663 return 1
1662
1664
1663 def next(ui, repo, **opts):
1665 def next(ui, repo, **opts):
1664 """print the name of the next patch"""
1666 """print the name of the next patch"""
1665 q = repo.mq
1667 q = repo.mq
1666 end = q.series_end()
1668 end = q.series_end()
1667 if end == len(q.series):
1669 if end == len(q.series):
1668 ui.write("All patches applied\n")
1670 ui.write("All patches applied\n")
1669 return 1
1671 return 1
1670 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1672 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1671
1673
1672 def prev(ui, repo, **opts):
1674 def prev(ui, repo, **opts):
1673 """print the name of the previous patch"""
1675 """print the name of the previous patch"""
1674 q = repo.mq
1676 q = repo.mq
1675 l = len(q.applied)
1677 l = len(q.applied)
1676 if l == 1:
1678 if l == 1:
1677 ui.write("Only one patch applied\n")
1679 ui.write("Only one patch applied\n")
1678 return 1
1680 return 1
1679 if not l:
1681 if not l:
1680 ui.write("No patches applied\n")
1682 ui.write("No patches applied\n")
1681 return 1
1683 return 1
1682 return q.qseries(repo, start=l-2, length=1, status='A',
1684 return q.qseries(repo, start=l-2, length=1, status='A',
1683 summary=opts.get('summary'))
1685 summary=opts.get('summary'))
1684
1686
1685 def setupheaderopts(ui, opts):
1687 def setupheaderopts(ui, opts):
1686 def do(opt,val):
1688 def do(opt,val):
1687 if not opts[opt] and opts['current' + opt]:
1689 if not opts[opt] and opts['current' + opt]:
1688 opts[opt] = val
1690 opts[opt] = val
1689 do('user', ui.username())
1691 do('user', ui.username())
1690 do('date', "%d %d" % util.makedate())
1692 do('date', "%d %d" % util.makedate())
1691
1693
1692 def new(ui, repo, patch, *args, **opts):
1694 def new(ui, repo, patch, *args, **opts):
1693 """create a new patch
1695 """create a new patch
1694
1696
1695 qnew creates a new patch on top of the currently-applied patch
1697 qnew creates a new patch on top of the currently-applied patch
1696 (if any). It will refuse to run if there are any outstanding
1698 (if any). It will refuse to run if there are any outstanding
1697 changes unless -f is specified, in which case the patch will
1699 changes unless -f is specified, in which case the patch will
1698 be initialised with them. You may also use -I, -X, and/or a list of
1700 be initialised with them. You may also use -I, -X, and/or a list of
1699 files after the patch name to add only changes to matching files
1701 files after the patch name to add only changes to matching files
1700 to the new patch, leaving the rest as uncommitted modifications.
1702 to the new patch, leaving the rest as uncommitted modifications.
1701
1703
1702 -e, -m or -l set the patch header as well as the commit message.
1704 -e, -m or -l set the patch header as well as the commit message.
1703 If none is specified, the patch header is empty and the
1705 If none is specified, the patch header is empty and the
1704 commit message is '[mq]: PATCH'"""
1706 commit message is '[mq]: PATCH'"""
1705 q = repo.mq
1707 q = repo.mq
1706 message = cmdutil.logmessage(opts)
1708 message = cmdutil.logmessage(opts)
1707 if opts['edit']:
1709 if opts['edit']:
1708 message = ui.edit(message, ui.username())
1710 message = ui.edit(message, ui.username())
1709 opts['msg'] = message
1711 opts['msg'] = message
1710 setupheaderopts(ui, opts)
1712 setupheaderopts(ui, opts)
1711 q.new(repo, patch, *args, **opts)
1713 q.new(repo, patch, *args, **opts)
1712 q.save_dirty()
1714 q.save_dirty()
1713 return 0
1715 return 0
1714
1716
1715 def refresh(ui, repo, *pats, **opts):
1717 def refresh(ui, repo, *pats, **opts):
1716 """update the current patch
1718 """update the current patch
1717
1719
1718 If any file patterns are provided, the refreshed patch will contain only
1720 If any file patterns are provided, the refreshed patch will contain only
1719 the modifications that match those patterns; the remaining modifications
1721 the modifications that match those patterns; the remaining modifications
1720 will remain in the working directory.
1722 will remain in the working directory.
1721
1723
1722 hg add/remove/copy/rename work as usual, though you might want to use
1724 hg add/remove/copy/rename work as usual, though you might want to use
1723 git-style patches (--git or [diff] git=1) to track copies and renames.
1725 git-style patches (--git or [diff] git=1) to track copies and renames.
1724 """
1726 """
1725 q = repo.mq
1727 q = repo.mq
1726 message = cmdutil.logmessage(opts)
1728 message = cmdutil.logmessage(opts)
1727 if opts['edit']:
1729 if opts['edit']:
1728 if not q.applied:
1730 if not q.applied:
1729 ui.write(_("No patches applied\n"))
1731 ui.write(_("No patches applied\n"))
1730 return 1
1732 return 1
1731 if message:
1733 if message:
1732 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1734 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1733 patch = q.applied[-1].name
1735 patch = q.applied[-1].name
1734 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1736 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1735 message = ui.edit('\n'.join(message), user or ui.username())
1737 message = ui.edit('\n'.join(message), user or ui.username())
1736 setupheaderopts(ui, opts)
1738 setupheaderopts(ui, opts)
1737 ret = q.refresh(repo, pats, msg=message, **opts)
1739 ret = q.refresh(repo, pats, msg=message, **opts)
1738 q.save_dirty()
1740 q.save_dirty()
1739 return ret
1741 return ret
1740
1742
1741 def diff(ui, repo, *pats, **opts):
1743 def diff(ui, repo, *pats, **opts):
1742 """diff of the current patch"""
1744 """diff of the current patch"""
1743 repo.mq.diff(repo, pats, opts)
1745 repo.mq.diff(repo, pats, opts)
1744 return 0
1746 return 0
1745
1747
1746 def fold(ui, repo, *files, **opts):
1748 def fold(ui, repo, *files, **opts):
1747 """fold the named patches into the current patch
1749 """fold the named patches into the current patch
1748
1750
1749 Patches must not yet be applied. Each patch will be successively
1751 Patches must not yet be applied. Each patch will be successively
1750 applied to the current patch in the order given. If all the
1752 applied to the current patch in the order given. If all the
1751 patches apply successfully, the current patch will be refreshed
1753 patches apply successfully, the current patch will be refreshed
1752 with the new cumulative patch, and the folded patches will
1754 with the new cumulative patch, and the folded patches will
1753 be deleted. With -k/--keep, the folded patch files will not
1755 be deleted. With -k/--keep, the folded patch files will not
1754 be removed afterwards.
1756 be removed afterwards.
1755
1757
1756 The header for each folded patch will be concatenated with
1758 The header for each folded patch will be concatenated with
1757 the current patch header, separated by a line of '* * *'."""
1759 the current patch header, separated by a line of '* * *'."""
1758
1760
1759 q = repo.mq
1761 q = repo.mq
1760
1762
1761 if not files:
1763 if not files:
1762 raise util.Abort(_('qfold requires at least one patch name'))
1764 raise util.Abort(_('qfold requires at least one patch name'))
1763 if not q.check_toppatch(repo):
1765 if not q.check_toppatch(repo):
1764 raise util.Abort(_('No patches applied'))
1766 raise util.Abort(_('No patches applied'))
1765
1767
1766 message = cmdutil.logmessage(opts)
1768 message = cmdutil.logmessage(opts)
1767 if opts['edit']:
1769 if opts['edit']:
1768 if message:
1770 if message:
1769 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1771 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1770
1772
1771 parent = q.lookup('qtip')
1773 parent = q.lookup('qtip')
1772 patches = []
1774 patches = []
1773 messages = []
1775 messages = []
1774 for f in files:
1776 for f in files:
1775 p = q.lookup(f)
1777 p = q.lookup(f)
1776 if p in patches or p == parent:
1778 if p in patches or p == parent:
1777 ui.warn(_('Skipping already folded patch %s') % p)
1779 ui.warn(_('Skipping already folded patch %s') % p)
1778 if q.isapplied(p):
1780 if q.isapplied(p):
1779 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1781 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1780 patches.append(p)
1782 patches.append(p)
1781
1783
1782 for p in patches:
1784 for p in patches:
1783 if not message:
1785 if not message:
1784 messages.append(q.readheaders(p)[0])
1786 messages.append(q.readheaders(p)[0])
1785 pf = q.join(p)
1787 pf = q.join(p)
1786 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1788 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1787 if not patchsuccess:
1789 if not patchsuccess:
1788 raise util.Abort(_('Error folding patch %s') % p)
1790 raise util.Abort(_('Error folding patch %s') % p)
1789 patch.updatedir(ui, repo, files)
1791 patch.updatedir(ui, repo, files)
1790
1792
1791 if not message:
1793 if not message:
1792 message, comments, user = q.readheaders(parent)[0:3]
1794 message, comments, user = q.readheaders(parent)[0:3]
1793 for msg in messages:
1795 for msg in messages:
1794 message.append('* * *')
1796 message.append('* * *')
1795 message.extend(msg)
1797 message.extend(msg)
1796 message = '\n'.join(message)
1798 message = '\n'.join(message)
1797
1799
1798 if opts['edit']:
1800 if opts['edit']:
1799 message = ui.edit(message, user or ui.username())
1801 message = ui.edit(message, user or ui.username())
1800
1802
1801 q.refresh(repo, msg=message)
1803 q.refresh(repo, msg=message)
1802 q.delete(repo, patches, opts)
1804 q.delete(repo, patches, opts)
1803 q.save_dirty()
1805 q.save_dirty()
1804
1806
1805 def goto(ui, repo, patch, **opts):
1807 def goto(ui, repo, patch, **opts):
1806 '''push or pop patches until named patch is at top of stack'''
1808 '''push or pop patches until named patch is at top of stack'''
1807 q = repo.mq
1809 q = repo.mq
1808 patch = q.lookup(patch)
1810 patch = q.lookup(patch)
1809 if q.isapplied(patch):
1811 if q.isapplied(patch):
1810 ret = q.pop(repo, patch, force=opts['force'])
1812 ret = q.pop(repo, patch, force=opts['force'])
1811 else:
1813 else:
1812 ret = q.push(repo, patch, force=opts['force'])
1814 ret = q.push(repo, patch, force=opts['force'])
1813 q.save_dirty()
1815 q.save_dirty()
1814 return ret
1816 return ret
1815
1817
1816 def guard(ui, repo, *args, **opts):
1818 def guard(ui, repo, *args, **opts):
1817 '''set or print guards for a patch
1819 '''set or print guards for a patch
1818
1820
1819 Guards control whether a patch can be pushed. A patch with no
1821 Guards control whether a patch can be pushed. A patch with no
1820 guards is always pushed. A patch with a positive guard ("+foo") is
1822 guards is always pushed. A patch with a positive guard ("+foo") is
1821 pushed only if the qselect command has activated it. A patch with
1823 pushed only if the qselect command has activated it. A patch with
1822 a negative guard ("-foo") is never pushed if the qselect command
1824 a negative guard ("-foo") is never pushed if the qselect command
1823 has activated it.
1825 has activated it.
1824
1826
1825 With no arguments, print the currently active guards.
1827 With no arguments, print the currently active guards.
1826 With arguments, set guards for the named patch.
1828 With arguments, set guards for the named patch.
1827
1829
1828 To set a negative guard "-foo" on topmost patch ("--" is needed so
1830 To set a negative guard "-foo" on topmost patch ("--" is needed so
1829 hg will not interpret "-foo" as an option):
1831 hg will not interpret "-foo" as an option):
1830 hg qguard -- -foo
1832 hg qguard -- -foo
1831
1833
1832 To set guards on another patch:
1834 To set guards on another patch:
1833 hg qguard other.patch +2.6.17 -stable
1835 hg qguard other.patch +2.6.17 -stable
1834 '''
1836 '''
1835 def status(idx):
1837 def status(idx):
1836 guards = q.series_guards[idx] or ['unguarded']
1838 guards = q.series_guards[idx] or ['unguarded']
1837 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1839 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1838 q = repo.mq
1840 q = repo.mq
1839 patch = None
1841 patch = None
1840 args = list(args)
1842 args = list(args)
1841 if opts['list']:
1843 if opts['list']:
1842 if args or opts['none']:
1844 if args or opts['none']:
1843 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1845 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1844 for i in xrange(len(q.series)):
1846 for i in xrange(len(q.series)):
1845 status(i)
1847 status(i)
1846 return
1848 return
1847 if not args or args[0][0:1] in '-+':
1849 if not args or args[0][0:1] in '-+':
1848 if not q.applied:
1850 if not q.applied:
1849 raise util.Abort(_('no patches applied'))
1851 raise util.Abort(_('no patches applied'))
1850 patch = q.applied[-1].name
1852 patch = q.applied[-1].name
1851 if patch is None and args[0][0:1] not in '-+':
1853 if patch is None and args[0][0:1] not in '-+':
1852 patch = args.pop(0)
1854 patch = args.pop(0)
1853 if patch is None:
1855 if patch is None:
1854 raise util.Abort(_('no patch to work with'))
1856 raise util.Abort(_('no patch to work with'))
1855 if args or opts['none']:
1857 if args or opts['none']:
1856 idx = q.find_series(patch)
1858 idx = q.find_series(patch)
1857 if idx is None:
1859 if idx is None:
1858 raise util.Abort(_('no patch named %s') % patch)
1860 raise util.Abort(_('no patch named %s') % patch)
1859 q.set_guards(idx, args)
1861 q.set_guards(idx, args)
1860 q.save_dirty()
1862 q.save_dirty()
1861 else:
1863 else:
1862 status(q.series.index(q.lookup(patch)))
1864 status(q.series.index(q.lookup(patch)))
1863
1865
1864 def header(ui, repo, patch=None):
1866 def header(ui, repo, patch=None):
1865 """Print the header of the topmost or specified patch"""
1867 """Print the header of the topmost or specified patch"""
1866 q = repo.mq
1868 q = repo.mq
1867
1869
1868 if patch:
1870 if patch:
1869 patch = q.lookup(patch)
1871 patch = q.lookup(patch)
1870 else:
1872 else:
1871 if not q.applied:
1873 if not q.applied:
1872 ui.write('No patches applied\n')
1874 ui.write('No patches applied\n')
1873 return 1
1875 return 1
1874 patch = q.lookup('qtip')
1876 patch = q.lookup('qtip')
1875 message = repo.mq.readheaders(patch)[0]
1877 message = repo.mq.readheaders(patch)[0]
1876
1878
1877 ui.write('\n'.join(message) + '\n')
1879 ui.write('\n'.join(message) + '\n')
1878
1880
1879 def lastsavename(path):
1881 def lastsavename(path):
1880 (directory, base) = os.path.split(path)
1882 (directory, base) = os.path.split(path)
1881 names = os.listdir(directory)
1883 names = os.listdir(directory)
1882 namere = re.compile("%s.([0-9]+)" % base)
1884 namere = re.compile("%s.([0-9]+)" % base)
1883 maxindex = None
1885 maxindex = None
1884 maxname = None
1886 maxname = None
1885 for f in names:
1887 for f in names:
1886 m = namere.match(f)
1888 m = namere.match(f)
1887 if m:
1889 if m:
1888 index = int(m.group(1))
1890 index = int(m.group(1))
1889 if maxindex == None or index > maxindex:
1891 if maxindex == None or index > maxindex:
1890 maxindex = index
1892 maxindex = index
1891 maxname = f
1893 maxname = f
1892 if maxname:
1894 if maxname:
1893 return (os.path.join(directory, maxname), maxindex)
1895 return (os.path.join(directory, maxname), maxindex)
1894 return (None, None)
1896 return (None, None)
1895
1897
1896 def savename(path):
1898 def savename(path):
1897 (last, index) = lastsavename(path)
1899 (last, index) = lastsavename(path)
1898 if last is None:
1900 if last is None:
1899 index = 0
1901 index = 0
1900 newpath = path + ".%d" % (index + 1)
1902 newpath = path + ".%d" % (index + 1)
1901 return newpath
1903 return newpath
1902
1904
1903 def push(ui, repo, patch=None, **opts):
1905 def push(ui, repo, patch=None, **opts):
1904 """push the next patch onto the stack
1906 """push the next patch onto the stack
1905
1907
1906 When --force is applied, all local changes in patched files will be lost.
1908 When --force is applied, all local changes in patched files will be lost.
1907 """
1909 """
1908 q = repo.mq
1910 q = repo.mq
1909 mergeq = None
1911 mergeq = None
1910
1912
1911 if opts['all']:
1913 if opts['all']:
1912 if not q.series:
1914 if not q.series:
1913 ui.warn(_('no patches in series\n'))
1915 ui.warn(_('no patches in series\n'))
1914 return 0
1916 return 0
1915 patch = q.series[-1]
1917 patch = q.series[-1]
1916 if opts['merge']:
1918 if opts['merge']:
1917 if opts['name']:
1919 if opts['name']:
1918 newpath = opts['name']
1920 newpath = opts['name']
1919 else:
1921 else:
1920 newpath, i = lastsavename(q.path)
1922 newpath, i = lastsavename(q.path)
1921 if not newpath:
1923 if not newpath:
1922 ui.warn("no saved queues found, please use -n\n")
1924 ui.warn("no saved queues found, please use -n\n")
1923 return 1
1925 return 1
1924 mergeq = queue(ui, repo.join(""), newpath)
1926 mergeq = queue(ui, repo.join(""), newpath)
1925 ui.warn("merging with queue at: %s\n" % mergeq.path)
1927 ui.warn("merging with queue at: %s\n" % mergeq.path)
1926 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1928 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1927 mergeq=mergeq)
1929 mergeq=mergeq)
1928 return ret
1930 return ret
1929
1931
1930 def pop(ui, repo, patch=None, **opts):
1932 def pop(ui, repo, patch=None, **opts):
1931 """pop the current patch off the stack"""
1933 """pop the current patch off the stack"""
1932 localupdate = True
1934 localupdate = True
1933 if opts['name']:
1935 if opts['name']:
1934 q = queue(ui, repo.join(""), repo.join(opts['name']))
1936 q = queue(ui, repo.join(""), repo.join(opts['name']))
1935 ui.warn('using patch queue: %s\n' % q.path)
1937 ui.warn('using patch queue: %s\n' % q.path)
1936 localupdate = False
1938 localupdate = False
1937 else:
1939 else:
1938 q = repo.mq
1940 q = repo.mq
1939 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1941 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1940 all=opts['all'])
1942 all=opts['all'])
1941 q.save_dirty()
1943 q.save_dirty()
1942 return ret
1944 return ret
1943
1945
1944 def rename(ui, repo, patch, name=None, **opts):
1946 def rename(ui, repo, patch, name=None, **opts):
1945 """rename a patch
1947 """rename a patch
1946
1948
1947 With one argument, renames the current patch to PATCH1.
1949 With one argument, renames the current patch to PATCH1.
1948 With two arguments, renames PATCH1 to PATCH2."""
1950 With two arguments, renames PATCH1 to PATCH2."""
1949
1951
1950 q = repo.mq
1952 q = repo.mq
1951
1953
1952 if not name:
1954 if not name:
1953 name = patch
1955 name = patch
1954 patch = None
1956 patch = None
1955
1957
1956 if patch:
1958 if patch:
1957 patch = q.lookup(patch)
1959 patch = q.lookup(patch)
1958 else:
1960 else:
1959 if not q.applied:
1961 if not q.applied:
1960 ui.write(_('No patches applied\n'))
1962 ui.write(_('No patches applied\n'))
1961 return
1963 return
1962 patch = q.lookup('qtip')
1964 patch = q.lookup('qtip')
1963 absdest = q.join(name)
1965 absdest = q.join(name)
1964 if os.path.isdir(absdest):
1966 if os.path.isdir(absdest):
1965 name = normname(os.path.join(name, os.path.basename(patch)))
1967 name = normname(os.path.join(name, os.path.basename(patch)))
1966 absdest = q.join(name)
1968 absdest = q.join(name)
1967 if os.path.exists(absdest):
1969 if os.path.exists(absdest):
1968 raise util.Abort(_('%s already exists') % absdest)
1970 raise util.Abort(_('%s already exists') % absdest)
1969
1971
1970 if name in q.series:
1972 if name in q.series:
1971 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1973 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1972
1974
1973 if ui.verbose:
1975 if ui.verbose:
1974 ui.write('Renaming %s to %s\n' % (patch, name))
1976 ui.write('Renaming %s to %s\n' % (patch, name))
1975 i = q.find_series(patch)
1977 i = q.find_series(patch)
1976 guards = q.guard_re.findall(q.full_series[i])
1978 guards = q.guard_re.findall(q.full_series[i])
1977 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1979 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1978 q.parse_series()
1980 q.parse_series()
1979 q.series_dirty = 1
1981 q.series_dirty = 1
1980
1982
1981 info = q.isapplied(patch)
1983 info = q.isapplied(patch)
1982 if info:
1984 if info:
1983 q.applied[info[0]] = statusentry(info[1], name)
1985 q.applied[info[0]] = statusentry(info[1], name)
1984 q.applied_dirty = 1
1986 q.applied_dirty = 1
1985
1987
1986 util.rename(q.join(patch), absdest)
1988 util.rename(q.join(patch), absdest)
1987 r = q.qrepo()
1989 r = q.qrepo()
1988 if r:
1990 if r:
1989 wlock = r.wlock()
1991 wlock = r.wlock()
1990 try:
1992 try:
1991 if r.dirstate[name] == 'r':
1993 if r.dirstate[name] == 'r':
1992 r.undelete([name])
1994 r.undelete([name])
1993 r.copy(patch, name)
1995 r.copy(patch, name)
1994 r.remove([patch], False)
1996 r.remove([patch], False)
1995 finally:
1997 finally:
1996 del wlock
1998 del wlock
1997
1999
1998 q.save_dirty()
2000 q.save_dirty()
1999
2001
2000 def restore(ui, repo, rev, **opts):
2002 def restore(ui, repo, rev, **opts):
2001 """restore the queue state saved by a rev"""
2003 """restore the queue state saved by a rev"""
2002 rev = repo.lookup(rev)
2004 rev = repo.lookup(rev)
2003 q = repo.mq
2005 q = repo.mq
2004 q.restore(repo, rev, delete=opts['delete'],
2006 q.restore(repo, rev, delete=opts['delete'],
2005 qupdate=opts['update'])
2007 qupdate=opts['update'])
2006 q.save_dirty()
2008 q.save_dirty()
2007 return 0
2009 return 0
2008
2010
2009 def save(ui, repo, **opts):
2011 def save(ui, repo, **opts):
2010 """save current queue state"""
2012 """save current queue state"""
2011 q = repo.mq
2013 q = repo.mq
2012 message = cmdutil.logmessage(opts)
2014 message = cmdutil.logmessage(opts)
2013 ret = q.save(repo, msg=message)
2015 ret = q.save(repo, msg=message)
2014 if ret:
2016 if ret:
2015 return ret
2017 return ret
2016 q.save_dirty()
2018 q.save_dirty()
2017 if opts['copy']:
2019 if opts['copy']:
2018 path = q.path
2020 path = q.path
2019 if opts['name']:
2021 if opts['name']:
2020 newpath = os.path.join(q.basepath, opts['name'])
2022 newpath = os.path.join(q.basepath, opts['name'])
2021 if os.path.exists(newpath):
2023 if os.path.exists(newpath):
2022 if not os.path.isdir(newpath):
2024 if not os.path.isdir(newpath):
2023 raise util.Abort(_('destination %s exists and is not '
2025 raise util.Abort(_('destination %s exists and is not '
2024 'a directory') % newpath)
2026 'a directory') % newpath)
2025 if not opts['force']:
2027 if not opts['force']:
2026 raise util.Abort(_('destination %s exists, '
2028 raise util.Abort(_('destination %s exists, '
2027 'use -f to force') % newpath)
2029 'use -f to force') % newpath)
2028 else:
2030 else:
2029 newpath = savename(path)
2031 newpath = savename(path)
2030 ui.warn("copy %s to %s\n" % (path, newpath))
2032 ui.warn("copy %s to %s\n" % (path, newpath))
2031 util.copyfiles(path, newpath)
2033 util.copyfiles(path, newpath)
2032 if opts['empty']:
2034 if opts['empty']:
2033 try:
2035 try:
2034 os.unlink(q.join(q.status_path))
2036 os.unlink(q.join(q.status_path))
2035 except:
2037 except:
2036 pass
2038 pass
2037 return 0
2039 return 0
2038
2040
2039 def strip(ui, repo, rev, **opts):
2041 def strip(ui, repo, rev, **opts):
2040 """strip a revision and all later revs on the same branch"""
2042 """strip a revision and all later revs on the same branch"""
2041 rev = repo.lookup(rev)
2043 rev = repo.lookup(rev)
2042 backup = 'all'
2044 backup = 'all'
2043 if opts['backup']:
2045 if opts['backup']:
2044 backup = 'strip'
2046 backup = 'strip'
2045 elif opts['nobackup']:
2047 elif opts['nobackup']:
2046 backup = 'none'
2048 backup = 'none'
2047 update = repo.dirstate.parents()[0] != revlog.nullid
2049 update = repo.dirstate.parents()[0] != revlog.nullid
2048 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2050 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2049 return 0
2051 return 0
2050
2052
2051 def select(ui, repo, *args, **opts):
2053 def select(ui, repo, *args, **opts):
2052 '''set or print guarded patches to push
2054 '''set or print guarded patches to push
2053
2055
2054 Use the qguard command to set or print guards on patch, then use
2056 Use the qguard command to set or print guards on patch, then use
2055 qselect to tell mq which guards to use. A patch will be pushed if it
2057 qselect to tell mq which guards to use. A patch will be pushed if it
2056 has no guards or any positive guards match the currently selected guard,
2058 has no guards or any positive guards match the currently selected guard,
2057 but will not be pushed if any negative guards match the current guard.
2059 but will not be pushed if any negative guards match the current guard.
2058 For example:
2060 For example:
2059
2061
2060 qguard foo.patch -stable (negative guard)
2062 qguard foo.patch -stable (negative guard)
2061 qguard bar.patch +stable (positive guard)
2063 qguard bar.patch +stable (positive guard)
2062 qselect stable
2064 qselect stable
2063
2065
2064 This activates the "stable" guard. mq will skip foo.patch (because
2066 This activates the "stable" guard. mq will skip foo.patch (because
2065 it has a negative match) but push bar.patch (because it
2067 it has a negative match) but push bar.patch (because it
2066 has a positive match).
2068 has a positive match).
2067
2069
2068 With no arguments, prints the currently active guards.
2070 With no arguments, prints the currently active guards.
2069 With one argument, sets the active guard.
2071 With one argument, sets the active guard.
2070
2072
2071 Use -n/--none to deactivate guards (no other arguments needed).
2073 Use -n/--none to deactivate guards (no other arguments needed).
2072 When no guards are active, patches with positive guards are skipped
2074 When no guards are active, patches with positive guards are skipped
2073 and patches with negative guards are pushed.
2075 and patches with negative guards are pushed.
2074
2076
2075 qselect can change the guards on applied patches. It does not pop
2077 qselect can change the guards on applied patches. It does not pop
2076 guarded patches by default. Use --pop to pop back to the last applied
2078 guarded patches by default. Use --pop to pop back to the last applied
2077 patch that is not guarded. Use --reapply (which implies --pop) to push
2079 patch that is not guarded. Use --reapply (which implies --pop) to push
2078 back to the current patch afterwards, but skip guarded patches.
2080 back to the current patch afterwards, but skip guarded patches.
2079
2081
2080 Use -s/--series to print a list of all guards in the series file (no
2082 Use -s/--series to print a list of all guards in the series file (no
2081 other arguments needed). Use -v for more information.'''
2083 other arguments needed). Use -v for more information.'''
2082
2084
2083 q = repo.mq
2085 q = repo.mq
2084 guards = q.active()
2086 guards = q.active()
2085 if args or opts['none']:
2087 if args or opts['none']:
2086 old_unapplied = q.unapplied(repo)
2088 old_unapplied = q.unapplied(repo)
2087 old_guarded = [i for i in xrange(len(q.applied)) if
2089 old_guarded = [i for i in xrange(len(q.applied)) if
2088 not q.pushable(i)[0]]
2090 not q.pushable(i)[0]]
2089 q.set_active(args)
2091 q.set_active(args)
2090 q.save_dirty()
2092 q.save_dirty()
2091 if not args:
2093 if not args:
2092 ui.status(_('guards deactivated\n'))
2094 ui.status(_('guards deactivated\n'))
2093 if not opts['pop'] and not opts['reapply']:
2095 if not opts['pop'] and not opts['reapply']:
2094 unapplied = q.unapplied(repo)
2096 unapplied = q.unapplied(repo)
2095 guarded = [i for i in xrange(len(q.applied))
2097 guarded = [i for i in xrange(len(q.applied))
2096 if not q.pushable(i)[0]]
2098 if not q.pushable(i)[0]]
2097 if len(unapplied) != len(old_unapplied):
2099 if len(unapplied) != len(old_unapplied):
2098 ui.status(_('number of unguarded, unapplied patches has '
2100 ui.status(_('number of unguarded, unapplied patches has '
2099 'changed from %d to %d\n') %
2101 'changed from %d to %d\n') %
2100 (len(old_unapplied), len(unapplied)))
2102 (len(old_unapplied), len(unapplied)))
2101 if len(guarded) != len(old_guarded):
2103 if len(guarded) != len(old_guarded):
2102 ui.status(_('number of guarded, applied patches has changed '
2104 ui.status(_('number of guarded, applied patches has changed '
2103 'from %d to %d\n') %
2105 'from %d to %d\n') %
2104 (len(old_guarded), len(guarded)))
2106 (len(old_guarded), len(guarded)))
2105 elif opts['series']:
2107 elif opts['series']:
2106 guards = {}
2108 guards = {}
2107 noguards = 0
2109 noguards = 0
2108 for gs in q.series_guards:
2110 for gs in q.series_guards:
2109 if not gs:
2111 if not gs:
2110 noguards += 1
2112 noguards += 1
2111 for g in gs:
2113 for g in gs:
2112 guards.setdefault(g, 0)
2114 guards.setdefault(g, 0)
2113 guards[g] += 1
2115 guards[g] += 1
2114 if ui.verbose:
2116 if ui.verbose:
2115 guards['NONE'] = noguards
2117 guards['NONE'] = noguards
2116 guards = guards.items()
2118 guards = guards.items()
2117 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2119 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2118 if guards:
2120 if guards:
2119 ui.note(_('guards in series file:\n'))
2121 ui.note(_('guards in series file:\n'))
2120 for guard, count in guards:
2122 for guard, count in guards:
2121 ui.note('%2d ' % count)
2123 ui.note('%2d ' % count)
2122 ui.write(guard, '\n')
2124 ui.write(guard, '\n')
2123 else:
2125 else:
2124 ui.note(_('no guards in series file\n'))
2126 ui.note(_('no guards in series file\n'))
2125 else:
2127 else:
2126 if guards:
2128 if guards:
2127 ui.note(_('active guards:\n'))
2129 ui.note(_('active guards:\n'))
2128 for g in guards:
2130 for g in guards:
2129 ui.write(g, '\n')
2131 ui.write(g, '\n')
2130 else:
2132 else:
2131 ui.write(_('no active guards\n'))
2133 ui.write(_('no active guards\n'))
2132 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2134 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2133 popped = False
2135 popped = False
2134 if opts['pop'] or opts['reapply']:
2136 if opts['pop'] or opts['reapply']:
2135 for i in xrange(len(q.applied)):
2137 for i in xrange(len(q.applied)):
2136 pushable, reason = q.pushable(i)
2138 pushable, reason = q.pushable(i)
2137 if not pushable:
2139 if not pushable:
2138 ui.status(_('popping guarded patches\n'))
2140 ui.status(_('popping guarded patches\n'))
2139 popped = True
2141 popped = True
2140 if i == 0:
2142 if i == 0:
2141 q.pop(repo, all=True)
2143 q.pop(repo, all=True)
2142 else:
2144 else:
2143 q.pop(repo, i-1)
2145 q.pop(repo, i-1)
2144 break
2146 break
2145 if popped:
2147 if popped:
2146 try:
2148 try:
2147 if reapply:
2149 if reapply:
2148 ui.status(_('reapplying unguarded patches\n'))
2150 ui.status(_('reapplying unguarded patches\n'))
2149 q.push(repo, reapply)
2151 q.push(repo, reapply)
2150 finally:
2152 finally:
2151 q.save_dirty()
2153 q.save_dirty()
2152
2154
2153 def reposetup(ui, repo):
2155 def reposetup(ui, repo):
2154 class mqrepo(repo.__class__):
2156 class mqrepo(repo.__class__):
2155 def abort_if_wdir_patched(self, errmsg, force=False):
2157 def abort_if_wdir_patched(self, errmsg, force=False):
2156 if self.mq.applied and not force:
2158 if self.mq.applied and not force:
2157 parent = revlog.hex(self.dirstate.parents()[0])
2159 parent = revlog.hex(self.dirstate.parents()[0])
2158 if parent in [s.rev for s in self.mq.applied]:
2160 if parent in [s.rev for s in self.mq.applied]:
2159 raise util.Abort(errmsg)
2161 raise util.Abort(errmsg)
2160
2162
2161 def commit(self, *args, **opts):
2163 def commit(self, *args, **opts):
2162 if len(args) >= 6:
2164 if len(args) >= 6:
2163 force = args[5]
2165 force = args[5]
2164 else:
2166 else:
2165 force = opts.get('force')
2167 force = opts.get('force')
2166 self.abort_if_wdir_patched(
2168 self.abort_if_wdir_patched(
2167 _('cannot commit over an applied mq patch'),
2169 _('cannot commit over an applied mq patch'),
2168 force)
2170 force)
2169
2171
2170 return super(mqrepo, self).commit(*args, **opts)
2172 return super(mqrepo, self).commit(*args, **opts)
2171
2173
2172 def push(self, remote, force=False, revs=None):
2174 def push(self, remote, force=False, revs=None):
2173 if self.mq.applied and not force and not revs:
2175 if self.mq.applied and not force and not revs:
2174 raise util.Abort(_('source has mq patches applied'))
2176 raise util.Abort(_('source has mq patches applied'))
2175 return super(mqrepo, self).push(remote, force, revs)
2177 return super(mqrepo, self).push(remote, force, revs)
2176
2178
2177 def tags(self):
2179 def tags(self):
2178 if self.tagscache:
2180 if self.tagscache:
2179 return self.tagscache
2181 return self.tagscache
2180
2182
2181 tagscache = super(mqrepo, self).tags()
2183 tagscache = super(mqrepo, self).tags()
2182
2184
2183 q = self.mq
2185 q = self.mq
2184 if not q.applied:
2186 if not q.applied:
2185 return tagscache
2187 return tagscache
2186
2188
2187 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2189 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2188
2190
2189 if mqtags[-1][0] not in self.changelog.nodemap:
2191 if mqtags[-1][0] not in self.changelog.nodemap:
2190 self.ui.warn('mq status file refers to unknown node %s\n'
2192 self.ui.warn('mq status file refers to unknown node %s\n'
2191 % revlog.short(mqtags[-1][0]))
2193 % revlog.short(mqtags[-1][0]))
2192 return tagscache
2194 return tagscache
2193
2195
2194 mqtags.append((mqtags[-1][0], 'qtip'))
2196 mqtags.append((mqtags[-1][0], 'qtip'))
2195 mqtags.append((mqtags[0][0], 'qbase'))
2197 mqtags.append((mqtags[0][0], 'qbase'))
2196 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2198 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2197 for patch in mqtags:
2199 for patch in mqtags:
2198 if patch[1] in tagscache:
2200 if patch[1] in tagscache:
2199 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2201 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2200 else:
2202 else:
2201 tagscache[patch[1]] = patch[0]
2203 tagscache[patch[1]] = patch[0]
2202
2204
2203 return tagscache
2205 return tagscache
2204
2206
2205 def _branchtags(self, partial, lrev):
2207 def _branchtags(self, partial, lrev):
2206 q = self.mq
2208 q = self.mq
2207 if not q.applied:
2209 if not q.applied:
2208 return super(mqrepo, self)._branchtags(partial, lrev)
2210 return super(mqrepo, self)._branchtags(partial, lrev)
2209
2211
2210 cl = self.changelog
2212 cl = self.changelog
2211 qbasenode = revlog.bin(q.applied[0].rev)
2213 qbasenode = revlog.bin(q.applied[0].rev)
2212 if qbasenode not in cl.nodemap:
2214 if qbasenode not in cl.nodemap:
2213 self.ui.warn('mq status file refers to unknown node %s\n'
2215 self.ui.warn('mq status file refers to unknown node %s\n'
2214 % revlog.short(qbasenode))
2216 % revlog.short(qbasenode))
2215 return super(mqrepo, self)._branchtags(partial, lrev)
2217 return super(mqrepo, self)._branchtags(partial, lrev)
2216
2218
2217 qbase = cl.rev(qbasenode)
2219 qbase = cl.rev(qbasenode)
2218 start = lrev + 1
2220 start = lrev + 1
2219 if start < qbase:
2221 if start < qbase:
2220 # update the cache (excluding the patches) and save it
2222 # update the cache (excluding the patches) and save it
2221 self._updatebranchcache(partial, lrev+1, qbase)
2223 self._updatebranchcache(partial, lrev+1, qbase)
2222 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2224 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2223 start = qbase
2225 start = qbase
2224 # if start = qbase, the cache is as updated as it should be.
2226 # if start = qbase, the cache is as updated as it should be.
2225 # if start > qbase, the cache includes (part of) the patches.
2227 # if start > qbase, the cache includes (part of) the patches.
2226 # we might as well use it, but we won't save it.
2228 # we might as well use it, but we won't save it.
2227
2229
2228 # update the cache up to the tip
2230 # update the cache up to the tip
2229 self._updatebranchcache(partial, start, cl.count())
2231 self._updatebranchcache(partial, start, cl.count())
2230
2232
2231 return partial
2233 return partial
2232
2234
2233 if repo.local():
2235 if repo.local():
2234 repo.__class__ = mqrepo
2236 repo.__class__ = mqrepo
2235 repo.mq = queue(ui, repo.join(""))
2237 repo.mq = queue(ui, repo.join(""))
2236
2238
2237 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2239 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2238
2240
2239 headeropts = [
2241 headeropts = [
2240 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2242 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2241 ('u', 'user', '', _('add "From: <given user>" to patch')),
2243 ('u', 'user', '', _('add "From: <given user>" to patch')),
2242 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2244 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2243 ('d', 'date', '', _('add "Date: <given date>" to patch'))]
2245 ('d', 'date', '', _('add "Date: <given date>" to patch'))]
2244
2246
2245 cmdtable = {
2247 cmdtable = {
2246 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2248 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2247 "qclone":
2249 "qclone":
2248 (clone,
2250 (clone,
2249 [('', 'pull', None, _('use pull protocol to copy metadata')),
2251 [('', 'pull', None, _('use pull protocol to copy metadata')),
2250 ('U', 'noupdate', None, _('do not update the new working directories')),
2252 ('U', 'noupdate', None, _('do not update the new working directories')),
2251 ('', 'uncompressed', None,
2253 ('', 'uncompressed', None,
2252 _('use uncompressed transfer (fast over LAN)')),
2254 _('use uncompressed transfer (fast over LAN)')),
2253 ('p', 'patches', '', _('location of source patch repo')),
2255 ('p', 'patches', '', _('location of source patch repo')),
2254 ] + commands.remoteopts,
2256 ] + commands.remoteopts,
2255 _('hg qclone [OPTION]... SOURCE [DEST]')),
2257 _('hg qclone [OPTION]... SOURCE [DEST]')),
2256 "qcommit|qci":
2258 "qcommit|qci":
2257 (commit,
2259 (commit,
2258 commands.table["^commit|ci"][1],
2260 commands.table["^commit|ci"][1],
2259 _('hg qcommit [OPTION]... [FILE]...')),
2261 _('hg qcommit [OPTION]... [FILE]...')),
2260 "^qdiff":
2262 "^qdiff":
2261 (diff,
2263 (diff,
2262 [('g', 'git', None, _('use git extended diff format')),
2264 [('g', 'git', None, _('use git extended diff format')),
2263 ('U', 'unified', 3, _('number of lines of context to show')),
2265 ('U', 'unified', 3, _('number of lines of context to show')),
2264 ] + commands.walkopts,
2266 ] + commands.walkopts,
2265 _('hg qdiff [-I] [-X] [-U NUM] [-g] [FILE]...')),
2267 _('hg qdiff [-I] [-X] [-U NUM] [-g] [FILE]...')),
2266 "qdelete|qremove|qrm":
2268 "qdelete|qremove|qrm":
2267 (delete,
2269 (delete,
2268 [('k', 'keep', None, _('keep patch file')),
2270 [('k', 'keep', None, _('keep patch file')),
2269 ('r', 'rev', [], _('stop managing a revision'))],
2271 ('r', 'rev', [], _('stop managing a revision'))],
2270 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2272 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2271 'qfold':
2273 'qfold':
2272 (fold,
2274 (fold,
2273 [('e', 'edit', None, _('edit patch header')),
2275 [('e', 'edit', None, _('edit patch header')),
2274 ('k', 'keep', None, _('keep folded patch files')),
2276 ('k', 'keep', None, _('keep folded patch files')),
2275 ] + commands.commitopts,
2277 ] + commands.commitopts,
2276 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2278 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2277 'qgoto':
2279 'qgoto':
2278 (goto,
2280 (goto,
2279 [('f', 'force', None, _('overwrite any local changes'))],
2281 [('f', 'force', None, _('overwrite any local changes'))],
2280 _('hg qgoto [OPTION]... PATCH')),
2282 _('hg qgoto [OPTION]... PATCH')),
2281 'qguard':
2283 'qguard':
2282 (guard,
2284 (guard,
2283 [('l', 'list', None, _('list all patches and guards')),
2285 [('l', 'list', None, _('list all patches and guards')),
2284 ('n', 'none', None, _('drop all guards'))],
2286 ('n', 'none', None, _('drop all guards'))],
2285 _('hg qguard [-l] [-n] [PATCH] [+GUARD]... [-GUARD]...')),
2287 _('hg qguard [-l] [-n] [PATCH] [+GUARD]... [-GUARD]...')),
2286 'qheader': (header, [], _('hg qheader [PATCH]')),
2288 'qheader': (header, [], _('hg qheader [PATCH]')),
2287 "^qimport":
2289 "^qimport":
2288 (qimport,
2290 (qimport,
2289 [('e', 'existing', None, 'import file in patch dir'),
2291 [('e', 'existing', None, 'import file in patch dir'),
2290 ('n', 'name', '', 'patch file name'),
2292 ('n', 'name', '', 'patch file name'),
2291 ('f', 'force', None, 'overwrite existing files'),
2293 ('f', 'force', None, 'overwrite existing files'),
2292 ('r', 'rev', [], 'place existing revisions under mq control'),
2294 ('r', 'rev', [], 'place existing revisions under mq control'),
2293 ('g', 'git', None, _('use git extended diff format'))],
2295 ('g', 'git', None, _('use git extended diff format'))],
2294 _('hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...')),
2296 _('hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...')),
2295 "^qinit":
2297 "^qinit":
2296 (init,
2298 (init,
2297 [('c', 'create-repo', None, 'create queue repository')],
2299 [('c', 'create-repo', None, 'create queue repository')],
2298 _('hg qinit [-c]')),
2300 _('hg qinit [-c]')),
2299 "qnew":
2301 "qnew":
2300 (new,
2302 (new,
2301 [('e', 'edit', None, _('edit commit message')),
2303 [('e', 'edit', None, _('edit commit message')),
2302 ('f', 'force', None, _('import uncommitted changes into patch')),
2304 ('f', 'force', None, _('import uncommitted changes into patch')),
2303 ('g', 'git', None, _('use git extended diff format')),
2305 ('g', 'git', None, _('use git extended diff format')),
2304 ] + commands.walkopts + commands.commitopts + headeropts,
2306 ] + commands.walkopts + commands.commitopts + headeropts,
2305 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2307 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2306 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2308 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2307 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2309 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2308 "^qpop":
2310 "^qpop":
2309 (pop,
2311 (pop,
2310 [('a', 'all', None, _('pop all patches')),
2312 [('a', 'all', None, _('pop all patches')),
2311 ('n', 'name', '', _('queue name to pop')),
2313 ('n', 'name', '', _('queue name to pop')),
2312 ('f', 'force', None, _('forget any local changes'))],
2314 ('f', 'force', None, _('forget any local changes'))],
2313 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2315 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2314 "^qpush":
2316 "^qpush":
2315 (push,
2317 (push,
2316 [('f', 'force', None, _('apply if the patch has rejects')),
2318 [('f', 'force', None, _('apply if the patch has rejects')),
2317 ('l', 'list', None, _('list patch name in commit text')),
2319 ('l', 'list', None, _('list patch name in commit text')),
2318 ('a', 'all', None, _('apply all patches')),
2320 ('a', 'all', None, _('apply all patches')),
2319 ('m', 'merge', None, _('merge from another queue')),
2321 ('m', 'merge', None, _('merge from another queue')),
2320 ('n', 'name', '', _('merge queue name'))],
2322 ('n', 'name', '', _('merge queue name'))],
2321 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2323 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2322 "^qrefresh":
2324 "^qrefresh":
2323 (refresh,
2325 (refresh,
2324 [('e', 'edit', None, _('edit commit message')),
2326 [('e', 'edit', None, _('edit commit message')),
2325 ('g', 'git', None, _('use git extended diff format')),
2327 ('g', 'git', None, _('use git extended diff format')),
2326 ('s', 'short', None, _('refresh only files already in the patch')),
2328 ('s', 'short', None, _('refresh only files already in the patch')),
2327 ] + commands.walkopts + commands.commitopts + headeropts,
2329 ] + commands.walkopts + commands.commitopts + headeropts,
2328 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2330 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2329 'qrename|qmv':
2331 'qrename|qmv':
2330 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2332 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2331 "qrestore":
2333 "qrestore":
2332 (restore,
2334 (restore,
2333 [('d', 'delete', None, _('delete save entry')),
2335 [('d', 'delete', None, _('delete save entry')),
2334 ('u', 'update', None, _('update queue working dir'))],
2336 ('u', 'update', None, _('update queue working dir'))],
2335 _('hg qrestore [-d] [-u] REV')),
2337 _('hg qrestore [-d] [-u] REV')),
2336 "qsave":
2338 "qsave":
2337 (save,
2339 (save,
2338 [('c', 'copy', None, _('copy patch directory')),
2340 [('c', 'copy', None, _('copy patch directory')),
2339 ('n', 'name', '', _('copy directory name')),
2341 ('n', 'name', '', _('copy directory name')),
2340 ('e', 'empty', None, _('clear queue status file')),
2342 ('e', 'empty', None, _('clear queue status file')),
2341 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2343 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2342 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2344 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2343 "qselect":
2345 "qselect":
2344 (select,
2346 (select,
2345 [('n', 'none', None, _('disable all guards')),
2347 [('n', 'none', None, _('disable all guards')),
2346 ('s', 'series', None, _('list all guards in series file')),
2348 ('s', 'series', None, _('list all guards in series file')),
2347 ('', 'pop', None, _('pop to before first guarded applied patch')),
2349 ('', 'pop', None, _('pop to before first guarded applied patch')),
2348 ('', 'reapply', None, _('pop, then reapply patches'))],
2350 ('', 'reapply', None, _('pop, then reapply patches'))],
2349 _('hg qselect [OPTION]... [GUARD]...')),
2351 _('hg qselect [OPTION]... [GUARD]...')),
2350 "qseries":
2352 "qseries":
2351 (series,
2353 (series,
2352 [('m', 'missing', None, _('print patches not in series')),
2354 [('m', 'missing', None, _('print patches not in series')),
2353 ] + seriesopts,
2355 ] + seriesopts,
2354 _('hg qseries [-ms]')),
2356 _('hg qseries [-ms]')),
2355 "^strip":
2357 "^strip":
2356 (strip,
2358 (strip,
2357 [('f', 'force', None, _('force removal with local changes')),
2359 [('f', 'force', None, _('force removal with local changes')),
2358 ('b', 'backup', None, _('bundle unrelated changesets')),
2360 ('b', 'backup', None, _('bundle unrelated changesets')),
2359 ('n', 'nobackup', None, _('no backups'))],
2361 ('n', 'nobackup', None, _('no backups'))],
2360 _('hg strip [-f] [-b] [-n] REV')),
2362 _('hg strip [-f] [-b] [-n] REV')),
2361 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2363 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2362 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2364 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2363 }
2365 }
@@ -1,138 +1,137 b''
1 # Copyright (C) 2006 - Marco Barisione <marco@barisione.org>
1 # Copyright (C) 2006 - Marco Barisione <marco@barisione.org>
2 #
2 #
3 # This is a small extension for Mercurial (http://www.selenic.com/mercurial)
3 # This is a small extension for Mercurial (http://www.selenic.com/mercurial)
4 # that removes files not known to mercurial
4 # that removes files not known to mercurial
5 #
5 #
6 # This program was inspired by the "cvspurge" script contained in CVS utilities
6 # This program was inspired by the "cvspurge" script contained in CVS utilities
7 # (http://www.red-bean.com/cvsutils/).
7 # (http://www.red-bean.com/cvsutils/).
8 #
8 #
9 # To enable the "purge" extension put these lines in your ~/.hgrc:
9 # To enable the "purge" extension put these lines in your ~/.hgrc:
10 # [extensions]
10 # [extensions]
11 # hgext.purge =
11 # hgext.purge =
12 #
12 #
13 # For help on the usage of "hg purge" use:
13 # For help on the usage of "hg purge" use:
14 # hg help purge
14 # hg help purge
15 #
15 #
16 # This program is free software; you can redistribute it and/or modify
16 # This program is free software; you can redistribute it and/or modify
17 # it under the terms of the GNU General Public License as published by
17 # it under the terms of the GNU General Public License as published by
18 # the Free Software Foundation; either version 2 of the License, or
18 # the Free Software Foundation; either version 2 of the License, or
19 # (at your option) any later version.
19 # (at your option) any later version.
20 #
20 #
21 # This program is distributed in the hope that it will be useful,
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 # GNU General Public License for more details.
24 # GNU General Public License for more details.
25 #
25 #
26 # You should have received a copy of the GNU General Public License
26 # You should have received a copy of the GNU General Public License
27 # along with this program; if not, write to the Free Software
27 # along with this program; if not, write to the Free Software
28 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29
29
30 from mercurial import util, commands, cmdutil
30 from mercurial import util, commands, cmdutil
31 from mercurial.i18n import _
31 from mercurial.i18n import _
32 import os
32 import os
33
33
34 def purge(ui, repo, *dirs, **opts):
34 def purge(ui, repo, *dirs, **opts):
35 '''removes files not tracked by mercurial
35 '''removes files not tracked by mercurial
36
36
37 Delete files not known to mercurial, this is useful to test local and
37 Delete files not known to mercurial, this is useful to test local and
38 uncommitted changes in the otherwise clean source tree.
38 uncommitted changes in the otherwise clean source tree.
39
39
40 This means that purge will delete:
40 This means that purge will delete:
41 - Unknown files: files marked with "?" by "hg status"
41 - Unknown files: files marked with "?" by "hg status"
42 - Ignored files: files usually ignored by Mercurial because they match
42 - Ignored files: files usually ignored by Mercurial because they match
43 a pattern in a ".hgignore" file
43 a pattern in a ".hgignore" file
44 - Empty directories: in fact Mercurial ignores directories unless they
44 - Empty directories: in fact Mercurial ignores directories unless they
45 contain files under source control managment
45 contain files under source control managment
46 But it will leave untouched:
46 But it will leave untouched:
47 - Unmodified tracked files
47 - Unmodified tracked files
48 - Modified tracked files
48 - Modified tracked files
49 - New files added to the repository (with "hg add")
49 - New files added to the repository (with "hg add")
50
50
51 If directories are given on the command line, only files in these
51 If directories are given on the command line, only files in these
52 directories are considered.
52 directories are considered.
53
53
54 Be careful with purge, you could irreversibly delete some files you
54 Be careful with purge, you could irreversibly delete some files you
55 forgot to add to the repository. If you only want to print the list of
55 forgot to add to the repository. If you only want to print the list of
56 files that this program would delete use the --print option.
56 files that this program would delete use the --print option.
57 '''
57 '''
58 act = not opts['print']
58 act = not opts['print']
59 ignored = bool(opts['all'])
59 ignored = bool(opts['all'])
60 abort_on_err = bool(opts['abort_on_err'])
60 abort_on_err = bool(opts['abort_on_err'])
61 eol = opts['print0'] and '\0' or '\n'
61 eol = opts['print0'] and '\0' or '\n'
62 if eol == '\0':
62 if eol == '\0':
63 # --print0 implies --print
63 # --print0 implies --print
64 act = False
64 act = False
65 force = bool(opts['force'])
65 force = bool(opts['force'])
66
66
67 def error(msg):
67 def error(msg):
68 if abort_on_err:
68 if abort_on_err:
69 raise util.Abort(msg)
69 raise util.Abort(msg)
70 else:
70 else:
71 ui.warn(_('warning: %s\n') % msg)
71 ui.warn(_('warning: %s\n') % msg)
72
72
73 def remove(remove_func, name):
73 def remove(remove_func, name):
74 if act:
74 if act:
75 try:
75 try:
76 remove_func(os.path.join(repo.root, name))
76 remove_func(os.path.join(repo.root, name))
77 except OSError, e:
77 except OSError, e:
78 error(_('%s cannot be removed') % name)
78 error(_('%s cannot be removed') % name)
79 else:
79 else:
80 ui.write('%s%s' % (name, eol))
80 ui.write('%s%s' % (name, eol))
81
81
82 if not force:
82 if not force:
83 _check_fs(ui, repo)
83 _check_fs(ui, repo)
84
84
85 directories = []
85 directories = []
86 files = []
86 files = []
87 match = cmdutil.match(repo, dirs, opts)
87 match = cmdutil.match(repo, dirs, opts)
88 match.dir = directories.append
88 match.dir = directories.append
89 for src, f, st in repo.dirstate.statwalk(match.files(), match,
89 for src, f, st in repo.dirstate.statwalk(match, ignored=ignored):
90 ignored=ignored):
91 if src == 'f' and f not in repo.dirstate:
90 if src == 'f' and f not in repo.dirstate:
92 files.append(f)
91 files.append(f)
93
92
94 directories.sort()
93 directories.sort()
95
94
96 for f in files:
95 for f in files:
97 if f not in repo.dirstate:
96 if f not in repo.dirstate:
98 ui.note(_('Removing file %s\n') % f)
97 ui.note(_('Removing file %s\n') % f)
99 remove(os.remove, f)
98 remove(os.remove, f)
100
99
101 for f in directories[::-1]:
100 for f in directories[::-1]:
102 if match(f) and not os.listdir(repo.wjoin(f)):
101 if match(f) and not os.listdir(repo.wjoin(f)):
103 ui.note(_('Removing directory %s\n') % f)
102 ui.note(_('Removing directory %s\n') % f)
104 remove(os.rmdir, f)
103 remove(os.rmdir, f)
105
104
106 def _check_fs(ui, repo):
105 def _check_fs(ui, repo):
107 """Abort if there is the chance of having problems with name-mangling fs
106 """Abort if there is the chance of having problems with name-mangling fs
108
107
109 In a name mangling filesystem (e.g. a case insensitive one)
108 In a name mangling filesystem (e.g. a case insensitive one)
110 dirstate.walk() can yield filenames different from the ones
109 dirstate.walk() can yield filenames different from the ones
111 stored in the dirstate. This already confuses the status and
110 stored in the dirstate. This already confuses the status and
112 add commands, but with purge this may cause data loss.
111 add commands, but with purge this may cause data loss.
113
112
114 To prevent this, this function will abort if there are uncommitted
113 To prevent this, this function will abort if there are uncommitted
115 changes.
114 changes.
116 """
115 """
117
116
118 # We can't use (files, match) to do a partial walk here - we wouldn't
117 # We can't use (files, match) to do a partial walk here - we wouldn't
119 # notice a modified README file if the user ran "hg purge readme"
118 # notice a modified README file if the user ran "hg purge readme"
120 modified, added, removed, deleted = repo.status()[:4]
119 modified, added, removed, deleted = repo.status()[:4]
121 if modified or added or removed or deleted:
120 if modified or added or removed or deleted:
122 if not util.checkfolding(repo.path) and not ui.quiet:
121 if not util.checkfolding(repo.path) and not ui.quiet:
123 ui.warn(_("Purging on name mangling filesystems is not "
122 ui.warn(_("Purging on name mangling filesystems is not "
124 "fully supported.\n"))
123 "fully supported.\n"))
125 raise util.Abort(_("outstanding uncommitted changes"))
124 raise util.Abort(_("outstanding uncommitted changes"))
126
125
127 cmdtable = {
126 cmdtable = {
128 'purge|clean':
127 'purge|clean':
129 (purge,
128 (purge,
130 [('a', 'abort-on-err', None, _('abort if an error occurs')),
129 [('a', 'abort-on-err', None, _('abort if an error occurs')),
131 ('', 'all', None, _('purge ignored files too')),
130 ('', 'all', None, _('purge ignored files too')),
132 ('f', 'force', None, _('purge even when there are uncommitted changes')),
131 ('f', 'force', None, _('purge even when there are uncommitted changes')),
133 ('p', 'print', None, _('print the file names instead of deleting them')),
132 ('p', 'print', None, _('print the file names instead of deleting them')),
134 ('0', 'print0', None, _('end filenames with NUL, for use with xargs'
133 ('0', 'print0', None, _('end filenames with NUL, for use with xargs'
135 ' (implies -p)')),
134 ' (implies -p)')),
136 ] + commands.walkopts,
135 ] + commands.walkopts,
137 _('hg purge [OPTION]... [DIR]...'))
136 _('hg purge [OPTION]... [DIR]...'))
138 }
137 }
@@ -1,528 +1,528 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
5 # This software may be used and distributed according to the terms of
6 # the GNU General Public License, incorporated herein by reference.
6 # the GNU General Public License, incorporated herein by reference.
7
7
8 '''interactive change selection during commit or qrefresh'''
8 '''interactive change selection during commit or qrefresh'''
9
9
10 from mercurial.i18n import _
10 from mercurial.i18n import _
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, operator, os, re, tempfile
13 import copy, cStringIO, errno, operator, os, re, 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/'):
45 if line.startswith('diff --git a/'):
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 diff_re = re.compile('diff --git a/(.*) b/(.*)$')
73 diff_re = re.compile('diff --git a/(.*) b/(.*)$')
74 allhunks_re = re.compile('(?:index|new file|deleted file) ')
74 allhunks_re = re.compile('(?:index|new file|deleted file) ')
75 pretty_re = re.compile('(?:new file|deleted file) ')
75 pretty_re = re.compile('(?:new file|deleted file) ')
76 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
76 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
77
77
78 def __init__(self, header):
78 def __init__(self, header):
79 self.header = header
79 self.header = header
80 self.hunks = []
80 self.hunks = []
81
81
82 def binary(self):
82 def binary(self):
83 for h in self.header:
83 for h in self.header:
84 if h.startswith('index '):
84 if h.startswith('index '):
85 return True
85 return True
86
86
87 def pretty(self, fp):
87 def pretty(self, fp):
88 for h in self.header:
88 for h in self.header:
89 if h.startswith('index '):
89 if h.startswith('index '):
90 fp.write(_('this modifies a binary file (all or nothing)\n'))
90 fp.write(_('this modifies a binary file (all or nothing)\n'))
91 break
91 break
92 if self.pretty_re.match(h):
92 if self.pretty_re.match(h):
93 fp.write(h)
93 fp.write(h)
94 if self.binary():
94 if self.binary():
95 fp.write(_('this is a binary file\n'))
95 fp.write(_('this is a binary file\n'))
96 break
96 break
97 if h.startswith('---'):
97 if h.startswith('---'):
98 fp.write(_('%d hunks, %d lines changed\n') %
98 fp.write(_('%d hunks, %d lines changed\n') %
99 (len(self.hunks),
99 (len(self.hunks),
100 sum([h.added + h.removed for h in self.hunks])))
100 sum([h.added + h.removed for h in self.hunks])))
101 break
101 break
102 fp.write(h)
102 fp.write(h)
103
103
104 def write(self, fp):
104 def write(self, fp):
105 fp.write(''.join(self.header))
105 fp.write(''.join(self.header))
106
106
107 def allhunks(self):
107 def allhunks(self):
108 for h in self.header:
108 for h in self.header:
109 if self.allhunks_re.match(h):
109 if self.allhunks_re.match(h):
110 return True
110 return True
111
111
112 def files(self):
112 def files(self):
113 fromfile, tofile = self.diff_re.match(self.header[0]).groups()
113 fromfile, tofile = self.diff_re.match(self.header[0]).groups()
114 if fromfile == tofile:
114 if fromfile == tofile:
115 return [fromfile]
115 return [fromfile]
116 return [fromfile, tofile]
116 return [fromfile, tofile]
117
117
118 def filename(self):
118 def filename(self):
119 return self.files()[-1]
119 return self.files()[-1]
120
120
121 def __repr__(self):
121 def __repr__(self):
122 return '<header %s>' % (' '.join(map(repr, self.files())))
122 return '<header %s>' % (' '.join(map(repr, self.files())))
123
123
124 def special(self):
124 def special(self):
125 for h in self.header:
125 for h in self.header:
126 if self.special_re.match(h):
126 if self.special_re.match(h):
127 return True
127 return True
128
128
129 def countchanges(hunk):
129 def countchanges(hunk):
130 """hunk -> (n+,n-)"""
130 """hunk -> (n+,n-)"""
131 add = len([h for h in hunk if h[0] == '+'])
131 add = len([h for h in hunk if h[0] == '+'])
132 rem = len([h for h in hunk if h[0] == '-'])
132 rem = len([h for h in hunk if h[0] == '-'])
133 return add, rem
133 return add, rem
134
134
135 class hunk(object):
135 class hunk(object):
136 """patch hunk
136 """patch hunk
137
137
138 XXX shouldn't we merge this with patch.hunk ?
138 XXX shouldn't we merge this with patch.hunk ?
139 """
139 """
140 maxcontext = 3
140 maxcontext = 3
141
141
142 def __init__(self, header, fromline, toline, proc, before, hunk, after):
142 def __init__(self, header, fromline, toline, proc, before, hunk, after):
143 def trimcontext(number, lines):
143 def trimcontext(number, lines):
144 delta = len(lines) - self.maxcontext
144 delta = len(lines) - self.maxcontext
145 if False and delta > 0:
145 if False and delta > 0:
146 return number + delta, lines[:self.maxcontext]
146 return number + delta, lines[:self.maxcontext]
147 return number, lines
147 return number, lines
148
148
149 self.header = header
149 self.header = header
150 self.fromline, self.before = trimcontext(fromline, before)
150 self.fromline, self.before = trimcontext(fromline, before)
151 self.toline, self.after = trimcontext(toline, after)
151 self.toline, self.after = trimcontext(toline, after)
152 self.proc = proc
152 self.proc = proc
153 self.hunk = hunk
153 self.hunk = hunk
154 self.added, self.removed = countchanges(self.hunk)
154 self.added, self.removed = countchanges(self.hunk)
155
155
156 def write(self, fp):
156 def write(self, fp):
157 delta = len(self.before) + len(self.after)
157 delta = len(self.before) + len(self.after)
158 fromlen = delta + self.removed
158 fromlen = delta + self.removed
159 tolen = delta + self.added
159 tolen = delta + self.added
160 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
160 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
161 (self.fromline, fromlen, self.toline, tolen,
161 (self.fromline, fromlen, self.toline, tolen,
162 self.proc and (' ' + self.proc)))
162 self.proc and (' ' + self.proc)))
163 fp.write(''.join(self.before + self.hunk + self.after))
163 fp.write(''.join(self.before + self.hunk + self.after))
164
164
165 pretty = write
165 pretty = write
166
166
167 def filename(self):
167 def filename(self):
168 return self.header.filename()
168 return self.header.filename()
169
169
170 def __repr__(self):
170 def __repr__(self):
171 return '<hunk %r@%d>' % (self.filename(), self.fromline)
171 return '<hunk %r@%d>' % (self.filename(), self.fromline)
172
172
173 def parsepatch(fp):
173 def parsepatch(fp):
174 """patch -> [] of hunks """
174 """patch -> [] of hunks """
175 class parser(object):
175 class parser(object):
176 """patch parsing state machine"""
176 """patch parsing state machine"""
177 def __init__(self):
177 def __init__(self):
178 self.fromline = 0
178 self.fromline = 0
179 self.toline = 0
179 self.toline = 0
180 self.proc = ''
180 self.proc = ''
181 self.header = None
181 self.header = None
182 self.context = []
182 self.context = []
183 self.before = []
183 self.before = []
184 self.hunk = []
184 self.hunk = []
185 self.stream = []
185 self.stream = []
186
186
187 def addrange(self, (fromstart, fromend, tostart, toend, proc)):
187 def addrange(self, (fromstart, fromend, tostart, toend, proc)):
188 self.fromline = int(fromstart)
188 self.fromline = int(fromstart)
189 self.toline = int(tostart)
189 self.toline = int(tostart)
190 self.proc = proc
190 self.proc = proc
191
191
192 def addcontext(self, context):
192 def addcontext(self, context):
193 if self.hunk:
193 if self.hunk:
194 h = hunk(self.header, self.fromline, self.toline, self.proc,
194 h = hunk(self.header, self.fromline, self.toline, self.proc,
195 self.before, self.hunk, context)
195 self.before, self.hunk, context)
196 self.header.hunks.append(h)
196 self.header.hunks.append(h)
197 self.stream.append(h)
197 self.stream.append(h)
198 self.fromline += len(self.before) + h.removed
198 self.fromline += len(self.before) + h.removed
199 self.toline += len(self.before) + h.added
199 self.toline += len(self.before) + h.added
200 self.before = []
200 self.before = []
201 self.hunk = []
201 self.hunk = []
202 self.proc = ''
202 self.proc = ''
203 self.context = context
203 self.context = context
204
204
205 def addhunk(self, hunk):
205 def addhunk(self, hunk):
206 if self.context:
206 if self.context:
207 self.before = self.context
207 self.before = self.context
208 self.context = []
208 self.context = []
209 self.hunk = data
209 self.hunk = data
210
210
211 def newfile(self, hdr):
211 def newfile(self, hdr):
212 self.addcontext([])
212 self.addcontext([])
213 h = header(hdr)
213 h = header(hdr)
214 self.stream.append(h)
214 self.stream.append(h)
215 self.header = h
215 self.header = h
216
216
217 def finished(self):
217 def finished(self):
218 self.addcontext([])
218 self.addcontext([])
219 return self.stream
219 return self.stream
220
220
221 transitions = {
221 transitions = {
222 'file': {'context': addcontext,
222 'file': {'context': addcontext,
223 'file': newfile,
223 'file': newfile,
224 'hunk': addhunk,
224 'hunk': addhunk,
225 'range': addrange},
225 'range': addrange},
226 'context': {'file': newfile,
226 'context': {'file': newfile,
227 'hunk': addhunk,
227 'hunk': addhunk,
228 'range': addrange},
228 'range': addrange},
229 'hunk': {'context': addcontext,
229 'hunk': {'context': addcontext,
230 'file': newfile,
230 'file': newfile,
231 'range': addrange},
231 'range': addrange},
232 'range': {'context': addcontext,
232 'range': {'context': addcontext,
233 'hunk': addhunk},
233 'hunk': addhunk},
234 }
234 }
235
235
236 p = parser()
236 p = parser()
237
237
238 state = 'context'
238 state = 'context'
239 for newstate, data in scanpatch(fp):
239 for newstate, data in scanpatch(fp):
240 try:
240 try:
241 p.transitions[state][newstate](p, data)
241 p.transitions[state][newstate](p, data)
242 except KeyError:
242 except KeyError:
243 raise patch.PatchError('unhandled transition: %s -> %s' %
243 raise patch.PatchError('unhandled transition: %s -> %s' %
244 (state, newstate))
244 (state, newstate))
245 state = newstate
245 state = newstate
246 return p.finished()
246 return p.finished()
247
247
248 def filterpatch(ui, chunks):
248 def filterpatch(ui, chunks):
249 """Interactively filter patch chunks into applied-only chunks"""
249 """Interactively filter patch chunks into applied-only chunks"""
250 chunks = list(chunks)
250 chunks = list(chunks)
251 chunks.reverse()
251 chunks.reverse()
252 seen = {}
252 seen = {}
253 def consumefile():
253 def consumefile():
254 """fetch next portion from chunks until a 'header' is seen
254 """fetch next portion from chunks until a 'header' is seen
255 NB: header == new-file mark
255 NB: header == new-file mark
256 """
256 """
257 consumed = []
257 consumed = []
258 while chunks:
258 while chunks:
259 if isinstance(chunks[-1], header):
259 if isinstance(chunks[-1], header):
260 break
260 break
261 else:
261 else:
262 consumed.append(chunks.pop())
262 consumed.append(chunks.pop())
263 return consumed
263 return consumed
264
264
265 resp_all = [None] # this two are changed from inside prompt,
265 resp_all = [None] # this two are changed from inside prompt,
266 resp_file = [None] # so can't be usual variables
266 resp_file = [None] # so can't be usual variables
267 applied = {} # 'filename' -> [] of chunks
267 applied = {} # 'filename' -> [] of chunks
268 def prompt(query):
268 def prompt(query):
269 """prompt query, and process base inputs
269 """prompt query, and process base inputs
270
270
271 - y/n for the rest of file
271 - y/n for the rest of file
272 - y/n for the rest
272 - y/n for the rest
273 - ? (help)
273 - ? (help)
274 - q (quit)
274 - q (quit)
275
275
276 else, input is returned to the caller.
276 else, input is returned to the caller.
277 """
277 """
278 if resp_all[0] is not None:
278 if resp_all[0] is not None:
279 return resp_all[0]
279 return resp_all[0]
280 if resp_file[0] is not None:
280 if resp_file[0] is not None:
281 return resp_file[0]
281 return resp_file[0]
282 while True:
282 while True:
283 r = (ui.prompt(query + _(' [Ynsfdaq?] '), '(?i)[Ynsfdaq?]?$')
283 r = (ui.prompt(query + _(' [Ynsfdaq?] '), '(?i)[Ynsfdaq?]?$')
284 or 'y').lower()
284 or 'y').lower()
285 if r == '?':
285 if r == '?':
286 c = record.__doc__.find('y - record this change')
286 c = record.__doc__.find('y - record this change')
287 for l in record.__doc__[c:].splitlines():
287 for l in record.__doc__[c:].splitlines():
288 if l: ui.write(_(l.strip()), '\n')
288 if l: ui.write(_(l.strip()), '\n')
289 continue
289 continue
290 elif r == 's':
290 elif r == 's':
291 r = resp_file[0] = 'n'
291 r = resp_file[0] = 'n'
292 elif r == 'f':
292 elif r == 'f':
293 r = resp_file[0] = 'y'
293 r = resp_file[0] = 'y'
294 elif r == 'd':
294 elif r == 'd':
295 r = resp_all[0] = 'n'
295 r = resp_all[0] = 'n'
296 elif r == 'a':
296 elif r == 'a':
297 r = resp_all[0] = 'y'
297 r = resp_all[0] = 'y'
298 elif r == 'q':
298 elif r == 'q':
299 raise util.Abort(_('user quit'))
299 raise util.Abort(_('user quit'))
300 return r
300 return r
301 while chunks:
301 while chunks:
302 chunk = chunks.pop()
302 chunk = chunks.pop()
303 if isinstance(chunk, header):
303 if isinstance(chunk, header):
304 # new-file mark
304 # new-file mark
305 resp_file = [None]
305 resp_file = [None]
306 fixoffset = 0
306 fixoffset = 0
307 hdr = ''.join(chunk.header)
307 hdr = ''.join(chunk.header)
308 if hdr in seen:
308 if hdr in seen:
309 consumefile()
309 consumefile()
310 continue
310 continue
311 seen[hdr] = True
311 seen[hdr] = True
312 if resp_all[0] is None:
312 if resp_all[0] is None:
313 chunk.pretty(ui)
313 chunk.pretty(ui)
314 r = prompt(_('examine changes to %s?') %
314 r = prompt(_('examine changes to %s?') %
315 _(' and ').join(map(repr, chunk.files())))
315 _(' and ').join(map(repr, chunk.files())))
316 if r == 'y':
316 if r == 'y':
317 applied[chunk.filename()] = [chunk]
317 applied[chunk.filename()] = [chunk]
318 if chunk.allhunks():
318 if chunk.allhunks():
319 applied[chunk.filename()] += consumefile()
319 applied[chunk.filename()] += consumefile()
320 else:
320 else:
321 consumefile()
321 consumefile()
322 else:
322 else:
323 # new hunk
323 # new hunk
324 if resp_file[0] is None and resp_all[0] is None:
324 if resp_file[0] is None and resp_all[0] is None:
325 chunk.pretty(ui)
325 chunk.pretty(ui)
326 r = prompt(_('record this change to %r?') %
326 r = prompt(_('record this change to %r?') %
327 chunk.filename())
327 chunk.filename())
328 if r == 'y':
328 if r == 'y':
329 if fixoffset:
329 if fixoffset:
330 chunk = copy.copy(chunk)
330 chunk = copy.copy(chunk)
331 chunk.toline += fixoffset
331 chunk.toline += fixoffset
332 applied[chunk.filename()].append(chunk)
332 applied[chunk.filename()].append(chunk)
333 else:
333 else:
334 fixoffset += chunk.removed - chunk.added
334 fixoffset += chunk.removed - chunk.added
335 return reduce(operator.add, [h for h in applied.itervalues()
335 return reduce(operator.add, [h for h in applied.itervalues()
336 if h[0].special() or len(h) > 1], [])
336 if h[0].special() or len(h) > 1], [])
337
337
338 def record(ui, repo, *pats, **opts):
338 def record(ui, repo, *pats, **opts):
339 '''interactively select changes to commit
339 '''interactively select changes to commit
340
340
341 If a list of files is omitted, all changes reported by "hg status"
341 If a list of files is omitted, all changes reported by "hg status"
342 will be candidates for recording.
342 will be candidates for recording.
343
343
344 See 'hg help dates' for a list of formats valid for -d/--date.
344 See 'hg help dates' for a list of formats valid for -d/--date.
345
345
346 You will be prompted for whether to record changes to each
346 You will be prompted for whether to record changes to each
347 modified file, and for files with multiple changes, for each
347 modified file, and for files with multiple changes, for each
348 change to use. For each query, the following responses are
348 change to use. For each query, the following responses are
349 possible:
349 possible:
350
350
351 y - record this change
351 y - record this change
352 n - skip this change
352 n - skip this change
353
353
354 s - skip remaining changes to this file
354 s - skip remaining changes to this file
355 f - record remaining changes to this file
355 f - record remaining changes to this file
356
356
357 d - done, skip remaining changes and files
357 d - done, skip remaining changes and files
358 a - record all changes to all remaining files
358 a - record all changes to all remaining files
359 q - quit, recording no changes
359 q - quit, recording no changes
360
360
361 ? - display help'''
361 ? - display help'''
362
362
363 def record_committer(ui, repo, pats, opts):
363 def record_committer(ui, repo, pats, opts):
364 commands.commit(ui, repo, *pats, **opts)
364 commands.commit(ui, repo, *pats, **opts)
365
365
366 dorecord(ui, repo, record_committer, *pats, **opts)
366 dorecord(ui, repo, record_committer, *pats, **opts)
367
367
368
368
369 def qrecord(ui, repo, patch, *pats, **opts):
369 def qrecord(ui, repo, patch, *pats, **opts):
370 '''interactively record a new patch
370 '''interactively record a new patch
371
371
372 see 'hg help qnew' & 'hg help record' for more information and usage
372 see 'hg help qnew' & 'hg help record' for more information and usage
373 '''
373 '''
374
374
375 try:
375 try:
376 mq = extensions.find('mq')
376 mq = extensions.find('mq')
377 except KeyError:
377 except KeyError:
378 raise util.Abort(_("'mq' extension not loaded"))
378 raise util.Abort(_("'mq' extension not loaded"))
379
379
380 def qrecord_committer(ui, repo, pats, opts):
380 def qrecord_committer(ui, repo, pats, opts):
381 mq.new(ui, repo, patch, *pats, **opts)
381 mq.new(ui, repo, patch, *pats, **opts)
382
382
383 opts = opts.copy()
383 opts = opts.copy()
384 opts['force'] = True # always 'qnew -f'
384 opts['force'] = True # always 'qnew -f'
385 dorecord(ui, repo, qrecord_committer, *pats, **opts)
385 dorecord(ui, repo, qrecord_committer, *pats, **opts)
386
386
387
387
388 def dorecord(ui, repo, committer, *pats, **opts):
388 def dorecord(ui, repo, committer, *pats, **opts):
389 if not ui.interactive:
389 if not ui.interactive:
390 raise util.Abort(_('running non-interactively, use commit instead'))
390 raise util.Abort(_('running non-interactively, use commit instead'))
391
391
392 def recordfunc(ui, repo, message, match, opts):
392 def recordfunc(ui, repo, message, match, opts):
393 """This is generic record driver.
393 """This is generic record driver.
394
394
395 It's job is to interactively filter local changes, and accordingly
395 It's job is to interactively filter local changes, and accordingly
396 prepare working dir into a state, where the job can be delegated to
396 prepare working dir into a state, where the job can be delegated to
397 non-interactive commit command such as 'commit' or 'qrefresh'.
397 non-interactive commit command such as 'commit' or 'qrefresh'.
398
398
399 After the actual job is done by non-interactive command, working dir
399 After the actual job is done by non-interactive command, working dir
400 state is restored to original.
400 state is restored to original.
401
401
402 In the end we'll record intresting changes, and everything else will be
402 In the end we'll record intresting changes, and everything else will be
403 left in place, so the user can continue his work.
403 left in place, so the user can continue his work.
404 """
404 """
405 if match.files():
405 if match.files():
406 changes = None
406 changes = None
407 else:
407 else:
408 changes = repo.status(match=match)[:5]
408 changes = repo.status(match=match)[:5]
409 modified, added, removed = changes[:3]
409 modified, added, removed = changes[:3]
410 match = cmdutil.matchfiles(repo, modified + added + removed)
410 match = cmdutil.matchfiles(repo, modified + added + removed)
411 diffopts = mdiff.diffopts(git=True, nodates=True)
411 diffopts = mdiff.diffopts(git=True, nodates=True)
412 fp = cStringIO.StringIO()
412 fp = cStringIO.StringIO()
413 patch.diff(repo, repo.dirstate.parents()[0], match=match,
413 patch.diff(repo, repo.dirstate.parents()[0], match=match,
414 changes=changes, opts=diffopts, fp=fp)
414 changes=changes, opts=diffopts, fp=fp)
415 fp.seek(0)
415 fp.seek(0)
416
416
417 # 1. filter patch, so we have intending-to apply subset of it
417 # 1. filter patch, so we have intending-to apply subset of it
418 chunks = filterpatch(ui, parsepatch(fp))
418 chunks = filterpatch(ui, parsepatch(fp))
419 del fp
419 del fp
420
420
421 contenders = {}
421 contenders = {}
422 for h in chunks:
422 for h in chunks:
423 try: contenders.update(dict.fromkeys(h.files()))
423 try: contenders.update(dict.fromkeys(h.files()))
424 except AttributeError: pass
424 except AttributeError: pass
425
425
426 newfiles = [f for f in match.files() if f in contenders]
426 newfiles = [f for f in match.files() if f in contenders]
427
427
428 if not newfiles:
428 if not newfiles:
429 ui.status(_('no changes to record\n'))
429 ui.status(_('no changes to record\n'))
430 return 0
430 return 0
431
431
432 if changes is None:
432 if changes is None:
433 match = cmdutil.matchfiles(repo, newfiles)
433 match = cmdutil.matchfiles(repo, newfiles)
434 changes = repo.status(files=match.files(), match=match)[:5]
434 changes = repo.status(match=match)[:5]
435 modified = dict.fromkeys(changes[0])
435 modified = dict.fromkeys(changes[0])
436
436
437 # 2. backup changed files, so we can restore them in the end
437 # 2. backup changed files, so we can restore them in the end
438 backups = {}
438 backups = {}
439 backupdir = repo.join('record-backups')
439 backupdir = repo.join('record-backups')
440 try:
440 try:
441 os.mkdir(backupdir)
441 os.mkdir(backupdir)
442 except OSError, err:
442 except OSError, err:
443 if err.errno != errno.EEXIST:
443 if err.errno != errno.EEXIST:
444 raise
444 raise
445 try:
445 try:
446 # backup continues
446 # backup continues
447 for f in newfiles:
447 for f in newfiles:
448 if f not in modified:
448 if f not in modified:
449 continue
449 continue
450 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
450 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
451 dir=backupdir)
451 dir=backupdir)
452 os.close(fd)
452 os.close(fd)
453 ui.debug('backup %r as %r\n' % (f, tmpname))
453 ui.debug('backup %r as %r\n' % (f, tmpname))
454 util.copyfile(repo.wjoin(f), tmpname)
454 util.copyfile(repo.wjoin(f), tmpname)
455 backups[f] = tmpname
455 backups[f] = tmpname
456
456
457 fp = cStringIO.StringIO()
457 fp = cStringIO.StringIO()
458 for c in chunks:
458 for c in chunks:
459 if c.filename() in backups:
459 if c.filename() in backups:
460 c.write(fp)
460 c.write(fp)
461 dopatch = fp.tell()
461 dopatch = fp.tell()
462 fp.seek(0)
462 fp.seek(0)
463
463
464 # 3a. apply filtered patch to clean repo (clean)
464 # 3a. apply filtered patch to clean repo (clean)
465 if backups:
465 if backups:
466 hg.revert(repo, repo.dirstate.parents()[0], backups.has_key)
466 hg.revert(repo, repo.dirstate.parents()[0], backups.has_key)
467
467
468 # 3b. (apply)
468 # 3b. (apply)
469 if dopatch:
469 if dopatch:
470 ui.debug('applying patch\n')
470 ui.debug('applying patch\n')
471 ui.debug(fp.getvalue())
471 ui.debug(fp.getvalue())
472 patch.internalpatch(fp, ui, 1, repo.root)
472 patch.internalpatch(fp, ui, 1, repo.root)
473 del fp
473 del fp
474
474
475 # 4. We prepared working directory according to filtered patch.
475 # 4. We prepared working directory according to filtered patch.
476 # Now is the time to delegate the job to commit/qrefresh or the like!
476 # Now is the time to delegate the job to commit/qrefresh or the like!
477
477
478 # it is important to first chdir to repo root -- we'll call a
478 # it is important to first chdir to repo root -- we'll call a
479 # highlevel command with list of pathnames relative to repo root
479 # highlevel command with list of pathnames relative to repo root
480 cwd = os.getcwd()
480 cwd = os.getcwd()
481 os.chdir(repo.root)
481 os.chdir(repo.root)
482 try:
482 try:
483 committer(ui, repo, newfiles, opts)
483 committer(ui, repo, newfiles, opts)
484 finally:
484 finally:
485 os.chdir(cwd)
485 os.chdir(cwd)
486
486
487 return 0
487 return 0
488 finally:
488 finally:
489 # 5. finally restore backed-up files
489 # 5. finally restore backed-up files
490 try:
490 try:
491 for realname, tmpname in backups.iteritems():
491 for realname, tmpname in backups.iteritems():
492 ui.debug('restoring %r to %r\n' % (tmpname, realname))
492 ui.debug('restoring %r to %r\n' % (tmpname, realname))
493 util.copyfile(tmpname, repo.wjoin(realname))
493 util.copyfile(tmpname, repo.wjoin(realname))
494 os.unlink(tmpname)
494 os.unlink(tmpname)
495 os.rmdir(backupdir)
495 os.rmdir(backupdir)
496 except OSError:
496 except OSError:
497 pass
497 pass
498 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
498 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
499
499
500 cmdtable = {
500 cmdtable = {
501 "record":
501 "record":
502 (record,
502 (record,
503
503
504 # add commit options
504 # add commit options
505 commands.table['^commit|ci'][1],
505 commands.table['^commit|ci'][1],
506
506
507 _('hg record [OPTION]... [FILE]...')),
507 _('hg record [OPTION]... [FILE]...')),
508 }
508 }
509
509
510
510
511 def extsetup():
511 def extsetup():
512 try:
512 try:
513 mq = extensions.find('mq')
513 mq = extensions.find('mq')
514 except KeyError:
514 except KeyError:
515 return
515 return
516
516
517 qcmdtable = {
517 qcmdtable = {
518 "qrecord":
518 "qrecord":
519 (qrecord,
519 (qrecord,
520
520
521 # add qnew options, except '--force'
521 # add qnew options, except '--force'
522 [opt for opt in mq.cmdtable['qnew'][1] if opt[1] != 'force'],
522 [opt for opt in mq.cmdtable['qnew'][1] if opt[1] != 'force'],
523
523
524 _('hg qrecord [OPTION]... PATCH [FILE]...')),
524 _('hg qrecord [OPTION]... PATCH [FILE]...')),
525 }
525 }
526
526
527 cmdtable.update(qcmdtable)
527 cmdtable.update(qcmdtable)
528
528
@@ -1,1188 +1,1188 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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
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, bisect, stat
10 import os, sys, bisect, stat
11 import mdiff, bdiff, util, templater, templatefilters, patch, errno
11 import mdiff, bdiff, util, templater, templatefilters, patch, errno
12 import match as _match
12 import match as _match
13
13
14 revrangesep = ':'
14 revrangesep = ':'
15
15
16 class UnknownCommand(Exception):
16 class UnknownCommand(Exception):
17 """Exception raised if command is not in the command table."""
17 """Exception raised if command is not in the command table."""
18 class AmbiguousCommand(Exception):
18 class AmbiguousCommand(Exception):
19 """Exception raised if command shortcut matches more than one command."""
19 """Exception raised if command shortcut matches more than one command."""
20
20
21 def findpossible(ui, cmd, table):
21 def findpossible(ui, cmd, table):
22 """
22 """
23 Return cmd -> (aliases, command table entry)
23 Return cmd -> (aliases, command table entry)
24 for each matching command.
24 for each matching command.
25 Return debug commands (or their aliases) only if no normal command matches.
25 Return debug commands (or their aliases) only if no normal command matches.
26 """
26 """
27 choice = {}
27 choice = {}
28 debugchoice = {}
28 debugchoice = {}
29 for e in table.keys():
29 for e in table.keys():
30 aliases = e.lstrip("^").split("|")
30 aliases = e.lstrip("^").split("|")
31 found = None
31 found = None
32 if cmd in aliases:
32 if cmd in aliases:
33 found = cmd
33 found = cmd
34 elif not ui.config("ui", "strict"):
34 elif not ui.config("ui", "strict"):
35 for a in aliases:
35 for a in aliases:
36 if a.startswith(cmd):
36 if a.startswith(cmd):
37 found = a
37 found = a
38 break
38 break
39 if found is not None:
39 if found is not None:
40 if aliases[0].startswith("debug") or found.startswith("debug"):
40 if aliases[0].startswith("debug") or found.startswith("debug"):
41 debugchoice[found] = (aliases, table[e])
41 debugchoice[found] = (aliases, table[e])
42 else:
42 else:
43 choice[found] = (aliases, table[e])
43 choice[found] = (aliases, table[e])
44
44
45 if not choice and debugchoice:
45 if not choice and debugchoice:
46 choice = debugchoice
46 choice = debugchoice
47
47
48 return choice
48 return choice
49
49
50 def findcmd(ui, cmd, table):
50 def findcmd(ui, cmd, table):
51 """Return (aliases, command table entry) for command string."""
51 """Return (aliases, command table entry) for command string."""
52 choice = findpossible(ui, cmd, table)
52 choice = findpossible(ui, cmd, table)
53
53
54 if cmd in choice:
54 if cmd in choice:
55 return choice[cmd]
55 return choice[cmd]
56
56
57 if len(choice) > 1:
57 if len(choice) > 1:
58 clist = choice.keys()
58 clist = choice.keys()
59 clist.sort()
59 clist.sort()
60 raise AmbiguousCommand(cmd, clist)
60 raise AmbiguousCommand(cmd, clist)
61
61
62 if choice:
62 if choice:
63 return choice.values()[0]
63 return choice.values()[0]
64
64
65 raise UnknownCommand(cmd)
65 raise UnknownCommand(cmd)
66
66
67 def bail_if_changed(repo):
67 def bail_if_changed(repo):
68 if repo.dirstate.parents()[1] != nullid:
68 if repo.dirstate.parents()[1] != nullid:
69 raise util.Abort(_('outstanding uncommitted merge'))
69 raise util.Abort(_('outstanding uncommitted merge'))
70 modified, added, removed, deleted = repo.status()[:4]
70 modified, added, removed, deleted = repo.status()[:4]
71 if modified or added or removed or deleted:
71 if modified or added or removed or deleted:
72 raise util.Abort(_("outstanding uncommitted changes"))
72 raise util.Abort(_("outstanding uncommitted changes"))
73
73
74 def logmessage(opts):
74 def logmessage(opts):
75 """ get the log message according to -m and -l option """
75 """ get the log message according to -m and -l option """
76 message = opts['message']
76 message = opts['message']
77 logfile = opts['logfile']
77 logfile = opts['logfile']
78
78
79 if message and logfile:
79 if message and logfile:
80 raise util.Abort(_('options --message and --logfile are mutually '
80 raise util.Abort(_('options --message and --logfile are mutually '
81 'exclusive'))
81 'exclusive'))
82 if not message and logfile:
82 if not message and logfile:
83 try:
83 try:
84 if logfile == '-':
84 if logfile == '-':
85 message = sys.stdin.read()
85 message = sys.stdin.read()
86 else:
86 else:
87 message = open(logfile).read()
87 message = open(logfile).read()
88 except IOError, inst:
88 except IOError, inst:
89 raise util.Abort(_("can't read commit message '%s': %s") %
89 raise util.Abort(_("can't read commit message '%s': %s") %
90 (logfile, inst.strerror))
90 (logfile, inst.strerror))
91 return message
91 return message
92
92
93 def loglimit(opts):
93 def loglimit(opts):
94 """get the log limit according to option -l/--limit"""
94 """get the log limit according to option -l/--limit"""
95 limit = opts.get('limit')
95 limit = opts.get('limit')
96 if limit:
96 if limit:
97 try:
97 try:
98 limit = int(limit)
98 limit = int(limit)
99 except ValueError:
99 except ValueError:
100 raise util.Abort(_('limit must be a positive integer'))
100 raise util.Abort(_('limit must be a positive integer'))
101 if limit <= 0: raise util.Abort(_('limit must be positive'))
101 if limit <= 0: raise util.Abort(_('limit must be positive'))
102 else:
102 else:
103 limit = sys.maxint
103 limit = sys.maxint
104 return limit
104 return limit
105
105
106 def setremoteconfig(ui, opts):
106 def setremoteconfig(ui, opts):
107 "copy remote options to ui tree"
107 "copy remote options to ui tree"
108 if opts.get('ssh'):
108 if opts.get('ssh'):
109 ui.setconfig("ui", "ssh", opts['ssh'])
109 ui.setconfig("ui", "ssh", opts['ssh'])
110 if opts.get('remotecmd'):
110 if opts.get('remotecmd'):
111 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
111 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
112
112
113 def revpair(repo, revs):
113 def revpair(repo, revs):
114 '''return pair of nodes, given list of revisions. second item can
114 '''return pair of nodes, given list of revisions. second item can
115 be None, meaning use working dir.'''
115 be None, meaning use working dir.'''
116
116
117 def revfix(repo, val, defval):
117 def revfix(repo, val, defval):
118 if not val and val != 0 and defval is not None:
118 if not val and val != 0 and defval is not None:
119 val = defval
119 val = defval
120 return repo.lookup(val)
120 return repo.lookup(val)
121
121
122 if not revs:
122 if not revs:
123 return repo.dirstate.parents()[0], None
123 return repo.dirstate.parents()[0], None
124 end = None
124 end = None
125 if len(revs) == 1:
125 if len(revs) == 1:
126 if revrangesep in revs[0]:
126 if revrangesep in revs[0]:
127 start, end = revs[0].split(revrangesep, 1)
127 start, end = revs[0].split(revrangesep, 1)
128 start = revfix(repo, start, 0)
128 start = revfix(repo, start, 0)
129 end = revfix(repo, end, repo.changelog.count() - 1)
129 end = revfix(repo, end, repo.changelog.count() - 1)
130 else:
130 else:
131 start = revfix(repo, revs[0], None)
131 start = revfix(repo, revs[0], None)
132 elif len(revs) == 2:
132 elif len(revs) == 2:
133 if revrangesep in revs[0] or revrangesep in revs[1]:
133 if revrangesep in revs[0] or revrangesep in revs[1]:
134 raise util.Abort(_('too many revisions specified'))
134 raise util.Abort(_('too many revisions specified'))
135 start = revfix(repo, revs[0], None)
135 start = revfix(repo, revs[0], None)
136 end = revfix(repo, revs[1], None)
136 end = revfix(repo, revs[1], None)
137 else:
137 else:
138 raise util.Abort(_('too many revisions specified'))
138 raise util.Abort(_('too many revisions specified'))
139 return start, end
139 return start, end
140
140
141 def revrange(repo, revs):
141 def revrange(repo, revs):
142 """Yield revision as strings from a list of revision specifications."""
142 """Yield revision as strings from a list of revision specifications."""
143
143
144 def revfix(repo, val, defval):
144 def revfix(repo, val, defval):
145 if not val and val != 0 and defval is not None:
145 if not val and val != 0 and defval is not None:
146 return defval
146 return defval
147 return repo.changelog.rev(repo.lookup(val))
147 return repo.changelog.rev(repo.lookup(val))
148
148
149 seen, l = {}, []
149 seen, l = {}, []
150 for spec in revs:
150 for spec in revs:
151 if revrangesep in spec:
151 if revrangesep in spec:
152 start, end = spec.split(revrangesep, 1)
152 start, end = spec.split(revrangesep, 1)
153 start = revfix(repo, start, 0)
153 start = revfix(repo, start, 0)
154 end = revfix(repo, end, repo.changelog.count() - 1)
154 end = revfix(repo, end, repo.changelog.count() - 1)
155 step = start > end and -1 or 1
155 step = start > end and -1 or 1
156 for rev in xrange(start, end+step, step):
156 for rev in xrange(start, end+step, step):
157 if rev in seen:
157 if rev in seen:
158 continue
158 continue
159 seen[rev] = 1
159 seen[rev] = 1
160 l.append(rev)
160 l.append(rev)
161 else:
161 else:
162 rev = revfix(repo, spec, None)
162 rev = revfix(repo, spec, None)
163 if rev in seen:
163 if rev in seen:
164 continue
164 continue
165 seen[rev] = 1
165 seen[rev] = 1
166 l.append(rev)
166 l.append(rev)
167
167
168 return l
168 return l
169
169
170 def make_filename(repo, pat, node,
170 def make_filename(repo, pat, node,
171 total=None, seqno=None, revwidth=None, pathname=None):
171 total=None, seqno=None, revwidth=None, pathname=None):
172 node_expander = {
172 node_expander = {
173 'H': lambda: hex(node),
173 'H': lambda: hex(node),
174 'R': lambda: str(repo.changelog.rev(node)),
174 'R': lambda: str(repo.changelog.rev(node)),
175 'h': lambda: short(node),
175 'h': lambda: short(node),
176 }
176 }
177 expander = {
177 expander = {
178 '%': lambda: '%',
178 '%': lambda: '%',
179 'b': lambda: os.path.basename(repo.root),
179 'b': lambda: os.path.basename(repo.root),
180 }
180 }
181
181
182 try:
182 try:
183 if node:
183 if node:
184 expander.update(node_expander)
184 expander.update(node_expander)
185 if node:
185 if node:
186 expander['r'] = (lambda:
186 expander['r'] = (lambda:
187 str(repo.changelog.rev(node)).zfill(revwidth or 0))
187 str(repo.changelog.rev(node)).zfill(revwidth or 0))
188 if total is not None:
188 if total is not None:
189 expander['N'] = lambda: str(total)
189 expander['N'] = lambda: str(total)
190 if seqno is not None:
190 if seqno is not None:
191 expander['n'] = lambda: str(seqno)
191 expander['n'] = lambda: str(seqno)
192 if total is not None and seqno is not None:
192 if total is not None and seqno is not None:
193 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
193 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
194 if pathname is not None:
194 if pathname is not None:
195 expander['s'] = lambda: os.path.basename(pathname)
195 expander['s'] = lambda: os.path.basename(pathname)
196 expander['d'] = lambda: os.path.dirname(pathname) or '.'
196 expander['d'] = lambda: os.path.dirname(pathname) or '.'
197 expander['p'] = lambda: pathname
197 expander['p'] = lambda: pathname
198
198
199 newname = []
199 newname = []
200 patlen = len(pat)
200 patlen = len(pat)
201 i = 0
201 i = 0
202 while i < patlen:
202 while i < patlen:
203 c = pat[i]
203 c = pat[i]
204 if c == '%':
204 if c == '%':
205 i += 1
205 i += 1
206 c = pat[i]
206 c = pat[i]
207 c = expander[c]()
207 c = expander[c]()
208 newname.append(c)
208 newname.append(c)
209 i += 1
209 i += 1
210 return ''.join(newname)
210 return ''.join(newname)
211 except KeyError, inst:
211 except KeyError, inst:
212 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
212 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
213 inst.args[0])
213 inst.args[0])
214
214
215 def make_file(repo, pat, node=None,
215 def make_file(repo, pat, node=None,
216 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
216 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
217 if not pat or pat == '-':
217 if not pat or pat == '-':
218 return 'w' in mode and sys.stdout or sys.stdin
218 return 'w' in mode and sys.stdout or sys.stdin
219 if hasattr(pat, 'write') and 'w' in mode:
219 if hasattr(pat, 'write') and 'w' in mode:
220 return pat
220 return pat
221 if hasattr(pat, 'read') and 'r' in mode:
221 if hasattr(pat, 'read') and 'r' in mode:
222 return pat
222 return pat
223 return open(make_filename(repo, pat, node, total, seqno, revwidth,
223 return open(make_filename(repo, pat, node, total, seqno, revwidth,
224 pathname),
224 pathname),
225 mode)
225 mode)
226
226
227 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
227 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
228 if not globbed and default == 'relpath':
228 if not globbed and default == 'relpath':
229 pats = util.expand_glob(pats or [])
229 pats = util.expand_glob(pats or [])
230 m = _match.match(repo.root, repo.getcwd(), pats,
230 m = _match.match(repo.root, repo.getcwd(), pats,
231 opts.get('include'), opts.get('exclude'), default)
231 opts.get('include'), opts.get('exclude'), default)
232 def badfn(f, msg):
232 def badfn(f, msg):
233 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
233 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
234 return False
234 return False
235 m.bad = badfn
235 m.bad = badfn
236 return m
236 return m
237
237
238 def matchall(repo):
238 def matchall(repo):
239 return _match.always(repo.root, repo.getcwd())
239 return _match.always(repo.root, repo.getcwd())
240
240
241 def matchfiles(repo, files):
241 def matchfiles(repo, files):
242 return _match.exact(repo.root, repo.getcwd(), files)
242 return _match.exact(repo.root, repo.getcwd(), files)
243
243
244 def findrenames(repo, added=None, removed=None, threshold=0.5):
244 def findrenames(repo, added=None, removed=None, threshold=0.5):
245 '''find renamed files -- yields (before, after, score) tuples'''
245 '''find renamed files -- yields (before, after, score) tuples'''
246 if added is None or removed is None:
246 if added is None or removed is None:
247 added, removed = repo.status()[1:3]
247 added, removed = repo.status()[1:3]
248 ctx = repo.changectx()
248 ctx = repo.changectx()
249 for a in added:
249 for a in added:
250 aa = repo.wread(a)
250 aa = repo.wread(a)
251 bestname, bestscore = None, threshold
251 bestname, bestscore = None, threshold
252 for r in removed:
252 for r in removed:
253 rr = ctx.filectx(r).data()
253 rr = ctx.filectx(r).data()
254
254
255 # bdiff.blocks() returns blocks of matching lines
255 # bdiff.blocks() returns blocks of matching lines
256 # count the number of bytes in each
256 # count the number of bytes in each
257 equal = 0
257 equal = 0
258 alines = mdiff.splitnewlines(aa)
258 alines = mdiff.splitnewlines(aa)
259 matches = bdiff.blocks(aa, rr)
259 matches = bdiff.blocks(aa, rr)
260 for x1,x2,y1,y2 in matches:
260 for x1,x2,y1,y2 in matches:
261 for line in alines[x1:x2]:
261 for line in alines[x1:x2]:
262 equal += len(line)
262 equal += len(line)
263
263
264 lengths = len(aa) + len(rr)
264 lengths = len(aa) + len(rr)
265 if lengths:
265 if lengths:
266 myscore = equal*2.0 / lengths
266 myscore = equal*2.0 / lengths
267 if myscore >= bestscore:
267 if myscore >= bestscore:
268 bestname, bestscore = r, myscore
268 bestname, bestscore = r, myscore
269 if bestname:
269 if bestname:
270 yield bestname, a, bestscore
270 yield bestname, a, bestscore
271
271
272 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
272 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
273 if dry_run is None:
273 if dry_run is None:
274 dry_run = opts.get('dry_run')
274 dry_run = opts.get('dry_run')
275 if similarity is None:
275 if similarity is None:
276 similarity = float(opts.get('similarity') or 0)
276 similarity = float(opts.get('similarity') or 0)
277 add, remove = [], []
277 add, remove = [], []
278 mapping = {}
278 mapping = {}
279 m = match(repo, pats, opts)
279 m = match(repo, pats, opts)
280 for abs in repo.walk(m):
280 for abs in repo.walk(m):
281 target = repo.wjoin(abs)
281 target = repo.wjoin(abs)
282 rel = m.rel(abs)
282 rel = m.rel(abs)
283 exact = m.exact(abs)
283 exact = m.exact(abs)
284 if abs not in repo.dirstate:
284 if abs not in repo.dirstate:
285 add.append(abs)
285 add.append(abs)
286 mapping[abs] = rel, m.exact(abs)
286 mapping[abs] = rel, m.exact(abs)
287 if repo.ui.verbose or not exact:
287 if repo.ui.verbose or not exact:
288 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
288 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
289 if repo.dirstate[abs] != 'r' and (not util.lexists(target)
289 if repo.dirstate[abs] != 'r' and (not util.lexists(target)
290 or (os.path.isdir(target) and not os.path.islink(target))):
290 or (os.path.isdir(target) and not os.path.islink(target))):
291 remove.append(abs)
291 remove.append(abs)
292 mapping[abs] = rel, exact
292 mapping[abs] = rel, exact
293 if repo.ui.verbose or not exact:
293 if repo.ui.verbose or not exact:
294 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
294 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
295 if not dry_run:
295 if not dry_run:
296 repo.remove(remove)
296 repo.remove(remove)
297 repo.add(add)
297 repo.add(add)
298 if similarity > 0:
298 if similarity > 0:
299 for old, new, score in findrenames(repo, add, remove, similarity):
299 for old, new, score in findrenames(repo, add, remove, similarity):
300 oldrel, oldexact = mapping[old]
300 oldrel, oldexact = mapping[old]
301 newrel, newexact = mapping[new]
301 newrel, newexact = mapping[new]
302 if repo.ui.verbose or not oldexact or not newexact:
302 if repo.ui.verbose or not oldexact or not newexact:
303 repo.ui.status(_('recording removal of %s as rename to %s '
303 repo.ui.status(_('recording removal of %s as rename to %s '
304 '(%d%% similar)\n') %
304 '(%d%% similar)\n') %
305 (oldrel, newrel, score * 100))
305 (oldrel, newrel, score * 100))
306 if not dry_run:
306 if not dry_run:
307 repo.copy(old, new)
307 repo.copy(old, new)
308
308
309 def copy(ui, repo, pats, opts, rename=False):
309 def copy(ui, repo, pats, opts, rename=False):
310 # called with the repo lock held
310 # called with the repo lock held
311 #
311 #
312 # hgsep => pathname that uses "/" to separate directories
312 # hgsep => pathname that uses "/" to separate directories
313 # ossep => pathname that uses os.sep to separate directories
313 # ossep => pathname that uses os.sep to separate directories
314 cwd = repo.getcwd()
314 cwd = repo.getcwd()
315 targets = {}
315 targets = {}
316 after = opts.get("after")
316 after = opts.get("after")
317 dryrun = opts.get("dry_run")
317 dryrun = opts.get("dry_run")
318
318
319 def walkpat(pat):
319 def walkpat(pat):
320 srcs = []
320 srcs = []
321 m = match(repo, [pat], opts, globbed=True)
321 m = match(repo, [pat], opts, globbed=True)
322 for abs in repo.walk(m):
322 for abs in repo.walk(m):
323 state = repo.dirstate[abs]
323 state = repo.dirstate[abs]
324 rel = m.rel(abs)
324 rel = m.rel(abs)
325 exact = m.exact(abs)
325 exact = m.exact(abs)
326 if state in '?r':
326 if state in '?r':
327 if exact and state == '?':
327 if exact and state == '?':
328 ui.warn(_('%s: not copying - file is not managed\n') % rel)
328 ui.warn(_('%s: not copying - file is not managed\n') % rel)
329 if exact and state == 'r':
329 if exact and state == 'r':
330 ui.warn(_('%s: not copying - file has been marked for'
330 ui.warn(_('%s: not copying - file has been marked for'
331 ' remove\n') % rel)
331 ' remove\n') % rel)
332 continue
332 continue
333 # abs: hgsep
333 # abs: hgsep
334 # rel: ossep
334 # rel: ossep
335 srcs.append((abs, rel, exact))
335 srcs.append((abs, rel, exact))
336 return srcs
336 return srcs
337
337
338 # abssrc: hgsep
338 # abssrc: hgsep
339 # relsrc: ossep
339 # relsrc: ossep
340 # otarget: ossep
340 # otarget: ossep
341 def copyfile(abssrc, relsrc, otarget, exact):
341 def copyfile(abssrc, relsrc, otarget, exact):
342 abstarget = util.canonpath(repo.root, cwd, otarget)
342 abstarget = util.canonpath(repo.root, cwd, otarget)
343 reltarget = repo.pathto(abstarget, cwd)
343 reltarget = repo.pathto(abstarget, cwd)
344 target = repo.wjoin(abstarget)
344 target = repo.wjoin(abstarget)
345 src = repo.wjoin(abssrc)
345 src = repo.wjoin(abssrc)
346 state = repo.dirstate[abstarget]
346 state = repo.dirstate[abstarget]
347
347
348 # check for collisions
348 # check for collisions
349 prevsrc = targets.get(abstarget)
349 prevsrc = targets.get(abstarget)
350 if prevsrc is not None:
350 if prevsrc is not None:
351 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
351 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
352 (reltarget, repo.pathto(abssrc, cwd),
352 (reltarget, repo.pathto(abssrc, cwd),
353 repo.pathto(prevsrc, cwd)))
353 repo.pathto(prevsrc, cwd)))
354 return
354 return
355
355
356 # check for overwrites
356 # check for overwrites
357 exists = os.path.exists(target)
357 exists = os.path.exists(target)
358 if (not after and exists or after and state in 'mn'):
358 if (not after and exists or after and state in 'mn'):
359 if not opts['force']:
359 if not opts['force']:
360 ui.warn(_('%s: not overwriting - file exists\n') %
360 ui.warn(_('%s: not overwriting - file exists\n') %
361 reltarget)
361 reltarget)
362 return
362 return
363
363
364 if after:
364 if after:
365 if not exists:
365 if not exists:
366 return
366 return
367 elif not dryrun:
367 elif not dryrun:
368 try:
368 try:
369 if exists:
369 if exists:
370 os.unlink(target)
370 os.unlink(target)
371 targetdir = os.path.dirname(target) or '.'
371 targetdir = os.path.dirname(target) or '.'
372 if not os.path.isdir(targetdir):
372 if not os.path.isdir(targetdir):
373 os.makedirs(targetdir)
373 os.makedirs(targetdir)
374 util.copyfile(src, target)
374 util.copyfile(src, target)
375 except IOError, inst:
375 except IOError, inst:
376 if inst.errno == errno.ENOENT:
376 if inst.errno == errno.ENOENT:
377 ui.warn(_('%s: deleted in working copy\n') % relsrc)
377 ui.warn(_('%s: deleted in working copy\n') % relsrc)
378 else:
378 else:
379 ui.warn(_('%s: cannot copy - %s\n') %
379 ui.warn(_('%s: cannot copy - %s\n') %
380 (relsrc, inst.strerror))
380 (relsrc, inst.strerror))
381 return True # report a failure
381 return True # report a failure
382
382
383 if ui.verbose or not exact:
383 if ui.verbose or not exact:
384 action = rename and "moving" or "copying"
384 action = rename and "moving" or "copying"
385 ui.status(_('%s %s to %s\n') % (action, relsrc, reltarget))
385 ui.status(_('%s %s to %s\n') % (action, relsrc, reltarget))
386
386
387 targets[abstarget] = abssrc
387 targets[abstarget] = abssrc
388
388
389 # fix up dirstate
389 # fix up dirstate
390 origsrc = repo.dirstate.copied(abssrc) or abssrc
390 origsrc = repo.dirstate.copied(abssrc) or abssrc
391 if abstarget == origsrc: # copying back a copy?
391 if abstarget == origsrc: # copying back a copy?
392 if state not in 'mn' and not dryrun:
392 if state not in 'mn' and not dryrun:
393 repo.dirstate.normallookup(abstarget)
393 repo.dirstate.normallookup(abstarget)
394 else:
394 else:
395 if repo.dirstate[origsrc] == 'a':
395 if repo.dirstate[origsrc] == 'a':
396 if not ui.quiet:
396 if not ui.quiet:
397 ui.warn(_("%s has not been committed yet, so no copy "
397 ui.warn(_("%s has not been committed yet, so no copy "
398 "data will be stored for %s.\n")
398 "data will be stored for %s.\n")
399 % (repo.pathto(origsrc, cwd), reltarget))
399 % (repo.pathto(origsrc, cwd), reltarget))
400 if abstarget not in repo.dirstate and not dryrun:
400 if abstarget not in repo.dirstate and not dryrun:
401 repo.add([abstarget])
401 repo.add([abstarget])
402 elif not dryrun:
402 elif not dryrun:
403 repo.copy(origsrc, abstarget)
403 repo.copy(origsrc, abstarget)
404
404
405 if rename and not dryrun:
405 if rename and not dryrun:
406 repo.remove([abssrc], not after)
406 repo.remove([abssrc], not after)
407
407
408 # pat: ossep
408 # pat: ossep
409 # dest ossep
409 # dest ossep
410 # srcs: list of (hgsep, hgsep, ossep, bool)
410 # srcs: list of (hgsep, hgsep, ossep, bool)
411 # return: function that takes hgsep and returns ossep
411 # return: function that takes hgsep and returns ossep
412 def targetpathfn(pat, dest, srcs):
412 def targetpathfn(pat, dest, srcs):
413 if os.path.isdir(pat):
413 if os.path.isdir(pat):
414 abspfx = util.canonpath(repo.root, cwd, pat)
414 abspfx = util.canonpath(repo.root, cwd, pat)
415 abspfx = util.localpath(abspfx)
415 abspfx = util.localpath(abspfx)
416 if destdirexists:
416 if destdirexists:
417 striplen = len(os.path.split(abspfx)[0])
417 striplen = len(os.path.split(abspfx)[0])
418 else:
418 else:
419 striplen = len(abspfx)
419 striplen = len(abspfx)
420 if striplen:
420 if striplen:
421 striplen += len(os.sep)
421 striplen += len(os.sep)
422 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
422 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
423 elif destdirexists:
423 elif destdirexists:
424 res = lambda p: os.path.join(dest,
424 res = lambda p: os.path.join(dest,
425 os.path.basename(util.localpath(p)))
425 os.path.basename(util.localpath(p)))
426 else:
426 else:
427 res = lambda p: dest
427 res = lambda p: dest
428 return res
428 return res
429
429
430 # pat: ossep
430 # pat: ossep
431 # dest ossep
431 # dest ossep
432 # srcs: list of (hgsep, hgsep, ossep, bool)
432 # srcs: list of (hgsep, hgsep, ossep, bool)
433 # return: function that takes hgsep and returns ossep
433 # return: function that takes hgsep and returns ossep
434 def targetpathafterfn(pat, dest, srcs):
434 def targetpathafterfn(pat, dest, srcs):
435 if util.patkind(pat, None)[0]:
435 if util.patkind(pat, None)[0]:
436 # a mercurial pattern
436 # a mercurial pattern
437 res = lambda p: os.path.join(dest,
437 res = lambda p: os.path.join(dest,
438 os.path.basename(util.localpath(p)))
438 os.path.basename(util.localpath(p)))
439 else:
439 else:
440 abspfx = util.canonpath(repo.root, cwd, pat)
440 abspfx = util.canonpath(repo.root, cwd, pat)
441 if len(abspfx) < len(srcs[0][0]):
441 if len(abspfx) < len(srcs[0][0]):
442 # A directory. Either the target path contains the last
442 # A directory. Either the target path contains the last
443 # component of the source path or it does not.
443 # component of the source path or it does not.
444 def evalpath(striplen):
444 def evalpath(striplen):
445 score = 0
445 score = 0
446 for s in srcs:
446 for s in srcs:
447 t = os.path.join(dest, util.localpath(s[0])[striplen:])
447 t = os.path.join(dest, util.localpath(s[0])[striplen:])
448 if os.path.exists(t):
448 if os.path.exists(t):
449 score += 1
449 score += 1
450 return score
450 return score
451
451
452 abspfx = util.localpath(abspfx)
452 abspfx = util.localpath(abspfx)
453 striplen = len(abspfx)
453 striplen = len(abspfx)
454 if striplen:
454 if striplen:
455 striplen += len(os.sep)
455 striplen += len(os.sep)
456 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
456 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
457 score = evalpath(striplen)
457 score = evalpath(striplen)
458 striplen1 = len(os.path.split(abspfx)[0])
458 striplen1 = len(os.path.split(abspfx)[0])
459 if striplen1:
459 if striplen1:
460 striplen1 += len(os.sep)
460 striplen1 += len(os.sep)
461 if evalpath(striplen1) > score:
461 if evalpath(striplen1) > score:
462 striplen = striplen1
462 striplen = striplen1
463 res = lambda p: os.path.join(dest,
463 res = lambda p: os.path.join(dest,
464 util.localpath(p)[striplen:])
464 util.localpath(p)[striplen:])
465 else:
465 else:
466 # a file
466 # a file
467 if destdirexists:
467 if destdirexists:
468 res = lambda p: os.path.join(dest,
468 res = lambda p: os.path.join(dest,
469 os.path.basename(util.localpath(p)))
469 os.path.basename(util.localpath(p)))
470 else:
470 else:
471 res = lambda p: dest
471 res = lambda p: dest
472 return res
472 return res
473
473
474
474
475 pats = util.expand_glob(pats)
475 pats = util.expand_glob(pats)
476 if not pats:
476 if not pats:
477 raise util.Abort(_('no source or destination specified'))
477 raise util.Abort(_('no source or destination specified'))
478 if len(pats) == 1:
478 if len(pats) == 1:
479 raise util.Abort(_('no destination specified'))
479 raise util.Abort(_('no destination specified'))
480 dest = pats.pop()
480 dest = pats.pop()
481 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
481 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
482 if not destdirexists:
482 if not destdirexists:
483 if len(pats) > 1 or util.patkind(pats[0], None)[0]:
483 if len(pats) > 1 or util.patkind(pats[0], None)[0]:
484 raise util.Abort(_('with multiple sources, destination must be an '
484 raise util.Abort(_('with multiple sources, destination must be an '
485 'existing directory'))
485 'existing directory'))
486 if util.endswithsep(dest):
486 if util.endswithsep(dest):
487 raise util.Abort(_('destination %s is not a directory') % dest)
487 raise util.Abort(_('destination %s is not a directory') % dest)
488
488
489 tfn = targetpathfn
489 tfn = targetpathfn
490 if after:
490 if after:
491 tfn = targetpathafterfn
491 tfn = targetpathafterfn
492 copylist = []
492 copylist = []
493 for pat in pats:
493 for pat in pats:
494 srcs = walkpat(pat)
494 srcs = walkpat(pat)
495 if not srcs:
495 if not srcs:
496 continue
496 continue
497 copylist.append((tfn(pat, dest, srcs), srcs))
497 copylist.append((tfn(pat, dest, srcs), srcs))
498 if not copylist:
498 if not copylist:
499 raise util.Abort(_('no files to copy'))
499 raise util.Abort(_('no files to copy'))
500
500
501 errors = 0
501 errors = 0
502 for targetpath, srcs in copylist:
502 for targetpath, srcs in copylist:
503 for abssrc, relsrc, exact in srcs:
503 for abssrc, relsrc, exact in srcs:
504 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
504 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
505 errors += 1
505 errors += 1
506
506
507 if errors:
507 if errors:
508 ui.warn(_('(consider using --after)\n'))
508 ui.warn(_('(consider using --after)\n'))
509
509
510 return errors
510 return errors
511
511
512 def service(opts, parentfn=None, initfn=None, runfn=None):
512 def service(opts, parentfn=None, initfn=None, runfn=None):
513 '''Run a command as a service.'''
513 '''Run a command as a service.'''
514
514
515 if opts['daemon'] and not opts['daemon_pipefds']:
515 if opts['daemon'] and not opts['daemon_pipefds']:
516 rfd, wfd = os.pipe()
516 rfd, wfd = os.pipe()
517 args = sys.argv[:]
517 args = sys.argv[:]
518 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
518 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
519 # Don't pass --cwd to the child process, because we've already
519 # Don't pass --cwd to the child process, because we've already
520 # changed directory.
520 # changed directory.
521 for i in xrange(1,len(args)):
521 for i in xrange(1,len(args)):
522 if args[i].startswith('--cwd='):
522 if args[i].startswith('--cwd='):
523 del args[i]
523 del args[i]
524 break
524 break
525 elif args[i].startswith('--cwd'):
525 elif args[i].startswith('--cwd'):
526 del args[i:i+2]
526 del args[i:i+2]
527 break
527 break
528 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
528 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
529 args[0], args)
529 args[0], args)
530 os.close(wfd)
530 os.close(wfd)
531 os.read(rfd, 1)
531 os.read(rfd, 1)
532 if parentfn:
532 if parentfn:
533 return parentfn(pid)
533 return parentfn(pid)
534 else:
534 else:
535 os._exit(0)
535 os._exit(0)
536
536
537 if initfn:
537 if initfn:
538 initfn()
538 initfn()
539
539
540 if opts['pid_file']:
540 if opts['pid_file']:
541 fp = open(opts['pid_file'], 'w')
541 fp = open(opts['pid_file'], 'w')
542 fp.write(str(os.getpid()) + '\n')
542 fp.write(str(os.getpid()) + '\n')
543 fp.close()
543 fp.close()
544
544
545 if opts['daemon_pipefds']:
545 if opts['daemon_pipefds']:
546 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
546 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
547 os.close(rfd)
547 os.close(rfd)
548 try:
548 try:
549 os.setsid()
549 os.setsid()
550 except AttributeError:
550 except AttributeError:
551 pass
551 pass
552 os.write(wfd, 'y')
552 os.write(wfd, 'y')
553 os.close(wfd)
553 os.close(wfd)
554 sys.stdout.flush()
554 sys.stdout.flush()
555 sys.stderr.flush()
555 sys.stderr.flush()
556 fd = os.open(util.nulldev, os.O_RDWR)
556 fd = os.open(util.nulldev, os.O_RDWR)
557 if fd != 0: os.dup2(fd, 0)
557 if fd != 0: os.dup2(fd, 0)
558 if fd != 1: os.dup2(fd, 1)
558 if fd != 1: os.dup2(fd, 1)
559 if fd != 2: os.dup2(fd, 2)
559 if fd != 2: os.dup2(fd, 2)
560 if fd not in (0, 1, 2): os.close(fd)
560 if fd not in (0, 1, 2): os.close(fd)
561
561
562 if runfn:
562 if runfn:
563 return runfn()
563 return runfn()
564
564
565 class changeset_printer(object):
565 class changeset_printer(object):
566 '''show changeset information when templating not requested.'''
566 '''show changeset information when templating not requested.'''
567
567
568 def __init__(self, ui, repo, patch, buffered):
568 def __init__(self, ui, repo, patch, buffered):
569 self.ui = ui
569 self.ui = ui
570 self.repo = repo
570 self.repo = repo
571 self.buffered = buffered
571 self.buffered = buffered
572 self.patch = patch
572 self.patch = patch
573 self.header = {}
573 self.header = {}
574 self.hunk = {}
574 self.hunk = {}
575 self.lastheader = None
575 self.lastheader = None
576
576
577 def flush(self, rev):
577 def flush(self, rev):
578 if rev in self.header:
578 if rev in self.header:
579 h = self.header[rev]
579 h = self.header[rev]
580 if h != self.lastheader:
580 if h != self.lastheader:
581 self.lastheader = h
581 self.lastheader = h
582 self.ui.write(h)
582 self.ui.write(h)
583 del self.header[rev]
583 del self.header[rev]
584 if rev in self.hunk:
584 if rev in self.hunk:
585 self.ui.write(self.hunk[rev])
585 self.ui.write(self.hunk[rev])
586 del self.hunk[rev]
586 del self.hunk[rev]
587 return 1
587 return 1
588 return 0
588 return 0
589
589
590 def show(self, rev=0, changenode=None, copies=(), **props):
590 def show(self, rev=0, changenode=None, copies=(), **props):
591 if self.buffered:
591 if self.buffered:
592 self.ui.pushbuffer()
592 self.ui.pushbuffer()
593 self._show(rev, changenode, copies, props)
593 self._show(rev, changenode, copies, props)
594 self.hunk[rev] = self.ui.popbuffer()
594 self.hunk[rev] = self.ui.popbuffer()
595 else:
595 else:
596 self._show(rev, changenode, copies, props)
596 self._show(rev, changenode, copies, props)
597
597
598 def _show(self, rev, changenode, copies, props):
598 def _show(self, rev, changenode, copies, props):
599 '''show a single changeset or file revision'''
599 '''show a single changeset or file revision'''
600 log = self.repo.changelog
600 log = self.repo.changelog
601 if changenode is None:
601 if changenode is None:
602 changenode = log.node(rev)
602 changenode = log.node(rev)
603 elif not rev:
603 elif not rev:
604 rev = log.rev(changenode)
604 rev = log.rev(changenode)
605
605
606 if self.ui.quiet:
606 if self.ui.quiet:
607 self.ui.write("%d:%s\n" % (rev, short(changenode)))
607 self.ui.write("%d:%s\n" % (rev, short(changenode)))
608 return
608 return
609
609
610 changes = log.read(changenode)
610 changes = log.read(changenode)
611 date = util.datestr(changes[2])
611 date = util.datestr(changes[2])
612 extra = changes[5]
612 extra = changes[5]
613 branch = extra.get("branch")
613 branch = extra.get("branch")
614
614
615 hexfunc = self.ui.debugflag and hex or short
615 hexfunc = self.ui.debugflag and hex or short
616
616
617 parents = [(p, hexfunc(log.node(p)))
617 parents = [(p, hexfunc(log.node(p)))
618 for p in self._meaningful_parentrevs(log, rev)]
618 for p in self._meaningful_parentrevs(log, rev)]
619
619
620 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
620 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
621
621
622 # don't show the default branch name
622 # don't show the default branch name
623 if branch != 'default':
623 if branch != 'default':
624 branch = util.tolocal(branch)
624 branch = util.tolocal(branch)
625 self.ui.write(_("branch: %s\n") % branch)
625 self.ui.write(_("branch: %s\n") % branch)
626 for tag in self.repo.nodetags(changenode):
626 for tag in self.repo.nodetags(changenode):
627 self.ui.write(_("tag: %s\n") % tag)
627 self.ui.write(_("tag: %s\n") % tag)
628 for parent in parents:
628 for parent in parents:
629 self.ui.write(_("parent: %d:%s\n") % parent)
629 self.ui.write(_("parent: %d:%s\n") % parent)
630
630
631 if self.ui.debugflag:
631 if self.ui.debugflag:
632 self.ui.write(_("manifest: %d:%s\n") %
632 self.ui.write(_("manifest: %d:%s\n") %
633 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
633 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
634 self.ui.write(_("user: %s\n") % changes[1])
634 self.ui.write(_("user: %s\n") % changes[1])
635 self.ui.write(_("date: %s\n") % date)
635 self.ui.write(_("date: %s\n") % date)
636
636
637 if self.ui.debugflag:
637 if self.ui.debugflag:
638 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
638 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
639 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
639 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
640 files):
640 files):
641 if value:
641 if value:
642 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
642 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
643 elif changes[3] and self.ui.verbose:
643 elif changes[3] and self.ui.verbose:
644 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
644 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
645 if copies and self.ui.verbose:
645 if copies and self.ui.verbose:
646 copies = ['%s (%s)' % c for c in copies]
646 copies = ['%s (%s)' % c for c in copies]
647 self.ui.write(_("copies: %s\n") % ' '.join(copies))
647 self.ui.write(_("copies: %s\n") % ' '.join(copies))
648
648
649 if extra and self.ui.debugflag:
649 if extra and self.ui.debugflag:
650 extraitems = extra.items()
650 extraitems = extra.items()
651 extraitems.sort()
651 extraitems.sort()
652 for key, value in extraitems:
652 for key, value in extraitems:
653 self.ui.write(_("extra: %s=%s\n")
653 self.ui.write(_("extra: %s=%s\n")
654 % (key, value.encode('string_escape')))
654 % (key, value.encode('string_escape')))
655
655
656 description = changes[4].strip()
656 description = changes[4].strip()
657 if description:
657 if description:
658 if self.ui.verbose:
658 if self.ui.verbose:
659 self.ui.write(_("description:\n"))
659 self.ui.write(_("description:\n"))
660 self.ui.write(description)
660 self.ui.write(description)
661 self.ui.write("\n\n")
661 self.ui.write("\n\n")
662 else:
662 else:
663 self.ui.write(_("summary: %s\n") %
663 self.ui.write(_("summary: %s\n") %
664 description.splitlines()[0])
664 description.splitlines()[0])
665 self.ui.write("\n")
665 self.ui.write("\n")
666
666
667 self.showpatch(changenode)
667 self.showpatch(changenode)
668
668
669 def showpatch(self, node):
669 def showpatch(self, node):
670 if self.patch:
670 if self.patch:
671 prev = self.repo.changelog.parents(node)[0]
671 prev = self.repo.changelog.parents(node)[0]
672 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui,
672 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui,
673 opts=patch.diffopts(self.ui))
673 opts=patch.diffopts(self.ui))
674 self.ui.write("\n")
674 self.ui.write("\n")
675
675
676 def _meaningful_parentrevs(self, log, rev):
676 def _meaningful_parentrevs(self, log, rev):
677 """Return list of meaningful (or all if debug) parentrevs for rev.
677 """Return list of meaningful (or all if debug) parentrevs for rev.
678
678
679 For merges (two non-nullrev revisions) both parents are meaningful.
679 For merges (two non-nullrev revisions) both parents are meaningful.
680 Otherwise the first parent revision is considered meaningful if it
680 Otherwise the first parent revision is considered meaningful if it
681 is not the preceding revision.
681 is not the preceding revision.
682 """
682 """
683 parents = log.parentrevs(rev)
683 parents = log.parentrevs(rev)
684 if not self.ui.debugflag and parents[1] == nullrev:
684 if not self.ui.debugflag and parents[1] == nullrev:
685 if parents[0] >= rev - 1:
685 if parents[0] >= rev - 1:
686 parents = []
686 parents = []
687 else:
687 else:
688 parents = [parents[0]]
688 parents = [parents[0]]
689 return parents
689 return parents
690
690
691
691
692 class changeset_templater(changeset_printer):
692 class changeset_templater(changeset_printer):
693 '''format changeset information.'''
693 '''format changeset information.'''
694
694
695 def __init__(self, ui, repo, patch, mapfile, buffered):
695 def __init__(self, ui, repo, patch, mapfile, buffered):
696 changeset_printer.__init__(self, ui, repo, patch, buffered)
696 changeset_printer.__init__(self, ui, repo, patch, buffered)
697 filters = templatefilters.filters.copy()
697 filters = templatefilters.filters.copy()
698 filters['formatnode'] = (ui.debugflag and (lambda x: x)
698 filters['formatnode'] = (ui.debugflag and (lambda x: x)
699 or (lambda x: x[:12]))
699 or (lambda x: x[:12]))
700 self.t = templater.templater(mapfile, filters,
700 self.t = templater.templater(mapfile, filters,
701 cache={
701 cache={
702 'parent': '{rev}:{node|formatnode} ',
702 'parent': '{rev}:{node|formatnode} ',
703 'manifest': '{rev}:{node|formatnode}',
703 'manifest': '{rev}:{node|formatnode}',
704 'filecopy': '{name} ({source})'})
704 'filecopy': '{name} ({source})'})
705
705
706 def use_template(self, t):
706 def use_template(self, t):
707 '''set template string to use'''
707 '''set template string to use'''
708 self.t.cache['changeset'] = t
708 self.t.cache['changeset'] = t
709
709
710 def _show(self, rev, changenode, copies, props):
710 def _show(self, rev, changenode, copies, props):
711 '''show a single changeset or file revision'''
711 '''show a single changeset or file revision'''
712 log = self.repo.changelog
712 log = self.repo.changelog
713 if changenode is None:
713 if changenode is None:
714 changenode = log.node(rev)
714 changenode = log.node(rev)
715 elif not rev:
715 elif not rev:
716 rev = log.rev(changenode)
716 rev = log.rev(changenode)
717
717
718 changes = log.read(changenode)
718 changes = log.read(changenode)
719
719
720 def showlist(name, values, plural=None, **args):
720 def showlist(name, values, plural=None, **args):
721 '''expand set of values.
721 '''expand set of values.
722 name is name of key in template map.
722 name is name of key in template map.
723 values is list of strings or dicts.
723 values is list of strings or dicts.
724 plural is plural of name, if not simply name + 's'.
724 plural is plural of name, if not simply name + 's'.
725
725
726 expansion works like this, given name 'foo'.
726 expansion works like this, given name 'foo'.
727
727
728 if values is empty, expand 'no_foos'.
728 if values is empty, expand 'no_foos'.
729
729
730 if 'foo' not in template map, return values as a string,
730 if 'foo' not in template map, return values as a string,
731 joined by space.
731 joined by space.
732
732
733 expand 'start_foos'.
733 expand 'start_foos'.
734
734
735 for each value, expand 'foo'. if 'last_foo' in template
735 for each value, expand 'foo'. if 'last_foo' in template
736 map, expand it instead of 'foo' for last key.
736 map, expand it instead of 'foo' for last key.
737
737
738 expand 'end_foos'.
738 expand 'end_foos'.
739 '''
739 '''
740 if plural: names = plural
740 if plural: names = plural
741 else: names = name + 's'
741 else: names = name + 's'
742 if not values:
742 if not values:
743 noname = 'no_' + names
743 noname = 'no_' + names
744 if noname in self.t:
744 if noname in self.t:
745 yield self.t(noname, **args)
745 yield self.t(noname, **args)
746 return
746 return
747 if name not in self.t:
747 if name not in self.t:
748 if isinstance(values[0], str):
748 if isinstance(values[0], str):
749 yield ' '.join(values)
749 yield ' '.join(values)
750 else:
750 else:
751 for v in values:
751 for v in values:
752 yield dict(v, **args)
752 yield dict(v, **args)
753 return
753 return
754 startname = 'start_' + names
754 startname = 'start_' + names
755 if startname in self.t:
755 if startname in self.t:
756 yield self.t(startname, **args)
756 yield self.t(startname, **args)
757 vargs = args.copy()
757 vargs = args.copy()
758 def one(v, tag=name):
758 def one(v, tag=name):
759 try:
759 try:
760 vargs.update(v)
760 vargs.update(v)
761 except (AttributeError, ValueError):
761 except (AttributeError, ValueError):
762 try:
762 try:
763 for a, b in v:
763 for a, b in v:
764 vargs[a] = b
764 vargs[a] = b
765 except ValueError:
765 except ValueError:
766 vargs[name] = v
766 vargs[name] = v
767 return self.t(tag, **vargs)
767 return self.t(tag, **vargs)
768 lastname = 'last_' + name
768 lastname = 'last_' + name
769 if lastname in self.t:
769 if lastname in self.t:
770 last = values.pop()
770 last = values.pop()
771 else:
771 else:
772 last = None
772 last = None
773 for v in values:
773 for v in values:
774 yield one(v)
774 yield one(v)
775 if last is not None:
775 if last is not None:
776 yield one(last, tag=lastname)
776 yield one(last, tag=lastname)
777 endname = 'end_' + names
777 endname = 'end_' + names
778 if endname in self.t:
778 if endname in self.t:
779 yield self.t(endname, **args)
779 yield self.t(endname, **args)
780
780
781 def showbranches(**args):
781 def showbranches(**args):
782 branch = changes[5].get("branch")
782 branch = changes[5].get("branch")
783 if branch != 'default':
783 if branch != 'default':
784 branch = util.tolocal(branch)
784 branch = util.tolocal(branch)
785 return showlist('branch', [branch], plural='branches', **args)
785 return showlist('branch', [branch], plural='branches', **args)
786
786
787 def showparents(**args):
787 def showparents(**args):
788 parents = [[('rev', p), ('node', hex(log.node(p)))]
788 parents = [[('rev', p), ('node', hex(log.node(p)))]
789 for p in self._meaningful_parentrevs(log, rev)]
789 for p in self._meaningful_parentrevs(log, rev)]
790 return showlist('parent', parents, **args)
790 return showlist('parent', parents, **args)
791
791
792 def showtags(**args):
792 def showtags(**args):
793 return showlist('tag', self.repo.nodetags(changenode), **args)
793 return showlist('tag', self.repo.nodetags(changenode), **args)
794
794
795 def showextras(**args):
795 def showextras(**args):
796 extras = changes[5].items()
796 extras = changes[5].items()
797 extras.sort()
797 extras.sort()
798 for key, value in extras:
798 for key, value in extras:
799 args = args.copy()
799 args = args.copy()
800 args.update(dict(key=key, value=value))
800 args.update(dict(key=key, value=value))
801 yield self.t('extra', **args)
801 yield self.t('extra', **args)
802
802
803 def showcopies(**args):
803 def showcopies(**args):
804 c = [{'name': x[0], 'source': x[1]} for x in copies]
804 c = [{'name': x[0], 'source': x[1]} for x in copies]
805 return showlist('file_copy', c, plural='file_copies', **args)
805 return showlist('file_copy', c, plural='file_copies', **args)
806
806
807 files = []
807 files = []
808 def getfiles():
808 def getfiles():
809 if not files:
809 if not files:
810 files[:] = self.repo.status(
810 files[:] = self.repo.status(
811 log.parents(changenode)[0], changenode)[:3]
811 log.parents(changenode)[0], changenode)[:3]
812 return files
812 return files
813 def showfiles(**args):
813 def showfiles(**args):
814 return showlist('file', changes[3], **args)
814 return showlist('file', changes[3], **args)
815 def showmods(**args):
815 def showmods(**args):
816 return showlist('file_mod', getfiles()[0], **args)
816 return showlist('file_mod', getfiles()[0], **args)
817 def showadds(**args):
817 def showadds(**args):
818 return showlist('file_add', getfiles()[1], **args)
818 return showlist('file_add', getfiles()[1], **args)
819 def showdels(**args):
819 def showdels(**args):
820 return showlist('file_del', getfiles()[2], **args)
820 return showlist('file_del', getfiles()[2], **args)
821 def showmanifest(**args):
821 def showmanifest(**args):
822 args = args.copy()
822 args = args.copy()
823 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
823 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
824 node=hex(changes[0])))
824 node=hex(changes[0])))
825 return self.t('manifest', **args)
825 return self.t('manifest', **args)
826
826
827 defprops = {
827 defprops = {
828 'author': changes[1],
828 'author': changes[1],
829 'branches': showbranches,
829 'branches': showbranches,
830 'date': changes[2],
830 'date': changes[2],
831 'desc': changes[4].strip(),
831 'desc': changes[4].strip(),
832 'file_adds': showadds,
832 'file_adds': showadds,
833 'file_dels': showdels,
833 'file_dels': showdels,
834 'file_mods': showmods,
834 'file_mods': showmods,
835 'files': showfiles,
835 'files': showfiles,
836 'file_copies': showcopies,
836 'file_copies': showcopies,
837 'manifest': showmanifest,
837 'manifest': showmanifest,
838 'node': hex(changenode),
838 'node': hex(changenode),
839 'parents': showparents,
839 'parents': showparents,
840 'rev': rev,
840 'rev': rev,
841 'tags': showtags,
841 'tags': showtags,
842 'extras': showextras,
842 'extras': showextras,
843 }
843 }
844 props = props.copy()
844 props = props.copy()
845 props.update(defprops)
845 props.update(defprops)
846
846
847 try:
847 try:
848 if self.ui.debugflag and 'header_debug' in self.t:
848 if self.ui.debugflag and 'header_debug' in self.t:
849 key = 'header_debug'
849 key = 'header_debug'
850 elif self.ui.quiet and 'header_quiet' in self.t:
850 elif self.ui.quiet and 'header_quiet' in self.t:
851 key = 'header_quiet'
851 key = 'header_quiet'
852 elif self.ui.verbose and 'header_verbose' in self.t:
852 elif self.ui.verbose and 'header_verbose' in self.t:
853 key = 'header_verbose'
853 key = 'header_verbose'
854 elif 'header' in self.t:
854 elif 'header' in self.t:
855 key = 'header'
855 key = 'header'
856 else:
856 else:
857 key = ''
857 key = ''
858 if key:
858 if key:
859 h = templater.stringify(self.t(key, **props))
859 h = templater.stringify(self.t(key, **props))
860 if self.buffered:
860 if self.buffered:
861 self.header[rev] = h
861 self.header[rev] = h
862 else:
862 else:
863 self.ui.write(h)
863 self.ui.write(h)
864 if self.ui.debugflag and 'changeset_debug' in self.t:
864 if self.ui.debugflag and 'changeset_debug' in self.t:
865 key = 'changeset_debug'
865 key = 'changeset_debug'
866 elif self.ui.quiet and 'changeset_quiet' in self.t:
866 elif self.ui.quiet and 'changeset_quiet' in self.t:
867 key = 'changeset_quiet'
867 key = 'changeset_quiet'
868 elif self.ui.verbose and 'changeset_verbose' in self.t:
868 elif self.ui.verbose and 'changeset_verbose' in self.t:
869 key = 'changeset_verbose'
869 key = 'changeset_verbose'
870 else:
870 else:
871 key = 'changeset'
871 key = 'changeset'
872 self.ui.write(templater.stringify(self.t(key, **props)))
872 self.ui.write(templater.stringify(self.t(key, **props)))
873 self.showpatch(changenode)
873 self.showpatch(changenode)
874 except KeyError, inst:
874 except KeyError, inst:
875 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
875 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
876 inst.args[0]))
876 inst.args[0]))
877 except SyntaxError, inst:
877 except SyntaxError, inst:
878 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
878 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
879
879
880 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
880 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
881 """show one changeset using template or regular display.
881 """show one changeset using template or regular display.
882
882
883 Display format will be the first non-empty hit of:
883 Display format will be the first non-empty hit of:
884 1. option 'template'
884 1. option 'template'
885 2. option 'style'
885 2. option 'style'
886 3. [ui] setting 'logtemplate'
886 3. [ui] setting 'logtemplate'
887 4. [ui] setting 'style'
887 4. [ui] setting 'style'
888 If all of these values are either the unset or the empty string,
888 If all of these values are either the unset or the empty string,
889 regular display via changeset_printer() is done.
889 regular display via changeset_printer() is done.
890 """
890 """
891 # options
891 # options
892 patch = False
892 patch = False
893 if opts.get('patch'):
893 if opts.get('patch'):
894 patch = matchfn or matchall(repo)
894 patch = matchfn or matchall(repo)
895
895
896 tmpl = opts.get('template')
896 tmpl = opts.get('template')
897 mapfile = None
897 mapfile = None
898 if tmpl:
898 if tmpl:
899 tmpl = templater.parsestring(tmpl, quoted=False)
899 tmpl = templater.parsestring(tmpl, quoted=False)
900 else:
900 else:
901 mapfile = opts.get('style')
901 mapfile = opts.get('style')
902 # ui settings
902 # ui settings
903 if not mapfile:
903 if not mapfile:
904 tmpl = ui.config('ui', 'logtemplate')
904 tmpl = ui.config('ui', 'logtemplate')
905 if tmpl:
905 if tmpl:
906 tmpl = templater.parsestring(tmpl)
906 tmpl = templater.parsestring(tmpl)
907 else:
907 else:
908 mapfile = ui.config('ui', 'style')
908 mapfile = ui.config('ui', 'style')
909
909
910 if tmpl or mapfile:
910 if tmpl or mapfile:
911 if mapfile:
911 if mapfile:
912 if not os.path.split(mapfile)[0]:
912 if not os.path.split(mapfile)[0]:
913 mapname = (templater.templatepath('map-cmdline.' + mapfile)
913 mapname = (templater.templatepath('map-cmdline.' + mapfile)
914 or templater.templatepath(mapfile))
914 or templater.templatepath(mapfile))
915 if mapname: mapfile = mapname
915 if mapname: mapfile = mapname
916 try:
916 try:
917 t = changeset_templater(ui, repo, patch, mapfile, buffered)
917 t = changeset_templater(ui, repo, patch, mapfile, buffered)
918 except SyntaxError, inst:
918 except SyntaxError, inst:
919 raise util.Abort(inst.args[0])
919 raise util.Abort(inst.args[0])
920 if tmpl: t.use_template(tmpl)
920 if tmpl: t.use_template(tmpl)
921 return t
921 return t
922 return changeset_printer(ui, repo, patch, buffered)
922 return changeset_printer(ui, repo, patch, buffered)
923
923
924 def finddate(ui, repo, date):
924 def finddate(ui, repo, date):
925 """Find the tipmost changeset that matches the given date spec"""
925 """Find the tipmost changeset that matches the given date spec"""
926 df = util.matchdate(date)
926 df = util.matchdate(date)
927 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
927 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
928 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
928 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
929 results = {}
929 results = {}
930 for st, rev, fns in changeiter:
930 for st, rev, fns in changeiter:
931 if st == 'add':
931 if st == 'add':
932 d = get(rev)[2]
932 d = get(rev)[2]
933 if df(d[0]):
933 if df(d[0]):
934 results[rev] = d
934 results[rev] = d
935 elif st == 'iter':
935 elif st == 'iter':
936 if rev in results:
936 if rev in results:
937 ui.status("Found revision %s from %s\n" %
937 ui.status("Found revision %s from %s\n" %
938 (rev, util.datestr(results[rev])))
938 (rev, util.datestr(results[rev])))
939 return str(rev)
939 return str(rev)
940
940
941 raise util.Abort(_("revision matching date not found"))
941 raise util.Abort(_("revision matching date not found"))
942
942
943 def walkchangerevs(ui, repo, pats, change, opts):
943 def walkchangerevs(ui, repo, pats, change, opts):
944 '''Iterate over files and the revs they changed in.
944 '''Iterate over files and the revs they changed in.
945
945
946 Callers most commonly need to iterate backwards over the history
946 Callers most commonly need to iterate backwards over the history
947 it is interested in. Doing so has awful (quadratic-looking)
947 it is interested in. Doing so has awful (quadratic-looking)
948 performance, so we use iterators in a "windowed" way.
948 performance, so we use iterators in a "windowed" way.
949
949
950 We walk a window of revisions in the desired order. Within the
950 We walk a window of revisions in the desired order. Within the
951 window, we first walk forwards to gather data, then in the desired
951 window, we first walk forwards to gather data, then in the desired
952 order (usually backwards) to display it.
952 order (usually backwards) to display it.
953
953
954 This function returns an (iterator, matchfn) tuple. The iterator
954 This function returns an (iterator, matchfn) tuple. The iterator
955 yields 3-tuples. They will be of one of the following forms:
955 yields 3-tuples. They will be of one of the following forms:
956
956
957 "window", incrementing, lastrev: stepping through a window,
957 "window", incrementing, lastrev: stepping through a window,
958 positive if walking forwards through revs, last rev in the
958 positive if walking forwards through revs, last rev in the
959 sequence iterated over - use to reset state for the current window
959 sequence iterated over - use to reset state for the current window
960
960
961 "add", rev, fns: out-of-order traversal of the given file names
961 "add", rev, fns: out-of-order traversal of the given file names
962 fns, which changed during revision rev - use to gather data for
962 fns, which changed during revision rev - use to gather data for
963 possible display
963 possible display
964
964
965 "iter", rev, None: in-order traversal of the revs earlier iterated
965 "iter", rev, None: in-order traversal of the revs earlier iterated
966 over with "add" - use to display data'''
966 over with "add" - use to display data'''
967
967
968 def increasing_windows(start, end, windowsize=8, sizelimit=512):
968 def increasing_windows(start, end, windowsize=8, sizelimit=512):
969 if start < end:
969 if start < end:
970 while start < end:
970 while start < end:
971 yield start, min(windowsize, end-start)
971 yield start, min(windowsize, end-start)
972 start += windowsize
972 start += windowsize
973 if windowsize < sizelimit:
973 if windowsize < sizelimit:
974 windowsize *= 2
974 windowsize *= 2
975 else:
975 else:
976 while start > end:
976 while start > end:
977 yield start, min(windowsize, start-end-1)
977 yield start, min(windowsize, start-end-1)
978 start -= windowsize
978 start -= windowsize
979 if windowsize < sizelimit:
979 if windowsize < sizelimit:
980 windowsize *= 2
980 windowsize *= 2
981
981
982 m = match(repo, pats, opts)
982 m = match(repo, pats, opts)
983 follow = opts.get('follow') or opts.get('follow_first')
983 follow = opts.get('follow') or opts.get('follow_first')
984
984
985 if repo.changelog.count() == 0:
985 if repo.changelog.count() == 0:
986 return [], m
986 return [], m
987
987
988 if follow:
988 if follow:
989 defrange = '%s:0' % repo.changectx().rev()
989 defrange = '%s:0' % repo.changectx().rev()
990 else:
990 else:
991 defrange = '-1:0'
991 defrange = '-1:0'
992 revs = revrange(repo, opts['rev'] or [defrange])
992 revs = revrange(repo, opts['rev'] or [defrange])
993 wanted = {}
993 wanted = {}
994 slowpath = m.anypats() or opts.get('removed')
994 slowpath = m.anypats() or opts.get('removed')
995 fncache = {}
995 fncache = {}
996
996
997 if not slowpath and not m.files():
997 if not slowpath and not m.files():
998 # No files, no patterns. Display all revs.
998 # No files, no patterns. Display all revs.
999 wanted = dict.fromkeys(revs)
999 wanted = dict.fromkeys(revs)
1000 copies = []
1000 copies = []
1001 if not slowpath:
1001 if not slowpath:
1002 # Only files, no patterns. Check the history of each file.
1002 # Only files, no patterns. Check the history of each file.
1003 def filerevgen(filelog, node):
1003 def filerevgen(filelog, node):
1004 cl_count = repo.changelog.count()
1004 cl_count = repo.changelog.count()
1005 if node is None:
1005 if node is None:
1006 last = filelog.count() - 1
1006 last = filelog.count() - 1
1007 else:
1007 else:
1008 last = filelog.rev(node)
1008 last = filelog.rev(node)
1009 for i, window in increasing_windows(last, nullrev):
1009 for i, window in increasing_windows(last, nullrev):
1010 revs = []
1010 revs = []
1011 for j in xrange(i - window, i + 1):
1011 for j in xrange(i - window, i + 1):
1012 n = filelog.node(j)
1012 n = filelog.node(j)
1013 revs.append((filelog.linkrev(n),
1013 revs.append((filelog.linkrev(n),
1014 follow and filelog.renamed(n)))
1014 follow and filelog.renamed(n)))
1015 revs.reverse()
1015 revs.reverse()
1016 for rev in revs:
1016 for rev in revs:
1017 # only yield rev for which we have the changelog, it can
1017 # only yield rev for which we have the changelog, it can
1018 # happen while doing "hg log" during a pull or commit
1018 # happen while doing "hg log" during a pull or commit
1019 if rev[0] < cl_count:
1019 if rev[0] < cl_count:
1020 yield rev
1020 yield rev
1021 def iterfiles():
1021 def iterfiles():
1022 for filename in m.files():
1022 for filename in m.files():
1023 yield filename, None
1023 yield filename, None
1024 for filename_node in copies:
1024 for filename_node in copies:
1025 yield filename_node
1025 yield filename_node
1026 minrev, maxrev = min(revs), max(revs)
1026 minrev, maxrev = min(revs), max(revs)
1027 for file_, node in iterfiles():
1027 for file_, node in iterfiles():
1028 filelog = repo.file(file_)
1028 filelog = repo.file(file_)
1029 if filelog.count() == 0:
1029 if filelog.count() == 0:
1030 if node is None:
1030 if node is None:
1031 # A zero count may be a directory or deleted file, so
1031 # A zero count may be a directory or deleted file, so
1032 # try to find matching entries on the slow path.
1032 # try to find matching entries on the slow path.
1033 slowpath = True
1033 slowpath = True
1034 break
1034 break
1035 else:
1035 else:
1036 ui.warn(_('%s:%s copy source revision cannot be found!\n')
1036 ui.warn(_('%s:%s copy source revision cannot be found!\n')
1037 % (file_, short(node)))
1037 % (file_, short(node)))
1038 continue
1038 continue
1039 for rev, copied in filerevgen(filelog, node):
1039 for rev, copied in filerevgen(filelog, node):
1040 if rev <= maxrev:
1040 if rev <= maxrev:
1041 if rev < minrev:
1041 if rev < minrev:
1042 break
1042 break
1043 fncache.setdefault(rev, [])
1043 fncache.setdefault(rev, [])
1044 fncache[rev].append(file_)
1044 fncache[rev].append(file_)
1045 wanted[rev] = 1
1045 wanted[rev] = 1
1046 if follow and copied:
1046 if follow and copied:
1047 copies.append(copied)
1047 copies.append(copied)
1048 if slowpath:
1048 if slowpath:
1049 if follow:
1049 if follow:
1050 raise util.Abort(_('can only follow copies/renames for explicit '
1050 raise util.Abort(_('can only follow copies/renames for explicit '
1051 'file names'))
1051 'file names'))
1052
1052
1053 # The slow path checks files modified in every changeset.
1053 # The slow path checks files modified in every changeset.
1054 def changerevgen():
1054 def changerevgen():
1055 for i, window in increasing_windows(repo.changelog.count()-1,
1055 for i, window in increasing_windows(repo.changelog.count()-1,
1056 nullrev):
1056 nullrev):
1057 for j in xrange(i - window, i + 1):
1057 for j in xrange(i - window, i + 1):
1058 yield j, change(j)[3]
1058 yield j, change(j)[3]
1059
1059
1060 for rev, changefiles in changerevgen():
1060 for rev, changefiles in changerevgen():
1061 matches = filter(m, changefiles)
1061 matches = filter(m, changefiles)
1062 if matches:
1062 if matches:
1063 fncache[rev] = matches
1063 fncache[rev] = matches
1064 wanted[rev] = 1
1064 wanted[rev] = 1
1065
1065
1066 class followfilter:
1066 class followfilter:
1067 def __init__(self, onlyfirst=False):
1067 def __init__(self, onlyfirst=False):
1068 self.startrev = nullrev
1068 self.startrev = nullrev
1069 self.roots = []
1069 self.roots = []
1070 self.onlyfirst = onlyfirst
1070 self.onlyfirst = onlyfirst
1071
1071
1072 def match(self, rev):
1072 def match(self, rev):
1073 def realparents(rev):
1073 def realparents(rev):
1074 if self.onlyfirst:
1074 if self.onlyfirst:
1075 return repo.changelog.parentrevs(rev)[0:1]
1075 return repo.changelog.parentrevs(rev)[0:1]
1076 else:
1076 else:
1077 return filter(lambda x: x != nullrev,
1077 return filter(lambda x: x != nullrev,
1078 repo.changelog.parentrevs(rev))
1078 repo.changelog.parentrevs(rev))
1079
1079
1080 if self.startrev == nullrev:
1080 if self.startrev == nullrev:
1081 self.startrev = rev
1081 self.startrev = rev
1082 return True
1082 return True
1083
1083
1084 if rev > self.startrev:
1084 if rev > self.startrev:
1085 # forward: all descendants
1085 # forward: all descendants
1086 if not self.roots:
1086 if not self.roots:
1087 self.roots.append(self.startrev)
1087 self.roots.append(self.startrev)
1088 for parent in realparents(rev):
1088 for parent in realparents(rev):
1089 if parent in self.roots:
1089 if parent in self.roots:
1090 self.roots.append(rev)
1090 self.roots.append(rev)
1091 return True
1091 return True
1092 else:
1092 else:
1093 # backwards: all parents
1093 # backwards: all parents
1094 if not self.roots:
1094 if not self.roots:
1095 self.roots.extend(realparents(self.startrev))
1095 self.roots.extend(realparents(self.startrev))
1096 if rev in self.roots:
1096 if rev in self.roots:
1097 self.roots.remove(rev)
1097 self.roots.remove(rev)
1098 self.roots.extend(realparents(rev))
1098 self.roots.extend(realparents(rev))
1099 return True
1099 return True
1100
1100
1101 return False
1101 return False
1102
1102
1103 # it might be worthwhile to do this in the iterator if the rev range
1103 # it might be worthwhile to do this in the iterator if the rev range
1104 # is descending and the prune args are all within that range
1104 # is descending and the prune args are all within that range
1105 for rev in opts.get('prune', ()):
1105 for rev in opts.get('prune', ()):
1106 rev = repo.changelog.rev(repo.lookup(rev))
1106 rev = repo.changelog.rev(repo.lookup(rev))
1107 ff = followfilter()
1107 ff = followfilter()
1108 stop = min(revs[0], revs[-1])
1108 stop = min(revs[0], revs[-1])
1109 for x in xrange(rev, stop-1, -1):
1109 for x in xrange(rev, stop-1, -1):
1110 if ff.match(x) and x in wanted:
1110 if ff.match(x) and x in wanted:
1111 del wanted[x]
1111 del wanted[x]
1112
1112
1113 def iterate():
1113 def iterate():
1114 if follow and not m.files():
1114 if follow and not m.files():
1115 ff = followfilter(onlyfirst=opts.get('follow_first'))
1115 ff = followfilter(onlyfirst=opts.get('follow_first'))
1116 def want(rev):
1116 def want(rev):
1117 if ff.match(rev) and rev in wanted:
1117 if ff.match(rev) and rev in wanted:
1118 return True
1118 return True
1119 return False
1119 return False
1120 else:
1120 else:
1121 def want(rev):
1121 def want(rev):
1122 return rev in wanted
1122 return rev in wanted
1123
1123
1124 for i, window in increasing_windows(0, len(revs)):
1124 for i, window in increasing_windows(0, len(revs)):
1125 yield 'window', revs[0] < revs[-1], revs[-1]
1125 yield 'window', revs[0] < revs[-1], revs[-1]
1126 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1126 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1127 srevs = list(nrevs)
1127 srevs = list(nrevs)
1128 srevs.sort()
1128 srevs.sort()
1129 for rev in srevs:
1129 for rev in srevs:
1130 fns = fncache.get(rev)
1130 fns = fncache.get(rev)
1131 if not fns:
1131 if not fns:
1132 def fns_generator():
1132 def fns_generator():
1133 for f in change(rev)[3]:
1133 for f in change(rev)[3]:
1134 if m(f):
1134 if m(f):
1135 yield f
1135 yield f
1136 fns = fns_generator()
1136 fns = fns_generator()
1137 yield 'add', rev, fns
1137 yield 'add', rev, fns
1138 for rev in nrevs:
1138 for rev in nrevs:
1139 yield 'iter', rev, None
1139 yield 'iter', rev, None
1140 return iterate(), m
1140 return iterate(), m
1141
1141
1142 def commit(ui, repo, commitfunc, pats, opts):
1142 def commit(ui, repo, commitfunc, pats, opts):
1143 '''commit the specified files or all outstanding changes'''
1143 '''commit the specified files or all outstanding changes'''
1144 date = opts.get('date')
1144 date = opts.get('date')
1145 if date:
1145 if date:
1146 opts['date'] = util.parsedate(date)
1146 opts['date'] = util.parsedate(date)
1147 message = logmessage(opts)
1147 message = logmessage(opts)
1148
1148
1149 # extract addremove carefully -- this function can be called from a command
1149 # extract addremove carefully -- this function can be called from a command
1150 # that doesn't support addremove
1150 # that doesn't support addremove
1151 if opts.get('addremove'):
1151 if opts.get('addremove'):
1152 addremove(repo, pats, opts)
1152 addremove(repo, pats, opts)
1153
1153
1154 m = match(repo, pats, opts)
1154 m = match(repo, pats, opts)
1155 if pats:
1155 if pats:
1156 status = repo.status(files=m.files(), match=m)
1156 status = repo.status(match=m)
1157 modified, added, removed, deleted, unknown = status[:5]
1157 modified, added, removed, deleted, unknown = status[:5]
1158 files = modified + added + removed
1158 files = modified + added + removed
1159 slist = None
1159 slist = None
1160 for f in m.files():
1160 for f in m.files():
1161 if f == '.':
1161 if f == '.':
1162 continue
1162 continue
1163 if f not in files:
1163 if f not in files:
1164 rf = repo.wjoin(f)
1164 rf = repo.wjoin(f)
1165 rel = repo.pathto(f)
1165 rel = repo.pathto(f)
1166 try:
1166 try:
1167 mode = os.lstat(rf)[stat.ST_MODE]
1167 mode = os.lstat(rf)[stat.ST_MODE]
1168 except OSError:
1168 except OSError:
1169 raise util.Abort(_("file %s not found!") % rel)
1169 raise util.Abort(_("file %s not found!") % rel)
1170 if stat.S_ISDIR(mode):
1170 if stat.S_ISDIR(mode):
1171 name = f + '/'
1171 name = f + '/'
1172 if slist is None:
1172 if slist is None:
1173 slist = list(files)
1173 slist = list(files)
1174 slist.sort()
1174 slist.sort()
1175 i = bisect.bisect(slist, name)
1175 i = bisect.bisect(slist, name)
1176 if i >= len(slist) or not slist[i].startswith(name):
1176 if i >= len(slist) or not slist[i].startswith(name):
1177 raise util.Abort(_("no match under directory %s!")
1177 raise util.Abort(_("no match under directory %s!")
1178 % rel)
1178 % rel)
1179 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
1179 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
1180 raise util.Abort(_("can't commit %s: "
1180 raise util.Abort(_("can't commit %s: "
1181 "unsupported file type!") % rel)
1181 "unsupported file type!") % rel)
1182 elif f not in repo.dirstate:
1182 elif f not in repo.dirstate:
1183 raise util.Abort(_("file %s not tracked!") % rel)
1183 raise util.Abort(_("file %s not tracked!") % rel)
1184 m = matchfiles(repo, files)
1184 m = matchfiles(repo, files)
1185 try:
1185 try:
1186 return commitfunc(ui, repo, message, m, opts)
1186 return commitfunc(ui, repo, message, m, opts)
1187 except ValueError, inst:
1187 except ValueError, inst:
1188 raise util.Abort(str(inst))
1188 raise util.Abort(str(inst))
@@ -1,3327 +1,3327 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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from repo import RepoError, NoCapability
9 from repo import RepoError, NoCapability
10 from i18n import _
10 from i18n import _
11 import os, re, sys, urllib
11 import os, re, sys, urllib
12 import hg, util, revlog, bundlerepo, extensions, copies
12 import hg, util, revlog, bundlerepo, extensions, copies
13 import difflib, patch, time, help, mdiff, tempfile
13 import difflib, patch, time, help, mdiff, tempfile
14 import version, socket
14 import version, socket
15 import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
15 import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
16 import merge as merge_
16 import merge as merge_
17
17
18 # Commands start here, listed alphabetically
18 # Commands start here, listed alphabetically
19
19
20 def add(ui, repo, *pats, **opts):
20 def add(ui, repo, *pats, **opts):
21 """add the specified files on the next commit
21 """add the specified files on the next commit
22
22
23 Schedule files to be version controlled and added to the repository.
23 Schedule files to be version controlled and added to the repository.
24
24
25 The files will be added to the repository at the next commit. To
25 The files will be added to the repository at the next commit. To
26 undo an add before that, see hg revert.
26 undo an add before that, see hg revert.
27
27
28 If no names are given, add all files in the repository.
28 If no names are given, add all files in the repository.
29 """
29 """
30
30
31 rejected = None
31 rejected = None
32 exacts = {}
32 exacts = {}
33 names = []
33 names = []
34 m = cmdutil.match(repo, pats, opts)
34 m = cmdutil.match(repo, pats, opts)
35 m.bad = lambda x,y: True
35 m.bad = lambda x,y: True
36 for abs in repo.walk(m):
36 for abs in repo.walk(m):
37 if m.exact(abs):
37 if m.exact(abs):
38 if ui.verbose:
38 if ui.verbose:
39 ui.status(_('adding %s\n') % m.rel(abs))
39 ui.status(_('adding %s\n') % m.rel(abs))
40 names.append(abs)
40 names.append(abs)
41 exacts[abs] = 1
41 exacts[abs] = 1
42 elif abs not in repo.dirstate:
42 elif abs not in repo.dirstate:
43 ui.status(_('adding %s\n') % m.rel(abs))
43 ui.status(_('adding %s\n') % m.rel(abs))
44 names.append(abs)
44 names.append(abs)
45 if not opts.get('dry_run'):
45 if not opts.get('dry_run'):
46 rejected = repo.add(names)
46 rejected = repo.add(names)
47 rejected = [p for p in rejected if p in exacts]
47 rejected = [p for p in rejected if p in exacts]
48 return rejected and 1 or 0
48 return rejected and 1 or 0
49
49
50 def addremove(ui, repo, *pats, **opts):
50 def addremove(ui, repo, *pats, **opts):
51 """add all new files, delete all missing files
51 """add all new files, delete all missing files
52
52
53 Add all new files and remove all missing files from the repository.
53 Add all new files and remove all missing files from the repository.
54
54
55 New files are ignored if they match any of the patterns in .hgignore. As
55 New files are ignored if they match any of the patterns in .hgignore. As
56 with add, these changes take effect at the next commit.
56 with add, these changes take effect at the next commit.
57
57
58 Use the -s option to detect renamed files. With a parameter > 0,
58 Use the -s option to detect renamed files. With a parameter > 0,
59 this compares every removed file with every added file and records
59 this compares every removed file with every added file and records
60 those similar enough as renames. This option takes a percentage
60 those similar enough as renames. This option takes a percentage
61 between 0 (disabled) and 100 (files must be identical) as its
61 between 0 (disabled) and 100 (files must be identical) as its
62 parameter. Detecting renamed files this way can be expensive.
62 parameter. Detecting renamed files this way can be expensive.
63 """
63 """
64 try:
64 try:
65 sim = float(opts.get('similarity') or 0)
65 sim = float(opts.get('similarity') or 0)
66 except ValueError:
66 except ValueError:
67 raise util.Abort(_('similarity must be a number'))
67 raise util.Abort(_('similarity must be a number'))
68 if sim < 0 or sim > 100:
68 if sim < 0 or sim > 100:
69 raise util.Abort(_('similarity must be between 0 and 100'))
69 raise util.Abort(_('similarity must be between 0 and 100'))
70 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
70 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
71
71
72 def annotate(ui, repo, *pats, **opts):
72 def annotate(ui, repo, *pats, **opts):
73 """show changeset information per file line
73 """show changeset information per file line
74
74
75 List changes in files, showing the revision id responsible for each line
75 List changes in files, showing the revision id responsible for each line
76
76
77 This command is useful to discover who did a change or when a change took
77 This command is useful to discover who did a change or when a change took
78 place.
78 place.
79
79
80 Without the -a option, annotate will avoid processing files it
80 Without the -a option, annotate will avoid processing files it
81 detects as binary. With -a, annotate will generate an annotation
81 detects as binary. With -a, annotate will generate an annotation
82 anyway, probably with undesirable results.
82 anyway, probably with undesirable results.
83 """
83 """
84 datefunc = ui.quiet and util.shortdate or util.datestr
84 datefunc = ui.quiet and util.shortdate or util.datestr
85 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
85 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
86
86
87 if not pats:
87 if not pats:
88 raise util.Abort(_('at least one file name or pattern required'))
88 raise util.Abort(_('at least one file name or pattern required'))
89
89
90 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
90 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
91 ('number', lambda x: str(x[0].rev())),
91 ('number', lambda x: str(x[0].rev())),
92 ('changeset', lambda x: short(x[0].node())),
92 ('changeset', lambda x: short(x[0].node())),
93 ('date', getdate),
93 ('date', getdate),
94 ('follow', lambda x: x[0].path()),
94 ('follow', lambda x: x[0].path()),
95 ]
95 ]
96
96
97 if (not opts['user'] and not opts['changeset'] and not opts['date']
97 if (not opts['user'] and not opts['changeset'] and not opts['date']
98 and not opts['follow']):
98 and not opts['follow']):
99 opts['number'] = 1
99 opts['number'] = 1
100
100
101 linenumber = opts.get('line_number') is not None
101 linenumber = opts.get('line_number') is not None
102 if (linenumber and (not opts['changeset']) and (not opts['number'])):
102 if (linenumber and (not opts['changeset']) and (not opts['number'])):
103 raise util.Abort(_('at least one of -n/-c is required for -l'))
103 raise util.Abort(_('at least one of -n/-c is required for -l'))
104
104
105 funcmap = [func for op, func in opmap if opts.get(op)]
105 funcmap = [func for op, func in opmap if opts.get(op)]
106 if linenumber:
106 if linenumber:
107 lastfunc = funcmap[-1]
107 lastfunc = funcmap[-1]
108 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
108 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
109
109
110 ctx = repo.changectx(opts['rev'])
110 ctx = repo.changectx(opts['rev'])
111
111
112 m = cmdutil.match(repo, pats, opts)
112 m = cmdutil.match(repo, pats, opts)
113 for abs in repo.walk(m, ctx.node()):
113 for abs in repo.walk(m, ctx.node()):
114 fctx = ctx.filectx(abs)
114 fctx = ctx.filectx(abs)
115 if not opts['text'] and util.binary(fctx.data()):
115 if not opts['text'] and util.binary(fctx.data()):
116 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
116 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
117 continue
117 continue
118
118
119 lines = fctx.annotate(follow=opts.get('follow'),
119 lines = fctx.annotate(follow=opts.get('follow'),
120 linenumber=linenumber)
120 linenumber=linenumber)
121 pieces = []
121 pieces = []
122
122
123 for f in funcmap:
123 for f in funcmap:
124 l = [f(n) for n, dummy in lines]
124 l = [f(n) for n, dummy in lines]
125 if l:
125 if l:
126 m = max(map(len, l))
126 m = max(map(len, l))
127 pieces.append(["%*s" % (m, x) for x in l])
127 pieces.append(["%*s" % (m, x) for x in l])
128
128
129 if pieces:
129 if pieces:
130 for p, l in zip(zip(*pieces), lines):
130 for p, l in zip(zip(*pieces), lines):
131 ui.write("%s: %s" % (" ".join(p), l[1]))
131 ui.write("%s: %s" % (" ".join(p), l[1]))
132
132
133 def archive(ui, repo, dest, **opts):
133 def archive(ui, repo, dest, **opts):
134 '''create unversioned archive of a repository revision
134 '''create unversioned archive of a repository revision
135
135
136 By default, the revision used is the parent of the working
136 By default, the revision used is the parent of the working
137 directory; use "-r" to specify a different revision.
137 directory; use "-r" to specify a different revision.
138
138
139 To specify the type of archive to create, use "-t". Valid
139 To specify the type of archive to create, use "-t". Valid
140 types are:
140 types are:
141
141
142 "files" (default): a directory full of files
142 "files" (default): a directory full of files
143 "tar": tar archive, uncompressed
143 "tar": tar archive, uncompressed
144 "tbz2": tar archive, compressed using bzip2
144 "tbz2": tar archive, compressed using bzip2
145 "tgz": tar archive, compressed using gzip
145 "tgz": tar archive, compressed using gzip
146 "uzip": zip archive, uncompressed
146 "uzip": zip archive, uncompressed
147 "zip": zip archive, compressed using deflate
147 "zip": zip archive, compressed using deflate
148
148
149 The exact name of the destination archive or directory is given
149 The exact name of the destination archive or directory is given
150 using a format string; see "hg help export" for details.
150 using a format string; see "hg help export" for details.
151
151
152 Each member added to an archive file has a directory prefix
152 Each member added to an archive file has a directory prefix
153 prepended. Use "-p" to specify a format string for the prefix.
153 prepended. Use "-p" to specify a format string for the prefix.
154 The default is the basename of the archive, with suffixes removed.
154 The default is the basename of the archive, with suffixes removed.
155 '''
155 '''
156
156
157 ctx = repo.changectx(opts['rev'])
157 ctx = repo.changectx(opts['rev'])
158 if not ctx:
158 if not ctx:
159 raise util.Abort(_('repository has no revisions'))
159 raise util.Abort(_('repository has no revisions'))
160 node = ctx.node()
160 node = ctx.node()
161 dest = cmdutil.make_filename(repo, dest, node)
161 dest = cmdutil.make_filename(repo, dest, node)
162 if os.path.realpath(dest) == repo.root:
162 if os.path.realpath(dest) == repo.root:
163 raise util.Abort(_('repository root cannot be destination'))
163 raise util.Abort(_('repository root cannot be destination'))
164 matchfn = cmdutil.match(repo, [], opts)
164 matchfn = cmdutil.match(repo, [], opts)
165 kind = opts.get('type') or 'files'
165 kind = opts.get('type') or 'files'
166 prefix = opts['prefix']
166 prefix = opts['prefix']
167 if dest == '-':
167 if dest == '-':
168 if kind == 'files':
168 if kind == 'files':
169 raise util.Abort(_('cannot archive plain files to stdout'))
169 raise util.Abort(_('cannot archive plain files to stdout'))
170 dest = sys.stdout
170 dest = sys.stdout
171 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
171 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
172 prefix = cmdutil.make_filename(repo, prefix, node)
172 prefix = cmdutil.make_filename(repo, prefix, node)
173 archival.archive(repo, dest, node, kind, not opts['no_decode'],
173 archival.archive(repo, dest, node, kind, not opts['no_decode'],
174 matchfn, prefix)
174 matchfn, prefix)
175
175
176 def backout(ui, repo, node=None, rev=None, **opts):
176 def backout(ui, repo, node=None, rev=None, **opts):
177 '''reverse effect of earlier changeset
177 '''reverse effect of earlier changeset
178
178
179 Commit the backed out changes as a new changeset. The new
179 Commit the backed out changes as a new changeset. The new
180 changeset is a child of the backed out changeset.
180 changeset is a child of the backed out changeset.
181
181
182 If you back out a changeset other than the tip, a new head is
182 If you back out a changeset other than the tip, a new head is
183 created. This head will be the new tip and you should merge this
183 created. This head will be the new tip and you should merge this
184 backout changeset with another head (current one by default).
184 backout changeset with another head (current one by default).
185
185
186 The --merge option remembers the parent of the working directory
186 The --merge option remembers the parent of the working directory
187 before starting the backout, then merges the new head with that
187 before starting the backout, then merges the new head with that
188 changeset afterwards. This saves you from doing the merge by
188 changeset afterwards. This saves you from doing the merge by
189 hand. The result of this merge is not committed, as for a normal
189 hand. The result of this merge is not committed, as for a normal
190 merge.
190 merge.
191
191
192 See 'hg help dates' for a list of formats valid for -d/--date.
192 See 'hg help dates' for a list of formats valid for -d/--date.
193 '''
193 '''
194 if rev and node:
194 if rev and node:
195 raise util.Abort(_("please specify just one revision"))
195 raise util.Abort(_("please specify just one revision"))
196
196
197 if not rev:
197 if not rev:
198 rev = node
198 rev = node
199
199
200 if not rev:
200 if not rev:
201 raise util.Abort(_("please specify a revision to backout"))
201 raise util.Abort(_("please specify a revision to backout"))
202
202
203 date = opts.get('date')
203 date = opts.get('date')
204 if date:
204 if date:
205 opts['date'] = util.parsedate(date)
205 opts['date'] = util.parsedate(date)
206
206
207 cmdutil.bail_if_changed(repo)
207 cmdutil.bail_if_changed(repo)
208 node = repo.lookup(rev)
208 node = repo.lookup(rev)
209
209
210 op1, op2 = repo.dirstate.parents()
210 op1, op2 = repo.dirstate.parents()
211 a = repo.changelog.ancestor(op1, node)
211 a = repo.changelog.ancestor(op1, node)
212 if a != node:
212 if a != node:
213 raise util.Abort(_('cannot back out change on a different branch'))
213 raise util.Abort(_('cannot back out change on a different branch'))
214
214
215 p1, p2 = repo.changelog.parents(node)
215 p1, p2 = repo.changelog.parents(node)
216 if p1 == nullid:
216 if p1 == nullid:
217 raise util.Abort(_('cannot back out a change with no parents'))
217 raise util.Abort(_('cannot back out a change with no parents'))
218 if p2 != nullid:
218 if p2 != nullid:
219 if not opts['parent']:
219 if not opts['parent']:
220 raise util.Abort(_('cannot back out a merge changeset without '
220 raise util.Abort(_('cannot back out a merge changeset without '
221 '--parent'))
221 '--parent'))
222 p = repo.lookup(opts['parent'])
222 p = repo.lookup(opts['parent'])
223 if p not in (p1, p2):
223 if p not in (p1, p2):
224 raise util.Abort(_('%s is not a parent of %s') %
224 raise util.Abort(_('%s is not a parent of %s') %
225 (short(p), short(node)))
225 (short(p), short(node)))
226 parent = p
226 parent = p
227 else:
227 else:
228 if opts['parent']:
228 if opts['parent']:
229 raise util.Abort(_('cannot use --parent on non-merge changeset'))
229 raise util.Abort(_('cannot use --parent on non-merge changeset'))
230 parent = p1
230 parent = p1
231
231
232 # the backout should appear on the same branch
232 # the backout should appear on the same branch
233 branch = repo.dirstate.branch()
233 branch = repo.dirstate.branch()
234 hg.clean(repo, node, show_stats=False)
234 hg.clean(repo, node, show_stats=False)
235 repo.dirstate.setbranch(branch)
235 repo.dirstate.setbranch(branch)
236 revert_opts = opts.copy()
236 revert_opts = opts.copy()
237 revert_opts['date'] = None
237 revert_opts['date'] = None
238 revert_opts['all'] = True
238 revert_opts['all'] = True
239 revert_opts['rev'] = hex(parent)
239 revert_opts['rev'] = hex(parent)
240 revert_opts['no_backup'] = None
240 revert_opts['no_backup'] = None
241 revert(ui, repo, **revert_opts)
241 revert(ui, repo, **revert_opts)
242 commit_opts = opts.copy()
242 commit_opts = opts.copy()
243 commit_opts['addremove'] = False
243 commit_opts['addremove'] = False
244 if not commit_opts['message'] and not commit_opts['logfile']:
244 if not commit_opts['message'] and not commit_opts['logfile']:
245 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
245 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
246 commit_opts['force_editor'] = True
246 commit_opts['force_editor'] = True
247 commit(ui, repo, **commit_opts)
247 commit(ui, repo, **commit_opts)
248 def nice(node):
248 def nice(node):
249 return '%d:%s' % (repo.changelog.rev(node), short(node))
249 return '%d:%s' % (repo.changelog.rev(node), short(node))
250 ui.status(_('changeset %s backs out changeset %s\n') %
250 ui.status(_('changeset %s backs out changeset %s\n') %
251 (nice(repo.changelog.tip()), nice(node)))
251 (nice(repo.changelog.tip()), nice(node)))
252 if op1 != node:
252 if op1 != node:
253 hg.clean(repo, op1, show_stats=False)
253 hg.clean(repo, op1, show_stats=False)
254 if opts['merge']:
254 if opts['merge']:
255 ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
255 ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
256 hg.merge(repo, hex(repo.changelog.tip()))
256 hg.merge(repo, hex(repo.changelog.tip()))
257 else:
257 else:
258 ui.status(_('the backout changeset is a new head - '
258 ui.status(_('the backout changeset is a new head - '
259 'do not forget to merge\n'))
259 'do not forget to merge\n'))
260 ui.status(_('(use "backout --merge" '
260 ui.status(_('(use "backout --merge" '
261 'if you want to auto-merge)\n'))
261 'if you want to auto-merge)\n'))
262
262
263 def bisect(ui, repo, rev=None, extra=None,
263 def bisect(ui, repo, rev=None, extra=None,
264 reset=None, good=None, bad=None, skip=None, noupdate=None):
264 reset=None, good=None, bad=None, skip=None, noupdate=None):
265 """subdivision search of changesets
265 """subdivision search of changesets
266
266
267 This command helps to find changesets which introduce problems.
267 This command helps to find changesets which introduce problems.
268 To use, mark the earliest changeset you know exhibits the problem
268 To use, mark the earliest changeset you know exhibits the problem
269 as bad, then mark the latest changeset which is free from the
269 as bad, then mark the latest changeset which is free from the
270 problem as good. Bisect will update your working directory to a
270 problem as good. Bisect will update your working directory to a
271 revision for testing. Once you have performed tests, mark the
271 revision for testing. Once you have performed tests, mark the
272 working directory as bad or good and bisect will either update to
272 working directory as bad or good and bisect will either update to
273 another candidate changeset or announce that it has found the bad
273 another candidate changeset or announce that it has found the bad
274 revision.
274 revision.
275 """
275 """
276 # backward compatibility
276 # backward compatibility
277 if rev in "good bad reset init".split():
277 if rev in "good bad reset init".split():
278 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
278 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
279 cmd, rev, extra = rev, extra, None
279 cmd, rev, extra = rev, extra, None
280 if cmd == "good":
280 if cmd == "good":
281 good = True
281 good = True
282 elif cmd == "bad":
282 elif cmd == "bad":
283 bad = True
283 bad = True
284 else:
284 else:
285 reset = True
285 reset = True
286 elif extra or good + bad + skip + reset > 1:
286 elif extra or good + bad + skip + reset > 1:
287 raise util.Abort("Incompatible arguments")
287 raise util.Abort("Incompatible arguments")
288
288
289 if reset:
289 if reset:
290 p = repo.join("bisect.state")
290 p = repo.join("bisect.state")
291 if os.path.exists(p):
291 if os.path.exists(p):
292 os.unlink(p)
292 os.unlink(p)
293 return
293 return
294
294
295 # load state
295 # load state
296 state = {'good': [], 'bad': [], 'skip': []}
296 state = {'good': [], 'bad': [], 'skip': []}
297 if os.path.exists(repo.join("bisect.state")):
297 if os.path.exists(repo.join("bisect.state")):
298 for l in repo.opener("bisect.state"):
298 for l in repo.opener("bisect.state"):
299 kind, node = l[:-1].split()
299 kind, node = l[:-1].split()
300 node = repo.lookup(node)
300 node = repo.lookup(node)
301 if kind not in state:
301 if kind not in state:
302 raise util.Abort(_("unknown bisect kind %s") % kind)
302 raise util.Abort(_("unknown bisect kind %s") % kind)
303 state[kind].append(node)
303 state[kind].append(node)
304
304
305 # update state
305 # update state
306 node = repo.lookup(rev or '.')
306 node = repo.lookup(rev or '.')
307 if good:
307 if good:
308 state['good'].append(node)
308 state['good'].append(node)
309 elif bad:
309 elif bad:
310 state['bad'].append(node)
310 state['bad'].append(node)
311 elif skip:
311 elif skip:
312 state['skip'].append(node)
312 state['skip'].append(node)
313
313
314 # save state
314 # save state
315 f = repo.opener("bisect.state", "w", atomictemp=True)
315 f = repo.opener("bisect.state", "w", atomictemp=True)
316 wlock = repo.wlock()
316 wlock = repo.wlock()
317 try:
317 try:
318 for kind in state:
318 for kind in state:
319 for node in state[kind]:
319 for node in state[kind]:
320 f.write("%s %s\n" % (kind, hex(node)))
320 f.write("%s %s\n" % (kind, hex(node)))
321 f.rename()
321 f.rename()
322 finally:
322 finally:
323 del wlock
323 del wlock
324
324
325 if not state['good'] or not state['bad']:
325 if not state['good'] or not state['bad']:
326 return
326 return
327
327
328 # actually bisect
328 # actually bisect
329 node, changesets, good = hbisect.bisect(repo.changelog, state)
329 node, changesets, good = hbisect.bisect(repo.changelog, state)
330 if changesets == 0:
330 if changesets == 0:
331 ui.write(_("The first %s revision is:\n") % (good and "good" or "bad"))
331 ui.write(_("The first %s revision is:\n") % (good and "good" or "bad"))
332 displayer = cmdutil.show_changeset(ui, repo, {})
332 displayer = cmdutil.show_changeset(ui, repo, {})
333 displayer.show(changenode=node)
333 displayer.show(changenode=node)
334 elif node is not None:
334 elif node is not None:
335 # compute the approximate number of remaining tests
335 # compute the approximate number of remaining tests
336 tests, size = 0, 2
336 tests, size = 0, 2
337 while size <= changesets:
337 while size <= changesets:
338 tests, size = tests + 1, size * 2
338 tests, size = tests + 1, size * 2
339 rev = repo.changelog.rev(node)
339 rev = repo.changelog.rev(node)
340 ui.write(_("Testing changeset %s:%s "
340 ui.write(_("Testing changeset %s:%s "
341 "(%s changesets remaining, ~%s tests)\n")
341 "(%s changesets remaining, ~%s tests)\n")
342 % (rev, short(node), changesets, tests))
342 % (rev, short(node), changesets, tests))
343 if not noupdate:
343 if not noupdate:
344 cmdutil.bail_if_changed(repo)
344 cmdutil.bail_if_changed(repo)
345 return hg.clean(repo, node)
345 return hg.clean(repo, node)
346
346
347 def branch(ui, repo, label=None, **opts):
347 def branch(ui, repo, label=None, **opts):
348 """set or show the current branch name
348 """set or show the current branch name
349
349
350 With no argument, show the current branch name. With one argument,
350 With no argument, show the current branch name. With one argument,
351 set the working directory branch name (the branch does not exist in
351 set the working directory branch name (the branch does not exist in
352 the repository until the next commit).
352 the repository until the next commit).
353
353
354 Unless --force is specified, branch will not let you set a
354 Unless --force is specified, branch will not let you set a
355 branch name that shadows an existing branch.
355 branch name that shadows an existing branch.
356
356
357 Use the command 'hg update' to switch to an existing branch.
357 Use the command 'hg update' to switch to an existing branch.
358 """
358 """
359
359
360 if label:
360 if label:
361 if not opts.get('force') and label in repo.branchtags():
361 if not opts.get('force') and label in repo.branchtags():
362 if label not in [p.branch() for p in repo.workingctx().parents()]:
362 if label not in [p.branch() for p in repo.workingctx().parents()]:
363 raise util.Abort(_('a branch of the same name already exists'
363 raise util.Abort(_('a branch of the same name already exists'
364 ' (use --force to override)'))
364 ' (use --force to override)'))
365 repo.dirstate.setbranch(util.fromlocal(label))
365 repo.dirstate.setbranch(util.fromlocal(label))
366 ui.status(_('marked working directory as branch %s\n') % label)
366 ui.status(_('marked working directory as branch %s\n') % label)
367 else:
367 else:
368 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
368 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
369
369
370 def branches(ui, repo, active=False):
370 def branches(ui, repo, active=False):
371 """list repository named branches
371 """list repository named branches
372
372
373 List the repository's named branches, indicating which ones are
373 List the repository's named branches, indicating which ones are
374 inactive. If active is specified, only show active branches.
374 inactive. If active is specified, only show active branches.
375
375
376 A branch is considered active if it contains unmerged heads.
376 A branch is considered active if it contains unmerged heads.
377
377
378 Use the command 'hg update' to switch to an existing branch.
378 Use the command 'hg update' to switch to an existing branch.
379 """
379 """
380 b = repo.branchtags()
380 b = repo.branchtags()
381 heads = dict.fromkeys(repo.heads(), 1)
381 heads = dict.fromkeys(repo.heads(), 1)
382 l = [((n in heads), repo.changelog.rev(n), n, t) for t, n in b.items()]
382 l = [((n in heads), repo.changelog.rev(n), n, t) for t, n in b.items()]
383 l.sort()
383 l.sort()
384 l.reverse()
384 l.reverse()
385 for ishead, r, n, t in l:
385 for ishead, r, n, t in l:
386 if active and not ishead:
386 if active and not ishead:
387 # If we're only displaying active branches, abort the loop on
387 # If we're only displaying active branches, abort the loop on
388 # encountering the first inactive head
388 # encountering the first inactive head
389 break
389 break
390 else:
390 else:
391 hexfunc = ui.debugflag and hex or short
391 hexfunc = ui.debugflag and hex or short
392 if ui.quiet:
392 if ui.quiet:
393 ui.write("%s\n" % t)
393 ui.write("%s\n" % t)
394 else:
394 else:
395 spaces = " " * (30 - util.locallen(t))
395 spaces = " " * (30 - util.locallen(t))
396 # The code only gets here if inactive branches are being
396 # The code only gets here if inactive branches are being
397 # displayed or the branch is active.
397 # displayed or the branch is active.
398 isinactive = ((not ishead) and " (inactive)") or ''
398 isinactive = ((not ishead) and " (inactive)") or ''
399 ui.write("%s%s %s:%s%s\n" % (t, spaces, r, hexfunc(n), isinactive))
399 ui.write("%s%s %s:%s%s\n" % (t, spaces, r, hexfunc(n), isinactive))
400
400
401 def bundle(ui, repo, fname, dest=None, **opts):
401 def bundle(ui, repo, fname, dest=None, **opts):
402 """create a changegroup file
402 """create a changegroup file
403
403
404 Generate a compressed changegroup file collecting changesets not
404 Generate a compressed changegroup file collecting changesets not
405 found in the other repository.
405 found in the other repository.
406
406
407 If no destination repository is specified the destination is
407 If no destination repository is specified the destination is
408 assumed to have all the nodes specified by one or more --base
408 assumed to have all the nodes specified by one or more --base
409 parameters. To create a bundle containing all changesets, use
409 parameters. To create a bundle containing all changesets, use
410 --all (or --base null). To change the compression method applied,
410 --all (or --base null). To change the compression method applied,
411 use the -t option (by default, bundles are compressed using bz2).
411 use the -t option (by default, bundles are compressed using bz2).
412
412
413 The bundle file can then be transferred using conventional means and
413 The bundle file can then be transferred using conventional means and
414 applied to another repository with the unbundle or pull command.
414 applied to another repository with the unbundle or pull command.
415 This is useful when direct push and pull are not available or when
415 This is useful when direct push and pull are not available or when
416 exporting an entire repository is undesirable.
416 exporting an entire repository is undesirable.
417
417
418 Applying bundles preserves all changeset contents including
418 Applying bundles preserves all changeset contents including
419 permissions, copy/rename information, and revision history.
419 permissions, copy/rename information, and revision history.
420 """
420 """
421 revs = opts.get('rev') or None
421 revs = opts.get('rev') or None
422 if revs:
422 if revs:
423 revs = [repo.lookup(rev) for rev in revs]
423 revs = [repo.lookup(rev) for rev in revs]
424 if opts.get('all'):
424 if opts.get('all'):
425 base = ['null']
425 base = ['null']
426 else:
426 else:
427 base = opts.get('base')
427 base = opts.get('base')
428 if base:
428 if base:
429 if dest:
429 if dest:
430 raise util.Abort(_("--base is incompatible with specifiying "
430 raise util.Abort(_("--base is incompatible with specifiying "
431 "a destination"))
431 "a destination"))
432 base = [repo.lookup(rev) for rev in base]
432 base = [repo.lookup(rev) for rev in base]
433 # create the right base
433 # create the right base
434 # XXX: nodesbetween / changegroup* should be "fixed" instead
434 # XXX: nodesbetween / changegroup* should be "fixed" instead
435 o = []
435 o = []
436 has = {nullid: None}
436 has = {nullid: None}
437 for n in base:
437 for n in base:
438 has.update(repo.changelog.reachable(n))
438 has.update(repo.changelog.reachable(n))
439 if revs:
439 if revs:
440 visit = list(revs)
440 visit = list(revs)
441 else:
441 else:
442 visit = repo.changelog.heads()
442 visit = repo.changelog.heads()
443 seen = {}
443 seen = {}
444 while visit:
444 while visit:
445 n = visit.pop(0)
445 n = visit.pop(0)
446 parents = [p for p in repo.changelog.parents(n) if p not in has]
446 parents = [p for p in repo.changelog.parents(n) if p not in has]
447 if len(parents) == 0:
447 if len(parents) == 0:
448 o.insert(0, n)
448 o.insert(0, n)
449 else:
449 else:
450 for p in parents:
450 for p in parents:
451 if p not in seen:
451 if p not in seen:
452 seen[p] = 1
452 seen[p] = 1
453 visit.append(p)
453 visit.append(p)
454 else:
454 else:
455 cmdutil.setremoteconfig(ui, opts)
455 cmdutil.setremoteconfig(ui, opts)
456 dest, revs, checkout = hg.parseurl(
456 dest, revs, checkout = hg.parseurl(
457 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
457 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
458 other = hg.repository(ui, dest)
458 other = hg.repository(ui, dest)
459 o = repo.findoutgoing(other, force=opts['force'])
459 o = repo.findoutgoing(other, force=opts['force'])
460
460
461 if revs:
461 if revs:
462 cg = repo.changegroupsubset(o, revs, 'bundle')
462 cg = repo.changegroupsubset(o, revs, 'bundle')
463 else:
463 else:
464 cg = repo.changegroup(o, 'bundle')
464 cg = repo.changegroup(o, 'bundle')
465
465
466 bundletype = opts.get('type', 'bzip2').lower()
466 bundletype = opts.get('type', 'bzip2').lower()
467 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
467 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
468 bundletype = btypes.get(bundletype)
468 bundletype = btypes.get(bundletype)
469 if bundletype not in changegroup.bundletypes:
469 if bundletype not in changegroup.bundletypes:
470 raise util.Abort(_('unknown bundle type specified with --type'))
470 raise util.Abort(_('unknown bundle type specified with --type'))
471
471
472 changegroup.writebundle(cg, fname, bundletype)
472 changegroup.writebundle(cg, fname, bundletype)
473
473
474 def cat(ui, repo, file1, *pats, **opts):
474 def cat(ui, repo, file1, *pats, **opts):
475 """output the current or given revision of files
475 """output the current or given revision of files
476
476
477 Print the specified files as they were at the given revision.
477 Print the specified files as they were at the given revision.
478 If no revision is given, the parent of the working directory is used,
478 If no revision is given, the parent of the working directory is used,
479 or tip if no revision is checked out.
479 or tip if no revision is checked out.
480
480
481 Output may be to a file, in which case the name of the file is
481 Output may be to a file, in which case the name of the file is
482 given using a format string. The formatting rules are the same as
482 given using a format string. The formatting rules are the same as
483 for the export command, with the following additions:
483 for the export command, with the following additions:
484
484
485 %s basename of file being printed
485 %s basename of file being printed
486 %d dirname of file being printed, or '.' if in repo root
486 %d dirname of file being printed, or '.' if in repo root
487 %p root-relative path name of file being printed
487 %p root-relative path name of file being printed
488 """
488 """
489 ctx = repo.changectx(opts['rev'])
489 ctx = repo.changectx(opts['rev'])
490 err = 1
490 err = 1
491 m = cmdutil.match(repo, (file1,) + pats, opts)
491 m = cmdutil.match(repo, (file1,) + pats, opts)
492 for abs in repo.walk(m, ctx.node()):
492 for abs in repo.walk(m, ctx.node()):
493 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
493 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
494 data = ctx.filectx(abs).data()
494 data = ctx.filectx(abs).data()
495 if opts.get('decode'):
495 if opts.get('decode'):
496 data = repo.wwritedata(abs, data)
496 data = repo.wwritedata(abs, data)
497 fp.write(data)
497 fp.write(data)
498 err = 0
498 err = 0
499 return err
499 return err
500
500
501 def clone(ui, source, dest=None, **opts):
501 def clone(ui, source, dest=None, **opts):
502 """make a copy of an existing repository
502 """make a copy of an existing repository
503
503
504 Create a copy of an existing repository in a new directory.
504 Create a copy of an existing repository in a new directory.
505
505
506 If no destination directory name is specified, it defaults to the
506 If no destination directory name is specified, it defaults to the
507 basename of the source.
507 basename of the source.
508
508
509 The location of the source is added to the new repository's
509 The location of the source is added to the new repository's
510 .hg/hgrc file, as the default to be used for future pulls.
510 .hg/hgrc file, as the default to be used for future pulls.
511
511
512 For efficiency, hardlinks are used for cloning whenever the source
512 For efficiency, hardlinks are used for cloning whenever the source
513 and destination are on the same filesystem (note this applies only
513 and destination are on the same filesystem (note this applies only
514 to the repository data, not to the checked out files). Some
514 to the repository data, not to the checked out files). Some
515 filesystems, such as AFS, implement hardlinking incorrectly, but
515 filesystems, such as AFS, implement hardlinking incorrectly, but
516 do not report errors. In these cases, use the --pull option to
516 do not report errors. In these cases, use the --pull option to
517 avoid hardlinking.
517 avoid hardlinking.
518
518
519 In some cases, you can clone repositories and checked out files
519 In some cases, you can clone repositories and checked out files
520 using full hardlinks with
520 using full hardlinks with
521
521
522 $ cp -al REPO REPOCLONE
522 $ cp -al REPO REPOCLONE
523
523
524 This is the fastest way to clone, but it is not always safe. The
524 This is the fastest way to clone, but it is not always safe. The
525 operation is not atomic (making sure REPO is not modified during
525 operation is not atomic (making sure REPO is not modified during
526 the operation is up to you) and you have to make sure your editor
526 the operation is up to you) and you have to make sure your editor
527 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
527 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
528 this is not compatible with certain extensions that place their
528 this is not compatible with certain extensions that place their
529 metadata under the .hg directory, such as mq.
529 metadata under the .hg directory, such as mq.
530
530
531 If you use the -r option to clone up to a specific revision, no
531 If you use the -r option to clone up to a specific revision, no
532 subsequent revisions will be present in the cloned repository.
532 subsequent revisions will be present in the cloned repository.
533 This option implies --pull, even on local repositories.
533 This option implies --pull, even on local repositories.
534
534
535 See pull for valid source format details.
535 See pull for valid source format details.
536
536
537 It is possible to specify an ssh:// URL as the destination, but no
537 It is possible to specify an ssh:// URL as the destination, but no
538 .hg/hgrc and working directory will be created on the remote side.
538 .hg/hgrc and working directory will be created on the remote side.
539 Look at the help text for the pull command for important details
539 Look at the help text for the pull command for important details
540 about ssh:// URLs.
540 about ssh:// URLs.
541 """
541 """
542 cmdutil.setremoteconfig(ui, opts)
542 cmdutil.setremoteconfig(ui, opts)
543 hg.clone(ui, source, dest,
543 hg.clone(ui, source, dest,
544 pull=opts['pull'],
544 pull=opts['pull'],
545 stream=opts['uncompressed'],
545 stream=opts['uncompressed'],
546 rev=opts['rev'],
546 rev=opts['rev'],
547 update=not opts['noupdate'])
547 update=not opts['noupdate'])
548
548
549 def commit(ui, repo, *pats, **opts):
549 def commit(ui, repo, *pats, **opts):
550 """commit the specified files or all outstanding changes
550 """commit the specified files or all outstanding changes
551
551
552 Commit changes to the given files into the repository.
552 Commit changes to the given files into the repository.
553
553
554 If a list of files is omitted, all changes reported by "hg status"
554 If a list of files is omitted, all changes reported by "hg status"
555 will be committed.
555 will be committed.
556
556
557 If you are committing the result of a merge, do not provide any
557 If you are committing the result of a merge, do not provide any
558 file names or -I/-X filters.
558 file names or -I/-X filters.
559
559
560 If no commit message is specified, the configured editor is started to
560 If no commit message is specified, the configured editor is started to
561 enter a message.
561 enter a message.
562
562
563 See 'hg help dates' for a list of formats valid for -d/--date.
563 See 'hg help dates' for a list of formats valid for -d/--date.
564 """
564 """
565 def commitfunc(ui, repo, message, match, opts):
565 def commitfunc(ui, repo, message, match, opts):
566 return repo.commit(match.files(), message, opts['user'], opts['date'],
566 return repo.commit(match.files(), message, opts['user'], opts['date'],
567 match, force_editor=opts.get('force_editor'))
567 match, force_editor=opts.get('force_editor'))
568
568
569 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
569 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
570 if not node:
570 if not node:
571 return
571 return
572 cl = repo.changelog
572 cl = repo.changelog
573 rev = cl.rev(node)
573 rev = cl.rev(node)
574 parents = cl.parentrevs(rev)
574 parents = cl.parentrevs(rev)
575 if rev - 1 in parents:
575 if rev - 1 in parents:
576 # one of the parents was the old tip
576 # one of the parents was the old tip
577 return
577 return
578 if (parents == (nullrev, nullrev) or
578 if (parents == (nullrev, nullrev) or
579 len(cl.heads(cl.node(parents[0]))) > 1 and
579 len(cl.heads(cl.node(parents[0]))) > 1 and
580 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
580 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
581 ui.status(_('created new head\n'))
581 ui.status(_('created new head\n'))
582
582
583 def copy(ui, repo, *pats, **opts):
583 def copy(ui, repo, *pats, **opts):
584 """mark files as copied for the next commit
584 """mark files as copied for the next commit
585
585
586 Mark dest as having copies of source files. If dest is a
586 Mark dest as having copies of source files. If dest is a
587 directory, copies are put in that directory. If dest is a file,
587 directory, copies are put in that directory. If dest is a file,
588 there can only be one source.
588 there can only be one source.
589
589
590 By default, this command copies the contents of files as they
590 By default, this command copies the contents of files as they
591 stand in the working directory. If invoked with --after, the
591 stand in the working directory. If invoked with --after, the
592 operation is recorded, but no copying is performed.
592 operation is recorded, but no copying is performed.
593
593
594 This command takes effect in the next commit. To undo a copy
594 This command takes effect in the next commit. To undo a copy
595 before that, see hg revert.
595 before that, see hg revert.
596 """
596 """
597 wlock = repo.wlock(False)
597 wlock = repo.wlock(False)
598 try:
598 try:
599 return cmdutil.copy(ui, repo, pats, opts)
599 return cmdutil.copy(ui, repo, pats, opts)
600 finally:
600 finally:
601 del wlock
601 del wlock
602
602
603 def debugancestor(ui, repo, *args):
603 def debugancestor(ui, repo, *args):
604 """find the ancestor revision of two revisions in a given index"""
604 """find the ancestor revision of two revisions in a given index"""
605 if len(args) == 3:
605 if len(args) == 3:
606 index, rev1, rev2 = args
606 index, rev1, rev2 = args
607 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
607 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
608 lookup = r.lookup
608 lookup = r.lookup
609 elif len(args) == 2:
609 elif len(args) == 2:
610 if not repo:
610 if not repo:
611 raise util.Abort(_("There is no Mercurial repository here "
611 raise util.Abort(_("There is no Mercurial repository here "
612 "(.hg not found)"))
612 "(.hg not found)"))
613 rev1, rev2 = args
613 rev1, rev2 = args
614 r = repo.changelog
614 r = repo.changelog
615 lookup = repo.lookup
615 lookup = repo.lookup
616 else:
616 else:
617 raise util.Abort(_('either two or three arguments required'))
617 raise util.Abort(_('either two or three arguments required'))
618 a = r.ancestor(lookup(rev1), lookup(rev2))
618 a = r.ancestor(lookup(rev1), lookup(rev2))
619 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
619 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
620
620
621 def debugcomplete(ui, cmd='', **opts):
621 def debugcomplete(ui, cmd='', **opts):
622 """returns the completion list associated with the given command"""
622 """returns the completion list associated with the given command"""
623
623
624 if opts['options']:
624 if opts['options']:
625 options = []
625 options = []
626 otables = [globalopts]
626 otables = [globalopts]
627 if cmd:
627 if cmd:
628 aliases, entry = cmdutil.findcmd(ui, cmd, table)
628 aliases, entry = cmdutil.findcmd(ui, cmd, table)
629 otables.append(entry[1])
629 otables.append(entry[1])
630 for t in otables:
630 for t in otables:
631 for o in t:
631 for o in t:
632 if o[0]:
632 if o[0]:
633 options.append('-%s' % o[0])
633 options.append('-%s' % o[0])
634 options.append('--%s' % o[1])
634 options.append('--%s' % o[1])
635 ui.write("%s\n" % "\n".join(options))
635 ui.write("%s\n" % "\n".join(options))
636 return
636 return
637
637
638 clist = cmdutil.findpossible(ui, cmd, table).keys()
638 clist = cmdutil.findpossible(ui, cmd, table).keys()
639 clist.sort()
639 clist.sort()
640 ui.write("%s\n" % "\n".join(clist))
640 ui.write("%s\n" % "\n".join(clist))
641
641
642 def debugfsinfo(ui, path = "."):
642 def debugfsinfo(ui, path = "."):
643 file('.debugfsinfo', 'w').write('')
643 file('.debugfsinfo', 'w').write('')
644 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
644 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
645 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
645 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
646 ui.write('case-sensitive: %s\n' % (util.checkfolding('.debugfsinfo')
646 ui.write('case-sensitive: %s\n' % (util.checkfolding('.debugfsinfo')
647 and 'yes' or 'no'))
647 and 'yes' or 'no'))
648 os.unlink('.debugfsinfo')
648 os.unlink('.debugfsinfo')
649
649
650 def debugrebuildstate(ui, repo, rev=""):
650 def debugrebuildstate(ui, repo, rev=""):
651 """rebuild the dirstate as it would look like for the given revision"""
651 """rebuild the dirstate as it would look like for the given revision"""
652 if rev == "":
652 if rev == "":
653 rev = repo.changelog.tip()
653 rev = repo.changelog.tip()
654 ctx = repo.changectx(rev)
654 ctx = repo.changectx(rev)
655 files = ctx.manifest()
655 files = ctx.manifest()
656 wlock = repo.wlock()
656 wlock = repo.wlock()
657 try:
657 try:
658 repo.dirstate.rebuild(rev, files)
658 repo.dirstate.rebuild(rev, files)
659 finally:
659 finally:
660 del wlock
660 del wlock
661
661
662 def debugcheckstate(ui, repo):
662 def debugcheckstate(ui, repo):
663 """validate the correctness of the current dirstate"""
663 """validate the correctness of the current dirstate"""
664 parent1, parent2 = repo.dirstate.parents()
664 parent1, parent2 = repo.dirstate.parents()
665 m1 = repo.changectx(parent1).manifest()
665 m1 = repo.changectx(parent1).manifest()
666 m2 = repo.changectx(parent2).manifest()
666 m2 = repo.changectx(parent2).manifest()
667 errors = 0
667 errors = 0
668 for f in repo.dirstate:
668 for f in repo.dirstate:
669 state = repo.dirstate[f]
669 state = repo.dirstate[f]
670 if state in "nr" and f not in m1:
670 if state in "nr" and f not in m1:
671 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
671 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
672 errors += 1
672 errors += 1
673 if state in "a" and f in m1:
673 if state in "a" and f in m1:
674 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
674 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
675 errors += 1
675 errors += 1
676 if state in "m" and f not in m1 and f not in m2:
676 if state in "m" and f not in m1 and f not in m2:
677 ui.warn(_("%s in state %s, but not in either manifest\n") %
677 ui.warn(_("%s in state %s, but not in either manifest\n") %
678 (f, state))
678 (f, state))
679 errors += 1
679 errors += 1
680 for f in m1:
680 for f in m1:
681 state = repo.dirstate[f]
681 state = repo.dirstate[f]
682 if state not in "nrm":
682 if state not in "nrm":
683 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
683 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
684 errors += 1
684 errors += 1
685 if errors:
685 if errors:
686 error = _(".hg/dirstate inconsistent with current parent's manifest")
686 error = _(".hg/dirstate inconsistent with current parent's manifest")
687 raise util.Abort(error)
687 raise util.Abort(error)
688
688
689 def showconfig(ui, repo, *values, **opts):
689 def showconfig(ui, repo, *values, **opts):
690 """show combined config settings from all hgrc files
690 """show combined config settings from all hgrc files
691
691
692 With no args, print names and values of all config items.
692 With no args, print names and values of all config items.
693
693
694 With one arg of the form section.name, print just the value of
694 With one arg of the form section.name, print just the value of
695 that config item.
695 that config item.
696
696
697 With multiple args, print names and values of all config items
697 With multiple args, print names and values of all config items
698 with matching section names."""
698 with matching section names."""
699
699
700 untrusted = bool(opts.get('untrusted'))
700 untrusted = bool(opts.get('untrusted'))
701 if values:
701 if values:
702 if len([v for v in values if '.' in v]) > 1:
702 if len([v for v in values if '.' in v]) > 1:
703 raise util.Abort(_('only one config item permitted'))
703 raise util.Abort(_('only one config item permitted'))
704 for section, name, value in ui.walkconfig(untrusted=untrusted):
704 for section, name, value in ui.walkconfig(untrusted=untrusted):
705 sectname = section + '.' + name
705 sectname = section + '.' + name
706 if values:
706 if values:
707 for v in values:
707 for v in values:
708 if v == section:
708 if v == section:
709 ui.write('%s=%s\n' % (sectname, value))
709 ui.write('%s=%s\n' % (sectname, value))
710 elif v == sectname:
710 elif v == sectname:
711 ui.write(value, '\n')
711 ui.write(value, '\n')
712 else:
712 else:
713 ui.write('%s=%s\n' % (sectname, value))
713 ui.write('%s=%s\n' % (sectname, value))
714
714
715 def debugsetparents(ui, repo, rev1, rev2=None):
715 def debugsetparents(ui, repo, rev1, rev2=None):
716 """manually set the parents of the current working directory
716 """manually set the parents of the current working directory
717
717
718 This is useful for writing repository conversion tools, but should
718 This is useful for writing repository conversion tools, but should
719 be used with care.
719 be used with care.
720 """
720 """
721
721
722 if not rev2:
722 if not rev2:
723 rev2 = hex(nullid)
723 rev2 = hex(nullid)
724
724
725 wlock = repo.wlock()
725 wlock = repo.wlock()
726 try:
726 try:
727 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
727 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
728 finally:
728 finally:
729 del wlock
729 del wlock
730
730
731 def debugstate(ui, repo, nodates=None):
731 def debugstate(ui, repo, nodates=None):
732 """show the contents of the current dirstate"""
732 """show the contents of the current dirstate"""
733 k = repo.dirstate._map.items()
733 k = repo.dirstate._map.items()
734 k.sort()
734 k.sort()
735 timestr = ""
735 timestr = ""
736 showdate = not nodates
736 showdate = not nodates
737 for file_, ent in k:
737 for file_, ent in k:
738 if showdate:
738 if showdate:
739 if ent[3] == -1:
739 if ent[3] == -1:
740 # Pad or slice to locale representation
740 # Pad or slice to locale representation
741 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(0)))
741 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(0)))
742 timestr = 'unset'
742 timestr = 'unset'
743 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
743 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
744 else:
744 else:
745 timestr = time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(ent[3]))
745 timestr = time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(ent[3]))
746 if ent[1] & 020000:
746 if ent[1] & 020000:
747 mode = 'lnk'
747 mode = 'lnk'
748 else:
748 else:
749 mode = '%3o' % (ent[1] & 0777)
749 mode = '%3o' % (ent[1] & 0777)
750 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
750 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
751 for f in repo.dirstate.copies():
751 for f in repo.dirstate.copies():
752 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
752 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
753
753
754 def debugdata(ui, file_, rev):
754 def debugdata(ui, file_, rev):
755 """dump the contents of a data file revision"""
755 """dump the contents of a data file revision"""
756 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
756 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
757 try:
757 try:
758 ui.write(r.revision(r.lookup(rev)))
758 ui.write(r.revision(r.lookup(rev)))
759 except KeyError:
759 except KeyError:
760 raise util.Abort(_('invalid revision identifier %s') % rev)
760 raise util.Abort(_('invalid revision identifier %s') % rev)
761
761
762 def debugdate(ui, date, range=None, **opts):
762 def debugdate(ui, date, range=None, **opts):
763 """parse and display a date"""
763 """parse and display a date"""
764 if opts["extended"]:
764 if opts["extended"]:
765 d = util.parsedate(date, util.extendeddateformats)
765 d = util.parsedate(date, util.extendeddateformats)
766 else:
766 else:
767 d = util.parsedate(date)
767 d = util.parsedate(date)
768 ui.write("internal: %s %s\n" % d)
768 ui.write("internal: %s %s\n" % d)
769 ui.write("standard: %s\n" % util.datestr(d))
769 ui.write("standard: %s\n" % util.datestr(d))
770 if range:
770 if range:
771 m = util.matchdate(range)
771 m = util.matchdate(range)
772 ui.write("match: %s\n" % m(d[0]))
772 ui.write("match: %s\n" % m(d[0]))
773
773
774 def debugindex(ui, file_):
774 def debugindex(ui, file_):
775 """dump the contents of an index file"""
775 """dump the contents of an index file"""
776 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
776 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
777 ui.write(" rev offset length base linkrev" +
777 ui.write(" rev offset length base linkrev" +
778 " nodeid p1 p2\n")
778 " nodeid p1 p2\n")
779 for i in xrange(r.count()):
779 for i in xrange(r.count()):
780 node = r.node(i)
780 node = r.node(i)
781 try:
781 try:
782 pp = r.parents(node)
782 pp = r.parents(node)
783 except:
783 except:
784 pp = [nullid, nullid]
784 pp = [nullid, nullid]
785 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
785 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
786 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
786 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
787 short(node), short(pp[0]), short(pp[1])))
787 short(node), short(pp[0]), short(pp[1])))
788
788
789 def debugindexdot(ui, file_):
789 def debugindexdot(ui, file_):
790 """dump an index DAG as a .dot file"""
790 """dump an index DAG as a .dot file"""
791 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
791 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
792 ui.write("digraph G {\n")
792 ui.write("digraph G {\n")
793 for i in xrange(r.count()):
793 for i in xrange(r.count()):
794 node = r.node(i)
794 node = r.node(i)
795 pp = r.parents(node)
795 pp = r.parents(node)
796 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
796 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
797 if pp[1] != nullid:
797 if pp[1] != nullid:
798 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
798 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
799 ui.write("}\n")
799 ui.write("}\n")
800
800
801 def debuginstall(ui):
801 def debuginstall(ui):
802 '''test Mercurial installation'''
802 '''test Mercurial installation'''
803
803
804 def writetemp(contents):
804 def writetemp(contents):
805 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
805 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
806 f = os.fdopen(fd, "wb")
806 f = os.fdopen(fd, "wb")
807 f.write(contents)
807 f.write(contents)
808 f.close()
808 f.close()
809 return name
809 return name
810
810
811 problems = 0
811 problems = 0
812
812
813 # encoding
813 # encoding
814 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
814 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
815 try:
815 try:
816 util.fromlocal("test")
816 util.fromlocal("test")
817 except util.Abort, inst:
817 except util.Abort, inst:
818 ui.write(" %s\n" % inst)
818 ui.write(" %s\n" % inst)
819 ui.write(_(" (check that your locale is properly set)\n"))
819 ui.write(_(" (check that your locale is properly set)\n"))
820 problems += 1
820 problems += 1
821
821
822 # compiled modules
822 # compiled modules
823 ui.status(_("Checking extensions...\n"))
823 ui.status(_("Checking extensions...\n"))
824 try:
824 try:
825 import bdiff, mpatch, base85
825 import bdiff, mpatch, base85
826 except Exception, inst:
826 except Exception, inst:
827 ui.write(" %s\n" % inst)
827 ui.write(" %s\n" % inst)
828 ui.write(_(" One or more extensions could not be found"))
828 ui.write(_(" One or more extensions could not be found"))
829 ui.write(_(" (check that you compiled the extensions)\n"))
829 ui.write(_(" (check that you compiled the extensions)\n"))
830 problems += 1
830 problems += 1
831
831
832 # templates
832 # templates
833 ui.status(_("Checking templates...\n"))
833 ui.status(_("Checking templates...\n"))
834 try:
834 try:
835 import templater
835 import templater
836 t = templater.templater(templater.templatepath("map-cmdline.default"))
836 t = templater.templater(templater.templatepath("map-cmdline.default"))
837 except Exception, inst:
837 except Exception, inst:
838 ui.write(" %s\n" % inst)
838 ui.write(" %s\n" % inst)
839 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
839 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
840 problems += 1
840 problems += 1
841
841
842 # patch
842 # patch
843 ui.status(_("Checking patch...\n"))
843 ui.status(_("Checking patch...\n"))
844 patchproblems = 0
844 patchproblems = 0
845 a = "1\n2\n3\n4\n"
845 a = "1\n2\n3\n4\n"
846 b = "1\n2\n3\ninsert\n4\n"
846 b = "1\n2\n3\ninsert\n4\n"
847 fa = writetemp(a)
847 fa = writetemp(a)
848 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
848 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
849 os.path.basename(fa))
849 os.path.basename(fa))
850 fd = writetemp(d)
850 fd = writetemp(d)
851
851
852 files = {}
852 files = {}
853 try:
853 try:
854 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
854 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
855 except util.Abort, e:
855 except util.Abort, e:
856 ui.write(_(" patch call failed:\n"))
856 ui.write(_(" patch call failed:\n"))
857 ui.write(" " + str(e) + "\n")
857 ui.write(" " + str(e) + "\n")
858 patchproblems += 1
858 patchproblems += 1
859 else:
859 else:
860 if list(files) != [os.path.basename(fa)]:
860 if list(files) != [os.path.basename(fa)]:
861 ui.write(_(" unexpected patch output!\n"))
861 ui.write(_(" unexpected patch output!\n"))
862 patchproblems += 1
862 patchproblems += 1
863 a = file(fa).read()
863 a = file(fa).read()
864 if a != b:
864 if a != b:
865 ui.write(_(" patch test failed!\n"))
865 ui.write(_(" patch test failed!\n"))
866 patchproblems += 1
866 patchproblems += 1
867
867
868 if patchproblems:
868 if patchproblems:
869 if ui.config('ui', 'patch'):
869 if ui.config('ui', 'patch'):
870 ui.write(_(" (Current patch tool may be incompatible with patch,"
870 ui.write(_(" (Current patch tool may be incompatible with patch,"
871 " or misconfigured. Please check your .hgrc file)\n"))
871 " or misconfigured. Please check your .hgrc file)\n"))
872 else:
872 else:
873 ui.write(_(" Internal patcher failure, please report this error"
873 ui.write(_(" Internal patcher failure, please report this error"
874 " to http://www.selenic.com/mercurial/bts\n"))
874 " to http://www.selenic.com/mercurial/bts\n"))
875 problems += patchproblems
875 problems += patchproblems
876
876
877 os.unlink(fa)
877 os.unlink(fa)
878 os.unlink(fd)
878 os.unlink(fd)
879
879
880 # editor
880 # editor
881 ui.status(_("Checking commit editor...\n"))
881 ui.status(_("Checking commit editor...\n"))
882 editor = ui.geteditor()
882 editor = ui.geteditor()
883 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
883 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
884 if not cmdpath:
884 if not cmdpath:
885 if editor == 'vi':
885 if editor == 'vi':
886 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
886 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
887 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
887 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
888 else:
888 else:
889 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
889 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
890 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
890 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
891 problems += 1
891 problems += 1
892
892
893 # check username
893 # check username
894 ui.status(_("Checking username...\n"))
894 ui.status(_("Checking username...\n"))
895 user = os.environ.get("HGUSER")
895 user = os.environ.get("HGUSER")
896 if user is None:
896 if user is None:
897 user = ui.config("ui", "username")
897 user = ui.config("ui", "username")
898 if user is None:
898 if user is None:
899 user = os.environ.get("EMAIL")
899 user = os.environ.get("EMAIL")
900 if not user:
900 if not user:
901 ui.warn(" ")
901 ui.warn(" ")
902 ui.username()
902 ui.username()
903 ui.write(_(" (specify a username in your .hgrc file)\n"))
903 ui.write(_(" (specify a username in your .hgrc file)\n"))
904
904
905 if not problems:
905 if not problems:
906 ui.status(_("No problems detected\n"))
906 ui.status(_("No problems detected\n"))
907 else:
907 else:
908 ui.write(_("%s problems detected,"
908 ui.write(_("%s problems detected,"
909 " please check your install!\n") % problems)
909 " please check your install!\n") % problems)
910
910
911 return problems
911 return problems
912
912
913 def debugrename(ui, repo, file1, *pats, **opts):
913 def debugrename(ui, repo, file1, *pats, **opts):
914 """dump rename information"""
914 """dump rename information"""
915
915
916 ctx = repo.changectx(opts.get('rev', 'tip'))
916 ctx = repo.changectx(opts.get('rev', 'tip'))
917 m = cmdutil.match(repo, (file1,) + pats, opts)
917 m = cmdutil.match(repo, (file1,) + pats, opts)
918 for abs in repo.walk(m, ctx.node()):
918 for abs in repo.walk(m, ctx.node()):
919 fctx = ctx.filectx(abs)
919 fctx = ctx.filectx(abs)
920 o = fctx.filelog().renamed(fctx.filenode())
920 o = fctx.filelog().renamed(fctx.filenode())
921 rel = m.rel(abs)
921 rel = m.rel(abs)
922 if o:
922 if o:
923 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
923 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
924 else:
924 else:
925 ui.write(_("%s not renamed\n") % rel)
925 ui.write(_("%s not renamed\n") % rel)
926
926
927 def debugwalk(ui, repo, *pats, **opts):
927 def debugwalk(ui, repo, *pats, **opts):
928 """show how files match on given patterns"""
928 """show how files match on given patterns"""
929 m = cmdutil.match(repo, pats, opts)
929 m = cmdutil.match(repo, pats, opts)
930 items = list(repo.walk(m))
930 items = list(repo.walk(m))
931 if not items:
931 if not items:
932 return
932 return
933 fmt = 'f %%-%ds %%-%ds %%s' % (
933 fmt = 'f %%-%ds %%-%ds %%s' % (
934 max([len(abs) for abs in items]),
934 max([len(abs) for abs in items]),
935 max([len(m.rel(abs)) for abs in items]))
935 max([len(m.rel(abs)) for abs in items]))
936 for abs in items:
936 for abs in items:
937 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
937 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
938 ui.write("%s\n" % line.rstrip())
938 ui.write("%s\n" % line.rstrip())
939
939
940 def diff(ui, repo, *pats, **opts):
940 def diff(ui, repo, *pats, **opts):
941 """diff repository (or selected files)
941 """diff repository (or selected files)
942
942
943 Show differences between revisions for the specified files.
943 Show differences between revisions for the specified files.
944
944
945 Differences between files are shown using the unified diff format.
945 Differences between files are shown using the unified diff format.
946
946
947 NOTE: diff may generate unexpected results for merges, as it will
947 NOTE: diff may generate unexpected results for merges, as it will
948 default to comparing against the working directory's first parent
948 default to comparing against the working directory's first parent
949 changeset if no revisions are specified.
949 changeset if no revisions are specified.
950
950
951 When two revision arguments are given, then changes are shown
951 When two revision arguments are given, then changes are shown
952 between those revisions. If only one revision is specified then
952 between those revisions. If only one revision is specified then
953 that revision is compared to the working directory, and, when no
953 that revision is compared to the working directory, and, when no
954 revisions are specified, the working directory files are compared
954 revisions are specified, the working directory files are compared
955 to its parent.
955 to its parent.
956
956
957 Without the -a option, diff will avoid generating diffs of files
957 Without the -a option, diff will avoid generating diffs of files
958 it detects as binary. With -a, diff will generate a diff anyway,
958 it detects as binary. With -a, diff will generate a diff anyway,
959 probably with undesirable results.
959 probably with undesirable results.
960 """
960 """
961 node1, node2 = cmdutil.revpair(repo, opts['rev'])
961 node1, node2 = cmdutil.revpair(repo, opts['rev'])
962
962
963 m = cmdutil.match(repo, pats, opts)
963 m = cmdutil.match(repo, pats, opts)
964 patch.diff(repo, node1, node2, match=m, opts=patch.diffopts(ui, opts))
964 patch.diff(repo, node1, node2, match=m, opts=patch.diffopts(ui, opts))
965
965
966 def export(ui, repo, *changesets, **opts):
966 def export(ui, repo, *changesets, **opts):
967 """dump the header and diffs for one or more changesets
967 """dump the header and diffs for one or more changesets
968
968
969 Print the changeset header and diffs for one or more revisions.
969 Print the changeset header and diffs for one or more revisions.
970
970
971 The information shown in the changeset header is: author,
971 The information shown in the changeset header is: author,
972 changeset hash, parent(s) and commit comment.
972 changeset hash, parent(s) and commit comment.
973
973
974 NOTE: export may generate unexpected diff output for merge changesets,
974 NOTE: export may generate unexpected diff output for merge changesets,
975 as it will compare the merge changeset against its first parent only.
975 as it will compare the merge changeset against its first parent only.
976
976
977 Output may be to a file, in which case the name of the file is
977 Output may be to a file, in which case the name of the file is
978 given using a format string. The formatting rules are as follows:
978 given using a format string. The formatting rules are as follows:
979
979
980 %% literal "%" character
980 %% literal "%" character
981 %H changeset hash (40 bytes of hexadecimal)
981 %H changeset hash (40 bytes of hexadecimal)
982 %N number of patches being generated
982 %N number of patches being generated
983 %R changeset revision number
983 %R changeset revision number
984 %b basename of the exporting repository
984 %b basename of the exporting repository
985 %h short-form changeset hash (12 bytes of hexadecimal)
985 %h short-form changeset hash (12 bytes of hexadecimal)
986 %n zero-padded sequence number, starting at 1
986 %n zero-padded sequence number, starting at 1
987 %r zero-padded changeset revision number
987 %r zero-padded changeset revision number
988
988
989 Without the -a option, export will avoid generating diffs of files
989 Without the -a option, export will avoid generating diffs of files
990 it detects as binary. With -a, export will generate a diff anyway,
990 it detects as binary. With -a, export will generate a diff anyway,
991 probably with undesirable results.
991 probably with undesirable results.
992
992
993 With the --switch-parent option, the diff will be against the second
993 With the --switch-parent option, the diff will be against the second
994 parent. It can be useful to review a merge.
994 parent. It can be useful to review a merge.
995 """
995 """
996 if not changesets:
996 if not changesets:
997 raise util.Abort(_("export requires at least one changeset"))
997 raise util.Abort(_("export requires at least one changeset"))
998 revs = cmdutil.revrange(repo, changesets)
998 revs = cmdutil.revrange(repo, changesets)
999 if len(revs) > 1:
999 if len(revs) > 1:
1000 ui.note(_('exporting patches:\n'))
1000 ui.note(_('exporting patches:\n'))
1001 else:
1001 else:
1002 ui.note(_('exporting patch:\n'))
1002 ui.note(_('exporting patch:\n'))
1003 patch.export(repo, revs, template=opts['output'],
1003 patch.export(repo, revs, template=opts['output'],
1004 switch_parent=opts['switch_parent'],
1004 switch_parent=opts['switch_parent'],
1005 opts=patch.diffopts(ui, opts))
1005 opts=patch.diffopts(ui, opts))
1006
1006
1007 def grep(ui, repo, pattern, *pats, **opts):
1007 def grep(ui, repo, pattern, *pats, **opts):
1008 """search for a pattern in specified files and revisions
1008 """search for a pattern in specified files and revisions
1009
1009
1010 Search revisions of files for a regular expression.
1010 Search revisions of files for a regular expression.
1011
1011
1012 This command behaves differently than Unix grep. It only accepts
1012 This command behaves differently than Unix grep. It only accepts
1013 Python/Perl regexps. It searches repository history, not the
1013 Python/Perl regexps. It searches repository history, not the
1014 working directory. It always prints the revision number in which
1014 working directory. It always prints the revision number in which
1015 a match appears.
1015 a match appears.
1016
1016
1017 By default, grep only prints output for the first revision of a
1017 By default, grep only prints output for the first revision of a
1018 file in which it finds a match. To get it to print every revision
1018 file in which it finds a match. To get it to print every revision
1019 that contains a change in match status ("-" for a match that
1019 that contains a change in match status ("-" for a match that
1020 becomes a non-match, or "+" for a non-match that becomes a match),
1020 becomes a non-match, or "+" for a non-match that becomes a match),
1021 use the --all flag.
1021 use the --all flag.
1022 """
1022 """
1023 reflags = 0
1023 reflags = 0
1024 if opts['ignore_case']:
1024 if opts['ignore_case']:
1025 reflags |= re.I
1025 reflags |= re.I
1026 try:
1026 try:
1027 regexp = re.compile(pattern, reflags)
1027 regexp = re.compile(pattern, reflags)
1028 except Exception, inst:
1028 except Exception, inst:
1029 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1029 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1030 return None
1030 return None
1031 sep, eol = ':', '\n'
1031 sep, eol = ':', '\n'
1032 if opts['print0']:
1032 if opts['print0']:
1033 sep = eol = '\0'
1033 sep = eol = '\0'
1034
1034
1035 fcache = {}
1035 fcache = {}
1036 def getfile(fn):
1036 def getfile(fn):
1037 if fn not in fcache:
1037 if fn not in fcache:
1038 fcache[fn] = repo.file(fn)
1038 fcache[fn] = repo.file(fn)
1039 return fcache[fn]
1039 return fcache[fn]
1040
1040
1041 def matchlines(body):
1041 def matchlines(body):
1042 begin = 0
1042 begin = 0
1043 linenum = 0
1043 linenum = 0
1044 while True:
1044 while True:
1045 match = regexp.search(body, begin)
1045 match = regexp.search(body, begin)
1046 if not match:
1046 if not match:
1047 break
1047 break
1048 mstart, mend = match.span()
1048 mstart, mend = match.span()
1049 linenum += body.count('\n', begin, mstart) + 1
1049 linenum += body.count('\n', begin, mstart) + 1
1050 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1050 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1051 lend = body.find('\n', mend)
1051 lend = body.find('\n', mend)
1052 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1052 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1053 begin = lend + 1
1053 begin = lend + 1
1054
1054
1055 class linestate(object):
1055 class linestate(object):
1056 def __init__(self, line, linenum, colstart, colend):
1056 def __init__(self, line, linenum, colstart, colend):
1057 self.line = line
1057 self.line = line
1058 self.linenum = linenum
1058 self.linenum = linenum
1059 self.colstart = colstart
1059 self.colstart = colstart
1060 self.colend = colend
1060 self.colend = colend
1061
1061
1062 def __hash__(self):
1062 def __hash__(self):
1063 return hash((self.linenum, self.line))
1063 return hash((self.linenum, self.line))
1064
1064
1065 def __eq__(self, other):
1065 def __eq__(self, other):
1066 return self.line == other.line
1066 return self.line == other.line
1067
1067
1068 matches = {}
1068 matches = {}
1069 copies = {}
1069 copies = {}
1070 def grepbody(fn, rev, body):
1070 def grepbody(fn, rev, body):
1071 matches[rev].setdefault(fn, [])
1071 matches[rev].setdefault(fn, [])
1072 m = matches[rev][fn]
1072 m = matches[rev][fn]
1073 for lnum, cstart, cend, line in matchlines(body):
1073 for lnum, cstart, cend, line in matchlines(body):
1074 s = linestate(line, lnum, cstart, cend)
1074 s = linestate(line, lnum, cstart, cend)
1075 m.append(s)
1075 m.append(s)
1076
1076
1077 def difflinestates(a, b):
1077 def difflinestates(a, b):
1078 sm = difflib.SequenceMatcher(None, a, b)
1078 sm = difflib.SequenceMatcher(None, a, b)
1079 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1079 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1080 if tag == 'insert':
1080 if tag == 'insert':
1081 for i in xrange(blo, bhi):
1081 for i in xrange(blo, bhi):
1082 yield ('+', b[i])
1082 yield ('+', b[i])
1083 elif tag == 'delete':
1083 elif tag == 'delete':
1084 for i in xrange(alo, ahi):
1084 for i in xrange(alo, ahi):
1085 yield ('-', a[i])
1085 yield ('-', a[i])
1086 elif tag == 'replace':
1086 elif tag == 'replace':
1087 for i in xrange(alo, ahi):
1087 for i in xrange(alo, ahi):
1088 yield ('-', a[i])
1088 yield ('-', a[i])
1089 for i in xrange(blo, bhi):
1089 for i in xrange(blo, bhi):
1090 yield ('+', b[i])
1090 yield ('+', b[i])
1091
1091
1092 prev = {}
1092 prev = {}
1093 def display(fn, rev, states, prevstates):
1093 def display(fn, rev, states, prevstates):
1094 datefunc = ui.quiet and util.shortdate or util.datestr
1094 datefunc = ui.quiet and util.shortdate or util.datestr
1095 found = False
1095 found = False
1096 filerevmatches = {}
1096 filerevmatches = {}
1097 r = prev.get(fn, -1)
1097 r = prev.get(fn, -1)
1098 if opts['all']:
1098 if opts['all']:
1099 iter = difflinestates(states, prevstates)
1099 iter = difflinestates(states, prevstates)
1100 else:
1100 else:
1101 iter = [('', l) for l in prevstates]
1101 iter = [('', l) for l in prevstates]
1102 for change, l in iter:
1102 for change, l in iter:
1103 cols = [fn, str(r)]
1103 cols = [fn, str(r)]
1104 if opts['line_number']:
1104 if opts['line_number']:
1105 cols.append(str(l.linenum))
1105 cols.append(str(l.linenum))
1106 if opts['all']:
1106 if opts['all']:
1107 cols.append(change)
1107 cols.append(change)
1108 if opts['user']:
1108 if opts['user']:
1109 cols.append(ui.shortuser(get(r)[1]))
1109 cols.append(ui.shortuser(get(r)[1]))
1110 if opts.get('date'):
1110 if opts.get('date'):
1111 cols.append(datefunc(get(r)[2]))
1111 cols.append(datefunc(get(r)[2]))
1112 if opts['files_with_matches']:
1112 if opts['files_with_matches']:
1113 c = (fn, r)
1113 c = (fn, r)
1114 if c in filerevmatches:
1114 if c in filerevmatches:
1115 continue
1115 continue
1116 filerevmatches[c] = 1
1116 filerevmatches[c] = 1
1117 else:
1117 else:
1118 cols.append(l.line)
1118 cols.append(l.line)
1119 ui.write(sep.join(cols), eol)
1119 ui.write(sep.join(cols), eol)
1120 found = True
1120 found = True
1121 return found
1121 return found
1122
1122
1123 fstate = {}
1123 fstate = {}
1124 skip = {}
1124 skip = {}
1125 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1125 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1126 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1126 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1127 found = False
1127 found = False
1128 follow = opts.get('follow')
1128 follow = opts.get('follow')
1129 for st, rev, fns in changeiter:
1129 for st, rev, fns in changeiter:
1130 if st == 'window':
1130 if st == 'window':
1131 matches.clear()
1131 matches.clear()
1132 elif st == 'add':
1132 elif st == 'add':
1133 ctx = repo.changectx(rev)
1133 ctx = repo.changectx(rev)
1134 matches[rev] = {}
1134 matches[rev] = {}
1135 for fn in fns:
1135 for fn in fns:
1136 if fn in skip:
1136 if fn in skip:
1137 continue
1137 continue
1138 try:
1138 try:
1139 grepbody(fn, rev, getfile(fn).read(ctx.filenode(fn)))
1139 grepbody(fn, rev, getfile(fn).read(ctx.filenode(fn)))
1140 fstate.setdefault(fn, [])
1140 fstate.setdefault(fn, [])
1141 if follow:
1141 if follow:
1142 copied = getfile(fn).renamed(ctx.filenode(fn))
1142 copied = getfile(fn).renamed(ctx.filenode(fn))
1143 if copied:
1143 if copied:
1144 copies.setdefault(rev, {})[fn] = copied[0]
1144 copies.setdefault(rev, {})[fn] = copied[0]
1145 except revlog.LookupError:
1145 except revlog.LookupError:
1146 pass
1146 pass
1147 elif st == 'iter':
1147 elif st == 'iter':
1148 states = matches[rev].items()
1148 states = matches[rev].items()
1149 states.sort()
1149 states.sort()
1150 for fn, m in states:
1150 for fn, m in states:
1151 copy = copies.get(rev, {}).get(fn)
1151 copy = copies.get(rev, {}).get(fn)
1152 if fn in skip:
1152 if fn in skip:
1153 if copy:
1153 if copy:
1154 skip[copy] = True
1154 skip[copy] = True
1155 continue
1155 continue
1156 if fn in prev or fstate[fn]:
1156 if fn in prev or fstate[fn]:
1157 r = display(fn, rev, m, fstate[fn])
1157 r = display(fn, rev, m, fstate[fn])
1158 found = found or r
1158 found = found or r
1159 if r and not opts['all']:
1159 if r and not opts['all']:
1160 skip[fn] = True
1160 skip[fn] = True
1161 if copy:
1161 if copy:
1162 skip[copy] = True
1162 skip[copy] = True
1163 fstate[fn] = m
1163 fstate[fn] = m
1164 if copy:
1164 if copy:
1165 fstate[copy] = m
1165 fstate[copy] = m
1166 prev[fn] = rev
1166 prev[fn] = rev
1167
1167
1168 fstate = fstate.items()
1168 fstate = fstate.items()
1169 fstate.sort()
1169 fstate.sort()
1170 for fn, state in fstate:
1170 for fn, state in fstate:
1171 if fn in skip:
1171 if fn in skip:
1172 continue
1172 continue
1173 if fn not in copies.get(prev[fn], {}):
1173 if fn not in copies.get(prev[fn], {}):
1174 found = display(fn, rev, {}, state) or found
1174 found = display(fn, rev, {}, state) or found
1175 return (not found and 1) or 0
1175 return (not found and 1) or 0
1176
1176
1177 def heads(ui, repo, *branchrevs, **opts):
1177 def heads(ui, repo, *branchrevs, **opts):
1178 """show current repository heads or show branch heads
1178 """show current repository heads or show branch heads
1179
1179
1180 With no arguments, show all repository head changesets.
1180 With no arguments, show all repository head changesets.
1181
1181
1182 If branch or revisions names are given this will show the heads of
1182 If branch or revisions names are given this will show the heads of
1183 the specified branches or the branches those revisions are tagged
1183 the specified branches or the branches those revisions are tagged
1184 with.
1184 with.
1185
1185
1186 Repository "heads" are changesets that don't have child
1186 Repository "heads" are changesets that don't have child
1187 changesets. They are where development generally takes place and
1187 changesets. They are where development generally takes place and
1188 are the usual targets for update and merge operations.
1188 are the usual targets for update and merge operations.
1189
1189
1190 Branch heads are changesets that have a given branch tag, but have
1190 Branch heads are changesets that have a given branch tag, but have
1191 no child changesets with that tag. They are usually where
1191 no child changesets with that tag. They are usually where
1192 development on the given branch takes place.
1192 development on the given branch takes place.
1193 """
1193 """
1194 if opts['rev']:
1194 if opts['rev']:
1195 start = repo.lookup(opts['rev'])
1195 start = repo.lookup(opts['rev'])
1196 else:
1196 else:
1197 start = None
1197 start = None
1198 if not branchrevs:
1198 if not branchrevs:
1199 # Assume we're looking repo-wide heads if no revs were specified.
1199 # Assume we're looking repo-wide heads if no revs were specified.
1200 heads = repo.heads(start)
1200 heads = repo.heads(start)
1201 else:
1201 else:
1202 heads = []
1202 heads = []
1203 visitedset = util.set()
1203 visitedset = util.set()
1204 for branchrev in branchrevs:
1204 for branchrev in branchrevs:
1205 branch = repo.changectx(branchrev).branch()
1205 branch = repo.changectx(branchrev).branch()
1206 if branch in visitedset:
1206 if branch in visitedset:
1207 continue
1207 continue
1208 visitedset.add(branch)
1208 visitedset.add(branch)
1209 bheads = repo.branchheads(branch, start)
1209 bheads = repo.branchheads(branch, start)
1210 if not bheads:
1210 if not bheads:
1211 if branch != branchrev:
1211 if branch != branchrev:
1212 ui.warn(_("no changes on branch %s containing %s are "
1212 ui.warn(_("no changes on branch %s containing %s are "
1213 "reachable from %s\n")
1213 "reachable from %s\n")
1214 % (branch, branchrev, opts['rev']))
1214 % (branch, branchrev, opts['rev']))
1215 else:
1215 else:
1216 ui.warn(_("no changes on branch %s are reachable from %s\n")
1216 ui.warn(_("no changes on branch %s are reachable from %s\n")
1217 % (branch, opts['rev']))
1217 % (branch, opts['rev']))
1218 heads.extend(bheads)
1218 heads.extend(bheads)
1219 if not heads:
1219 if not heads:
1220 return 1
1220 return 1
1221 displayer = cmdutil.show_changeset(ui, repo, opts)
1221 displayer = cmdutil.show_changeset(ui, repo, opts)
1222 for n in heads:
1222 for n in heads:
1223 displayer.show(changenode=n)
1223 displayer.show(changenode=n)
1224
1224
1225 def help_(ui, name=None, with_version=False):
1225 def help_(ui, name=None, with_version=False):
1226 """show help for a command, extension, or list of commands
1226 """show help for a command, extension, or list of commands
1227
1227
1228 With no arguments, print a list of commands and short help.
1228 With no arguments, print a list of commands and short help.
1229
1229
1230 Given a command name, print help for that command.
1230 Given a command name, print help for that command.
1231
1231
1232 Given an extension name, print help for that extension, and the
1232 Given an extension name, print help for that extension, and the
1233 commands it provides."""
1233 commands it provides."""
1234 option_lists = []
1234 option_lists = []
1235
1235
1236 def addglobalopts(aliases):
1236 def addglobalopts(aliases):
1237 if ui.verbose:
1237 if ui.verbose:
1238 option_lists.append((_("global options:"), globalopts))
1238 option_lists.append((_("global options:"), globalopts))
1239 if name == 'shortlist':
1239 if name == 'shortlist':
1240 option_lists.append((_('use "hg help" for the full list '
1240 option_lists.append((_('use "hg help" for the full list '
1241 'of commands'), ()))
1241 'of commands'), ()))
1242 else:
1242 else:
1243 if name == 'shortlist':
1243 if name == 'shortlist':
1244 msg = _('use "hg help" for the full list of commands '
1244 msg = _('use "hg help" for the full list of commands '
1245 'or "hg -v" for details')
1245 'or "hg -v" for details')
1246 elif aliases:
1246 elif aliases:
1247 msg = _('use "hg -v help%s" to show aliases and '
1247 msg = _('use "hg -v help%s" to show aliases and '
1248 'global options') % (name and " " + name or "")
1248 'global options') % (name and " " + name or "")
1249 else:
1249 else:
1250 msg = _('use "hg -v help %s" to show global options') % name
1250 msg = _('use "hg -v help %s" to show global options') % name
1251 option_lists.append((msg, ()))
1251 option_lists.append((msg, ()))
1252
1252
1253 def helpcmd(name):
1253 def helpcmd(name):
1254 if with_version:
1254 if with_version:
1255 version_(ui)
1255 version_(ui)
1256 ui.write('\n')
1256 ui.write('\n')
1257 aliases, i = cmdutil.findcmd(ui, name, table)
1257 aliases, i = cmdutil.findcmd(ui, name, table)
1258 # synopsis
1258 # synopsis
1259 ui.write("%s\n" % i[2])
1259 ui.write("%s\n" % i[2])
1260
1260
1261 # aliases
1261 # aliases
1262 if not ui.quiet and len(aliases) > 1:
1262 if not ui.quiet and len(aliases) > 1:
1263 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1263 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1264
1264
1265 # description
1265 # description
1266 doc = i[0].__doc__
1266 doc = i[0].__doc__
1267 if not doc:
1267 if not doc:
1268 doc = _("(No help text available)")
1268 doc = _("(No help text available)")
1269 if ui.quiet:
1269 if ui.quiet:
1270 doc = doc.splitlines(0)[0]
1270 doc = doc.splitlines(0)[0]
1271 ui.write("\n%s\n" % doc.rstrip())
1271 ui.write("\n%s\n" % doc.rstrip())
1272
1272
1273 if not ui.quiet:
1273 if not ui.quiet:
1274 # options
1274 # options
1275 if i[1]:
1275 if i[1]:
1276 option_lists.append((_("options:\n"), i[1]))
1276 option_lists.append((_("options:\n"), i[1]))
1277
1277
1278 addglobalopts(False)
1278 addglobalopts(False)
1279
1279
1280 def helplist(header, select=None):
1280 def helplist(header, select=None):
1281 h = {}
1281 h = {}
1282 cmds = {}
1282 cmds = {}
1283 for c, e in table.items():
1283 for c, e in table.items():
1284 f = c.split("|", 1)[0]
1284 f = c.split("|", 1)[0]
1285 if select and not select(f):
1285 if select and not select(f):
1286 continue
1286 continue
1287 if name == "shortlist" and not f.startswith("^"):
1287 if name == "shortlist" and not f.startswith("^"):
1288 continue
1288 continue
1289 f = f.lstrip("^")
1289 f = f.lstrip("^")
1290 if not ui.debugflag and f.startswith("debug"):
1290 if not ui.debugflag and f.startswith("debug"):
1291 continue
1291 continue
1292 doc = e[0].__doc__
1292 doc = e[0].__doc__
1293 if not doc:
1293 if not doc:
1294 doc = _("(No help text available)")
1294 doc = _("(No help text available)")
1295 h[f] = doc.splitlines(0)[0].rstrip()
1295 h[f] = doc.splitlines(0)[0].rstrip()
1296 cmds[f] = c.lstrip("^")
1296 cmds[f] = c.lstrip("^")
1297
1297
1298 if not h:
1298 if not h:
1299 ui.status(_('no commands defined\n'))
1299 ui.status(_('no commands defined\n'))
1300 return
1300 return
1301
1301
1302 ui.status(header)
1302 ui.status(header)
1303 fns = h.keys()
1303 fns = h.keys()
1304 fns.sort()
1304 fns.sort()
1305 m = max(map(len, fns))
1305 m = max(map(len, fns))
1306 for f in fns:
1306 for f in fns:
1307 if ui.verbose:
1307 if ui.verbose:
1308 commands = cmds[f].replace("|",", ")
1308 commands = cmds[f].replace("|",", ")
1309 ui.write(" %s:\n %s\n"%(commands, h[f]))
1309 ui.write(" %s:\n %s\n"%(commands, h[f]))
1310 else:
1310 else:
1311 ui.write(' %-*s %s\n' % (m, f, h[f]))
1311 ui.write(' %-*s %s\n' % (m, f, h[f]))
1312
1312
1313 if not ui.quiet:
1313 if not ui.quiet:
1314 addglobalopts(True)
1314 addglobalopts(True)
1315
1315
1316 def helptopic(name):
1316 def helptopic(name):
1317 v = None
1317 v = None
1318 for i in help.helptable:
1318 for i in help.helptable:
1319 l = i.split('|')
1319 l = i.split('|')
1320 if name in l:
1320 if name in l:
1321 v = i
1321 v = i
1322 header = l[-1]
1322 header = l[-1]
1323 if not v:
1323 if not v:
1324 raise cmdutil.UnknownCommand(name)
1324 raise cmdutil.UnknownCommand(name)
1325
1325
1326 # description
1326 # description
1327 doc = help.helptable[v]
1327 doc = help.helptable[v]
1328 if not doc:
1328 if not doc:
1329 doc = _("(No help text available)")
1329 doc = _("(No help text available)")
1330 if callable(doc):
1330 if callable(doc):
1331 doc = doc()
1331 doc = doc()
1332
1332
1333 ui.write("%s\n" % header)
1333 ui.write("%s\n" % header)
1334 ui.write("%s\n" % doc.rstrip())
1334 ui.write("%s\n" % doc.rstrip())
1335
1335
1336 def helpext(name):
1336 def helpext(name):
1337 try:
1337 try:
1338 mod = extensions.find(name)
1338 mod = extensions.find(name)
1339 except KeyError:
1339 except KeyError:
1340 raise cmdutil.UnknownCommand(name)
1340 raise cmdutil.UnknownCommand(name)
1341
1341
1342 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1342 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1343 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1343 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1344 for d in doc[1:]:
1344 for d in doc[1:]:
1345 ui.write(d, '\n')
1345 ui.write(d, '\n')
1346
1346
1347 ui.status('\n')
1347 ui.status('\n')
1348
1348
1349 try:
1349 try:
1350 ct = mod.cmdtable
1350 ct = mod.cmdtable
1351 except AttributeError:
1351 except AttributeError:
1352 ct = {}
1352 ct = {}
1353
1353
1354 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1354 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1355 helplist(_('list of commands:\n\n'), modcmds.has_key)
1355 helplist(_('list of commands:\n\n'), modcmds.has_key)
1356
1356
1357 if name and name != 'shortlist':
1357 if name and name != 'shortlist':
1358 i = None
1358 i = None
1359 for f in (helpcmd, helptopic, helpext):
1359 for f in (helpcmd, helptopic, helpext):
1360 try:
1360 try:
1361 f(name)
1361 f(name)
1362 i = None
1362 i = None
1363 break
1363 break
1364 except cmdutil.UnknownCommand, inst:
1364 except cmdutil.UnknownCommand, inst:
1365 i = inst
1365 i = inst
1366 if i:
1366 if i:
1367 raise i
1367 raise i
1368
1368
1369 else:
1369 else:
1370 # program name
1370 # program name
1371 if ui.verbose or with_version:
1371 if ui.verbose or with_version:
1372 version_(ui)
1372 version_(ui)
1373 else:
1373 else:
1374 ui.status(_("Mercurial Distributed SCM\n"))
1374 ui.status(_("Mercurial Distributed SCM\n"))
1375 ui.status('\n')
1375 ui.status('\n')
1376
1376
1377 # list of commands
1377 # list of commands
1378 if name == "shortlist":
1378 if name == "shortlist":
1379 header = _('basic commands:\n\n')
1379 header = _('basic commands:\n\n')
1380 else:
1380 else:
1381 header = _('list of commands:\n\n')
1381 header = _('list of commands:\n\n')
1382
1382
1383 helplist(header)
1383 helplist(header)
1384
1384
1385 # list all option lists
1385 # list all option lists
1386 opt_output = []
1386 opt_output = []
1387 for title, options in option_lists:
1387 for title, options in option_lists:
1388 opt_output.append(("\n%s" % title, None))
1388 opt_output.append(("\n%s" % title, None))
1389 for shortopt, longopt, default, desc in options:
1389 for shortopt, longopt, default, desc in options:
1390 if "DEPRECATED" in desc and not ui.verbose: continue
1390 if "DEPRECATED" in desc and not ui.verbose: continue
1391 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1391 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1392 longopt and " --%s" % longopt),
1392 longopt and " --%s" % longopt),
1393 "%s%s" % (desc,
1393 "%s%s" % (desc,
1394 default
1394 default
1395 and _(" (default: %s)") % default
1395 and _(" (default: %s)") % default
1396 or "")))
1396 or "")))
1397
1397
1398 if opt_output:
1398 if opt_output:
1399 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1399 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1400 for first, second in opt_output:
1400 for first, second in opt_output:
1401 if second:
1401 if second:
1402 ui.write(" %-*s %s\n" % (opts_len, first, second))
1402 ui.write(" %-*s %s\n" % (opts_len, first, second))
1403 else:
1403 else:
1404 ui.write("%s\n" % first)
1404 ui.write("%s\n" % first)
1405
1405
1406 def identify(ui, repo, source=None,
1406 def identify(ui, repo, source=None,
1407 rev=None, num=None, id=None, branch=None, tags=None):
1407 rev=None, num=None, id=None, branch=None, tags=None):
1408 """identify the working copy or specified revision
1408 """identify the working copy or specified revision
1409
1409
1410 With no revision, print a summary of the current state of the repo.
1410 With no revision, print a summary of the current state of the repo.
1411
1411
1412 With a path, do a lookup in another repository.
1412 With a path, do a lookup in another repository.
1413
1413
1414 This summary identifies the repository state using one or two parent
1414 This summary identifies the repository state using one or two parent
1415 hash identifiers, followed by a "+" if there are uncommitted changes
1415 hash identifiers, followed by a "+" if there are uncommitted changes
1416 in the working directory, a list of tags for this revision and a branch
1416 in the working directory, a list of tags for this revision and a branch
1417 name for non-default branches.
1417 name for non-default branches.
1418 """
1418 """
1419
1419
1420 if not repo and not source:
1420 if not repo and not source:
1421 raise util.Abort(_("There is no Mercurial repository here "
1421 raise util.Abort(_("There is no Mercurial repository here "
1422 "(.hg not found)"))
1422 "(.hg not found)"))
1423
1423
1424 hexfunc = ui.debugflag and hex or short
1424 hexfunc = ui.debugflag and hex or short
1425 default = not (num or id or branch or tags)
1425 default = not (num or id or branch or tags)
1426 output = []
1426 output = []
1427
1427
1428 if source:
1428 if source:
1429 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1429 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1430 srepo = hg.repository(ui, source)
1430 srepo = hg.repository(ui, source)
1431 if not rev and revs:
1431 if not rev and revs:
1432 rev = revs[0]
1432 rev = revs[0]
1433 if not rev:
1433 if not rev:
1434 rev = "tip"
1434 rev = "tip"
1435 if num or branch or tags:
1435 if num or branch or tags:
1436 raise util.Abort(
1436 raise util.Abort(
1437 "can't query remote revision number, branch, or tags")
1437 "can't query remote revision number, branch, or tags")
1438 output = [hexfunc(srepo.lookup(rev))]
1438 output = [hexfunc(srepo.lookup(rev))]
1439 elif not rev:
1439 elif not rev:
1440 ctx = repo.workingctx()
1440 ctx = repo.workingctx()
1441 parents = ctx.parents()
1441 parents = ctx.parents()
1442 changed = False
1442 changed = False
1443 if default or id or num:
1443 if default or id or num:
1444 changed = ctx.files() + ctx.deleted()
1444 changed = ctx.files() + ctx.deleted()
1445 if default or id:
1445 if default or id:
1446 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1446 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1447 (changed) and "+" or "")]
1447 (changed) and "+" or "")]
1448 if num:
1448 if num:
1449 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1449 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1450 (changed) and "+" or ""))
1450 (changed) and "+" or ""))
1451 else:
1451 else:
1452 ctx = repo.changectx(rev)
1452 ctx = repo.changectx(rev)
1453 if default or id:
1453 if default or id:
1454 output = [hexfunc(ctx.node())]
1454 output = [hexfunc(ctx.node())]
1455 if num:
1455 if num:
1456 output.append(str(ctx.rev()))
1456 output.append(str(ctx.rev()))
1457
1457
1458 if not source and default and not ui.quiet:
1458 if not source and default and not ui.quiet:
1459 b = util.tolocal(ctx.branch())
1459 b = util.tolocal(ctx.branch())
1460 if b != 'default':
1460 if b != 'default':
1461 output.append("(%s)" % b)
1461 output.append("(%s)" % b)
1462
1462
1463 # multiple tags for a single parent separated by '/'
1463 # multiple tags for a single parent separated by '/'
1464 t = "/".join(ctx.tags())
1464 t = "/".join(ctx.tags())
1465 if t:
1465 if t:
1466 output.append(t)
1466 output.append(t)
1467
1467
1468 if branch:
1468 if branch:
1469 output.append(util.tolocal(ctx.branch()))
1469 output.append(util.tolocal(ctx.branch()))
1470
1470
1471 if tags:
1471 if tags:
1472 output.extend(ctx.tags())
1472 output.extend(ctx.tags())
1473
1473
1474 ui.write("%s\n" % ' '.join(output))
1474 ui.write("%s\n" % ' '.join(output))
1475
1475
1476 def import_(ui, repo, patch1, *patches, **opts):
1476 def import_(ui, repo, patch1, *patches, **opts):
1477 """import an ordered set of patches
1477 """import an ordered set of patches
1478
1478
1479 Import a list of patches and commit them individually.
1479 Import a list of patches and commit them individually.
1480
1480
1481 If there are outstanding changes in the working directory, import
1481 If there are outstanding changes in the working directory, import
1482 will abort unless given the -f flag.
1482 will abort unless given the -f flag.
1483
1483
1484 You can import a patch straight from a mail message. Even patches
1484 You can import a patch straight from a mail message. Even patches
1485 as attachments work (body part must be type text/plain or
1485 as attachments work (body part must be type text/plain or
1486 text/x-patch to be used). From and Subject headers of email
1486 text/x-patch to be used). From and Subject headers of email
1487 message are used as default committer and commit message. All
1487 message are used as default committer and commit message. All
1488 text/plain body parts before first diff are added to commit
1488 text/plain body parts before first diff are added to commit
1489 message.
1489 message.
1490
1490
1491 If the imported patch was generated by hg export, user and description
1491 If the imported patch was generated by hg export, user and description
1492 from patch override values from message headers and body. Values
1492 from patch override values from message headers and body. Values
1493 given on command line with -m and -u override these.
1493 given on command line with -m and -u override these.
1494
1494
1495 If --exact is specified, import will set the working directory
1495 If --exact is specified, import will set the working directory
1496 to the parent of each patch before applying it, and will abort
1496 to the parent of each patch before applying it, and will abort
1497 if the resulting changeset has a different ID than the one
1497 if the resulting changeset has a different ID than the one
1498 recorded in the patch. This may happen due to character set
1498 recorded in the patch. This may happen due to character set
1499 problems or other deficiencies in the text patch format.
1499 problems or other deficiencies in the text patch format.
1500
1500
1501 To read a patch from standard input, use patch name "-".
1501 To read a patch from standard input, use patch name "-".
1502 See 'hg help dates' for a list of formats valid for -d/--date.
1502 See 'hg help dates' for a list of formats valid for -d/--date.
1503 """
1503 """
1504 patches = (patch1,) + patches
1504 patches = (patch1,) + patches
1505
1505
1506 date = opts.get('date')
1506 date = opts.get('date')
1507 if date:
1507 if date:
1508 opts['date'] = util.parsedate(date)
1508 opts['date'] = util.parsedate(date)
1509
1509
1510 if opts.get('exact') or not opts['force']:
1510 if opts.get('exact') or not opts['force']:
1511 cmdutil.bail_if_changed(repo)
1511 cmdutil.bail_if_changed(repo)
1512
1512
1513 d = opts["base"]
1513 d = opts["base"]
1514 strip = opts["strip"]
1514 strip = opts["strip"]
1515 wlock = lock = None
1515 wlock = lock = None
1516 try:
1516 try:
1517 wlock = repo.wlock()
1517 wlock = repo.wlock()
1518 lock = repo.lock()
1518 lock = repo.lock()
1519 for p in patches:
1519 for p in patches:
1520 pf = os.path.join(d, p)
1520 pf = os.path.join(d, p)
1521
1521
1522 if pf == '-':
1522 if pf == '-':
1523 ui.status(_("applying patch from stdin\n"))
1523 ui.status(_("applying patch from stdin\n"))
1524 data = patch.extract(ui, sys.stdin)
1524 data = patch.extract(ui, sys.stdin)
1525 else:
1525 else:
1526 ui.status(_("applying %s\n") % p)
1526 ui.status(_("applying %s\n") % p)
1527 if os.path.exists(pf):
1527 if os.path.exists(pf):
1528 data = patch.extract(ui, file(pf, 'rb'))
1528 data = patch.extract(ui, file(pf, 'rb'))
1529 else:
1529 else:
1530 data = patch.extract(ui, urllib.urlopen(pf))
1530 data = patch.extract(ui, urllib.urlopen(pf))
1531 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1531 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1532
1532
1533 if tmpname is None:
1533 if tmpname is None:
1534 raise util.Abort(_('no diffs found'))
1534 raise util.Abort(_('no diffs found'))
1535
1535
1536 try:
1536 try:
1537 cmdline_message = cmdutil.logmessage(opts)
1537 cmdline_message = cmdutil.logmessage(opts)
1538 if cmdline_message:
1538 if cmdline_message:
1539 # pickup the cmdline msg
1539 # pickup the cmdline msg
1540 message = cmdline_message
1540 message = cmdline_message
1541 elif message:
1541 elif message:
1542 # pickup the patch msg
1542 # pickup the patch msg
1543 message = message.strip()
1543 message = message.strip()
1544 else:
1544 else:
1545 # launch the editor
1545 # launch the editor
1546 message = None
1546 message = None
1547 ui.debug(_('message:\n%s\n') % message)
1547 ui.debug(_('message:\n%s\n') % message)
1548
1548
1549 wp = repo.workingctx().parents()
1549 wp = repo.workingctx().parents()
1550 if opts.get('exact'):
1550 if opts.get('exact'):
1551 if not nodeid or not p1:
1551 if not nodeid or not p1:
1552 raise util.Abort(_('not a mercurial patch'))
1552 raise util.Abort(_('not a mercurial patch'))
1553 p1 = repo.lookup(p1)
1553 p1 = repo.lookup(p1)
1554 p2 = repo.lookup(p2 or hex(nullid))
1554 p2 = repo.lookup(p2 or hex(nullid))
1555
1555
1556 if p1 != wp[0].node():
1556 if p1 != wp[0].node():
1557 hg.clean(repo, p1)
1557 hg.clean(repo, p1)
1558 repo.dirstate.setparents(p1, p2)
1558 repo.dirstate.setparents(p1, p2)
1559 elif p2:
1559 elif p2:
1560 try:
1560 try:
1561 p1 = repo.lookup(p1)
1561 p1 = repo.lookup(p1)
1562 p2 = repo.lookup(p2)
1562 p2 = repo.lookup(p2)
1563 if p1 == wp[0].node():
1563 if p1 == wp[0].node():
1564 repo.dirstate.setparents(p1, p2)
1564 repo.dirstate.setparents(p1, p2)
1565 except RepoError:
1565 except RepoError:
1566 pass
1566 pass
1567 if opts.get('exact') or opts.get('import_branch'):
1567 if opts.get('exact') or opts.get('import_branch'):
1568 repo.dirstate.setbranch(branch or 'default')
1568 repo.dirstate.setbranch(branch or 'default')
1569
1569
1570 files = {}
1570 files = {}
1571 try:
1571 try:
1572 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1572 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1573 files=files)
1573 files=files)
1574 finally:
1574 finally:
1575 files = patch.updatedir(ui, repo, files)
1575 files = patch.updatedir(ui, repo, files)
1576 if not opts.get('no_commit'):
1576 if not opts.get('no_commit'):
1577 n = repo.commit(files, message, opts.get('user') or user,
1577 n = repo.commit(files, message, opts.get('user') or user,
1578 opts.get('date') or date)
1578 opts.get('date') or date)
1579 if opts.get('exact'):
1579 if opts.get('exact'):
1580 if hex(n) != nodeid:
1580 if hex(n) != nodeid:
1581 repo.rollback()
1581 repo.rollback()
1582 raise util.Abort(_('patch is damaged'
1582 raise util.Abort(_('patch is damaged'
1583 ' or loses information'))
1583 ' or loses information'))
1584 # Force a dirstate write so that the next transaction
1584 # Force a dirstate write so that the next transaction
1585 # backups an up-do-date file.
1585 # backups an up-do-date file.
1586 repo.dirstate.write()
1586 repo.dirstate.write()
1587 finally:
1587 finally:
1588 os.unlink(tmpname)
1588 os.unlink(tmpname)
1589 finally:
1589 finally:
1590 del lock, wlock
1590 del lock, wlock
1591
1591
1592 def incoming(ui, repo, source="default", **opts):
1592 def incoming(ui, repo, source="default", **opts):
1593 """show new changesets found in source
1593 """show new changesets found in source
1594
1594
1595 Show new changesets found in the specified path/URL or the default
1595 Show new changesets found in the specified path/URL or the default
1596 pull location. These are the changesets that would be pulled if a pull
1596 pull location. These are the changesets that would be pulled if a pull
1597 was requested.
1597 was requested.
1598
1598
1599 For remote repository, using --bundle avoids downloading the changesets
1599 For remote repository, using --bundle avoids downloading the changesets
1600 twice if the incoming is followed by a pull.
1600 twice if the incoming is followed by a pull.
1601
1601
1602 See pull for valid source format details.
1602 See pull for valid source format details.
1603 """
1603 """
1604 limit = cmdutil.loglimit(opts)
1604 limit = cmdutil.loglimit(opts)
1605 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
1605 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
1606 cmdutil.setremoteconfig(ui, opts)
1606 cmdutil.setremoteconfig(ui, opts)
1607
1607
1608 other = hg.repository(ui, source)
1608 other = hg.repository(ui, source)
1609 ui.status(_('comparing with %s\n') % util.hidepassword(source))
1609 ui.status(_('comparing with %s\n') % util.hidepassword(source))
1610 if revs:
1610 if revs:
1611 revs = [other.lookup(rev) for rev in revs]
1611 revs = [other.lookup(rev) for rev in revs]
1612 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1612 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1613 if not incoming:
1613 if not incoming:
1614 try:
1614 try:
1615 os.unlink(opts["bundle"])
1615 os.unlink(opts["bundle"])
1616 except:
1616 except:
1617 pass
1617 pass
1618 ui.status(_("no changes found\n"))
1618 ui.status(_("no changes found\n"))
1619 return 1
1619 return 1
1620
1620
1621 cleanup = None
1621 cleanup = None
1622 try:
1622 try:
1623 fname = opts["bundle"]
1623 fname = opts["bundle"]
1624 if fname or not other.local():
1624 if fname or not other.local():
1625 # create a bundle (uncompressed if other repo is not local)
1625 # create a bundle (uncompressed if other repo is not local)
1626 if revs is None:
1626 if revs is None:
1627 cg = other.changegroup(incoming, "incoming")
1627 cg = other.changegroup(incoming, "incoming")
1628 else:
1628 else:
1629 cg = other.changegroupsubset(incoming, revs, 'incoming')
1629 cg = other.changegroupsubset(incoming, revs, 'incoming')
1630 bundletype = other.local() and "HG10BZ" or "HG10UN"
1630 bundletype = other.local() and "HG10BZ" or "HG10UN"
1631 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1631 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1632 # keep written bundle?
1632 # keep written bundle?
1633 if opts["bundle"]:
1633 if opts["bundle"]:
1634 cleanup = None
1634 cleanup = None
1635 if not other.local():
1635 if not other.local():
1636 # use the created uncompressed bundlerepo
1636 # use the created uncompressed bundlerepo
1637 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1637 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1638
1638
1639 o = other.changelog.nodesbetween(incoming, revs)[0]
1639 o = other.changelog.nodesbetween(incoming, revs)[0]
1640 if opts['newest_first']:
1640 if opts['newest_first']:
1641 o.reverse()
1641 o.reverse()
1642 displayer = cmdutil.show_changeset(ui, other, opts)
1642 displayer = cmdutil.show_changeset(ui, other, opts)
1643 count = 0
1643 count = 0
1644 for n in o:
1644 for n in o:
1645 if count >= limit:
1645 if count >= limit:
1646 break
1646 break
1647 parents = [p for p in other.changelog.parents(n) if p != nullid]
1647 parents = [p for p in other.changelog.parents(n) if p != nullid]
1648 if opts['no_merges'] and len(parents) == 2:
1648 if opts['no_merges'] and len(parents) == 2:
1649 continue
1649 continue
1650 count += 1
1650 count += 1
1651 displayer.show(changenode=n)
1651 displayer.show(changenode=n)
1652 finally:
1652 finally:
1653 if hasattr(other, 'close'):
1653 if hasattr(other, 'close'):
1654 other.close()
1654 other.close()
1655 if cleanup:
1655 if cleanup:
1656 os.unlink(cleanup)
1656 os.unlink(cleanup)
1657
1657
1658 def init(ui, dest=".", **opts):
1658 def init(ui, dest=".", **opts):
1659 """create a new repository in the given directory
1659 """create a new repository in the given directory
1660
1660
1661 Initialize a new repository in the given directory. If the given
1661 Initialize a new repository in the given directory. If the given
1662 directory does not exist, it is created.
1662 directory does not exist, it is created.
1663
1663
1664 If no directory is given, the current directory is used.
1664 If no directory is given, the current directory is used.
1665
1665
1666 It is possible to specify an ssh:// URL as the destination.
1666 It is possible to specify an ssh:// URL as the destination.
1667 Look at the help text for the pull command for important details
1667 Look at the help text for the pull command for important details
1668 about ssh:// URLs.
1668 about ssh:// URLs.
1669 """
1669 """
1670 cmdutil.setremoteconfig(ui, opts)
1670 cmdutil.setremoteconfig(ui, opts)
1671 hg.repository(ui, dest, create=1)
1671 hg.repository(ui, dest, create=1)
1672
1672
1673 def locate(ui, repo, *pats, **opts):
1673 def locate(ui, repo, *pats, **opts):
1674 """locate files matching specific patterns
1674 """locate files matching specific patterns
1675
1675
1676 Print all files under Mercurial control whose names match the
1676 Print all files under Mercurial control whose names match the
1677 given patterns.
1677 given patterns.
1678
1678
1679 This command searches the entire repository by default. To search
1679 This command searches the entire repository by default. To search
1680 just the current directory and its subdirectories, use
1680 just the current directory and its subdirectories, use
1681 "--include .".
1681 "--include .".
1682
1682
1683 If no patterns are given to match, this command prints all file
1683 If no patterns are given to match, this command prints all file
1684 names.
1684 names.
1685
1685
1686 If you want to feed the output of this command into the "xargs"
1686 If you want to feed the output of this command into the "xargs"
1687 command, use the "-0" option to both this command and "xargs".
1687 command, use the "-0" option to both this command and "xargs".
1688 This will avoid the problem of "xargs" treating single filenames
1688 This will avoid the problem of "xargs" treating single filenames
1689 that contain white space as multiple filenames.
1689 that contain white space as multiple filenames.
1690 """
1690 """
1691 end = opts['print0'] and '\0' or '\n'
1691 end = opts['print0'] and '\0' or '\n'
1692 rev = opts['rev']
1692 rev = opts['rev']
1693 if rev:
1693 if rev:
1694 node = repo.lookup(rev)
1694 node = repo.lookup(rev)
1695 else:
1695 else:
1696 node = None
1696 node = None
1697
1697
1698 ret = 1
1698 ret = 1
1699 m = cmdutil.match(repo, pats, opts, default='relglob')
1699 m = cmdutil.match(repo, pats, opts, default='relglob')
1700 m.bad = lambda x,y: False
1700 m.bad = lambda x,y: False
1701 for abs in repo.walk(m, node):
1701 for abs in repo.walk(m, node):
1702 if not node and abs not in repo.dirstate:
1702 if not node and abs not in repo.dirstate:
1703 continue
1703 continue
1704 if opts['fullpath']:
1704 if opts['fullpath']:
1705 ui.write(os.path.join(repo.root, abs), end)
1705 ui.write(os.path.join(repo.root, abs), end)
1706 else:
1706 else:
1707 ui.write(((pats and m.rel(abs)) or abs), end)
1707 ui.write(((pats and m.rel(abs)) or abs), end)
1708 ret = 0
1708 ret = 0
1709
1709
1710 return ret
1710 return ret
1711
1711
1712 def log(ui, repo, *pats, **opts):
1712 def log(ui, repo, *pats, **opts):
1713 """show revision history of entire repository or files
1713 """show revision history of entire repository or files
1714
1714
1715 Print the revision history of the specified files or the entire
1715 Print the revision history of the specified files or the entire
1716 project.
1716 project.
1717
1717
1718 File history is shown without following rename or copy history of
1718 File history is shown without following rename or copy history of
1719 files. Use -f/--follow with a file name to follow history across
1719 files. Use -f/--follow with a file name to follow history across
1720 renames and copies. --follow without a file name will only show
1720 renames and copies. --follow without a file name will only show
1721 ancestors or descendants of the starting revision. --follow-first
1721 ancestors or descendants of the starting revision. --follow-first
1722 only follows the first parent of merge revisions.
1722 only follows the first parent of merge revisions.
1723
1723
1724 If no revision range is specified, the default is tip:0 unless
1724 If no revision range is specified, the default is tip:0 unless
1725 --follow is set, in which case the working directory parent is
1725 --follow is set, in which case the working directory parent is
1726 used as the starting revision.
1726 used as the starting revision.
1727
1727
1728 See 'hg help dates' for a list of formats valid for -d/--date.
1728 See 'hg help dates' for a list of formats valid for -d/--date.
1729
1729
1730 By default this command outputs: changeset id and hash, tags,
1730 By default this command outputs: changeset id and hash, tags,
1731 non-trivial parents, user, date and time, and a summary for each
1731 non-trivial parents, user, date and time, and a summary for each
1732 commit. When the -v/--verbose switch is used, the list of changed
1732 commit. When the -v/--verbose switch is used, the list of changed
1733 files and full commit message is shown.
1733 files and full commit message is shown.
1734
1734
1735 NOTE: log -p may generate unexpected diff output for merge
1735 NOTE: log -p may generate unexpected diff output for merge
1736 changesets, as it will compare the merge changeset against its
1736 changesets, as it will compare the merge changeset against its
1737 first parent only. Also, the files: list will only reflect files
1737 first parent only. Also, the files: list will only reflect files
1738 that are different from BOTH parents.
1738 that are different from BOTH parents.
1739
1739
1740 """
1740 """
1741
1741
1742 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1742 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1743 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1743 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1744
1744
1745 limit = cmdutil.loglimit(opts)
1745 limit = cmdutil.loglimit(opts)
1746 count = 0
1746 count = 0
1747
1747
1748 if opts['copies'] and opts['rev']:
1748 if opts['copies'] and opts['rev']:
1749 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1749 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1750 else:
1750 else:
1751 endrev = repo.changelog.count()
1751 endrev = repo.changelog.count()
1752 rcache = {}
1752 rcache = {}
1753 ncache = {}
1753 ncache = {}
1754 def getrenamed(fn, rev):
1754 def getrenamed(fn, rev):
1755 '''looks up all renames for a file (up to endrev) the first
1755 '''looks up all renames for a file (up to endrev) the first
1756 time the file is given. It indexes on the changerev and only
1756 time the file is given. It indexes on the changerev and only
1757 parses the manifest if linkrev != changerev.
1757 parses the manifest if linkrev != changerev.
1758 Returns rename info for fn at changerev rev.'''
1758 Returns rename info for fn at changerev rev.'''
1759 if fn not in rcache:
1759 if fn not in rcache:
1760 rcache[fn] = {}
1760 rcache[fn] = {}
1761 ncache[fn] = {}
1761 ncache[fn] = {}
1762 fl = repo.file(fn)
1762 fl = repo.file(fn)
1763 for i in xrange(fl.count()):
1763 for i in xrange(fl.count()):
1764 node = fl.node(i)
1764 node = fl.node(i)
1765 lr = fl.linkrev(node)
1765 lr = fl.linkrev(node)
1766 renamed = fl.renamed(node)
1766 renamed = fl.renamed(node)
1767 rcache[fn][lr] = renamed
1767 rcache[fn][lr] = renamed
1768 if renamed:
1768 if renamed:
1769 ncache[fn][node] = renamed
1769 ncache[fn][node] = renamed
1770 if lr >= endrev:
1770 if lr >= endrev:
1771 break
1771 break
1772 if rev in rcache[fn]:
1772 if rev in rcache[fn]:
1773 return rcache[fn][rev]
1773 return rcache[fn][rev]
1774
1774
1775 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1775 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1776 # filectx logic.
1776 # filectx logic.
1777
1777
1778 try:
1778 try:
1779 return repo.changectx(rev).filectx(fn).renamed()
1779 return repo.changectx(rev).filectx(fn).renamed()
1780 except revlog.LookupError:
1780 except revlog.LookupError:
1781 pass
1781 pass
1782 return None
1782 return None
1783
1783
1784 df = False
1784 df = False
1785 if opts["date"]:
1785 if opts["date"]:
1786 df = util.matchdate(opts["date"])
1786 df = util.matchdate(opts["date"])
1787
1787
1788 only_branches = opts['only_branch']
1788 only_branches = opts['only_branch']
1789
1789
1790 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1790 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1791 for st, rev, fns in changeiter:
1791 for st, rev, fns in changeiter:
1792 if st == 'add':
1792 if st == 'add':
1793 changenode = repo.changelog.node(rev)
1793 changenode = repo.changelog.node(rev)
1794 parents = [p for p in repo.changelog.parentrevs(rev)
1794 parents = [p for p in repo.changelog.parentrevs(rev)
1795 if p != nullrev]
1795 if p != nullrev]
1796 if opts['no_merges'] and len(parents) == 2:
1796 if opts['no_merges'] and len(parents) == 2:
1797 continue
1797 continue
1798 if opts['only_merges'] and len(parents) != 2:
1798 if opts['only_merges'] and len(parents) != 2:
1799 continue
1799 continue
1800
1800
1801 if only_branches:
1801 if only_branches:
1802 revbranch = get(rev)[5]['branch']
1802 revbranch = get(rev)[5]['branch']
1803 if revbranch not in only_branches:
1803 if revbranch not in only_branches:
1804 continue
1804 continue
1805
1805
1806 if df:
1806 if df:
1807 changes = get(rev)
1807 changes = get(rev)
1808 if not df(changes[2][0]):
1808 if not df(changes[2][0]):
1809 continue
1809 continue
1810
1810
1811 if opts['keyword']:
1811 if opts['keyword']:
1812 changes = get(rev)
1812 changes = get(rev)
1813 miss = 0
1813 miss = 0
1814 for k in [kw.lower() for kw in opts['keyword']]:
1814 for k in [kw.lower() for kw in opts['keyword']]:
1815 if not (k in changes[1].lower() or
1815 if not (k in changes[1].lower() or
1816 k in changes[4].lower() or
1816 k in changes[4].lower() or
1817 k in " ".join(changes[3]).lower()):
1817 k in " ".join(changes[3]).lower()):
1818 miss = 1
1818 miss = 1
1819 break
1819 break
1820 if miss:
1820 if miss:
1821 continue
1821 continue
1822
1822
1823 copies = []
1823 copies = []
1824 if opts.get('copies') and rev:
1824 if opts.get('copies') and rev:
1825 for fn in get(rev)[3]:
1825 for fn in get(rev)[3]:
1826 rename = getrenamed(fn, rev)
1826 rename = getrenamed(fn, rev)
1827 if rename:
1827 if rename:
1828 copies.append((fn, rename[0]))
1828 copies.append((fn, rename[0]))
1829 displayer.show(rev, changenode, copies=copies)
1829 displayer.show(rev, changenode, copies=copies)
1830 elif st == 'iter':
1830 elif st == 'iter':
1831 if count == limit: break
1831 if count == limit: break
1832 if displayer.flush(rev):
1832 if displayer.flush(rev):
1833 count += 1
1833 count += 1
1834
1834
1835 def manifest(ui, repo, node=None, rev=None):
1835 def manifest(ui, repo, node=None, rev=None):
1836 """output the current or given revision of the project manifest
1836 """output the current or given revision of the project manifest
1837
1837
1838 Print a list of version controlled files for the given revision.
1838 Print a list of version controlled files for the given revision.
1839 If no revision is given, the parent of the working directory is used,
1839 If no revision is given, the parent of the working directory is used,
1840 or tip if no revision is checked out.
1840 or tip if no revision is checked out.
1841
1841
1842 The manifest is the list of files being version controlled. If no revision
1842 The manifest is the list of files being version controlled. If no revision
1843 is given then the first parent of the working directory is used.
1843 is given then the first parent of the working directory is used.
1844
1844
1845 With -v flag, print file permissions, symlink and executable bits. With
1845 With -v flag, print file permissions, symlink and executable bits. With
1846 --debug flag, print file revision hashes.
1846 --debug flag, print file revision hashes.
1847 """
1847 """
1848
1848
1849 if rev and node:
1849 if rev and node:
1850 raise util.Abort(_("please specify just one revision"))
1850 raise util.Abort(_("please specify just one revision"))
1851
1851
1852 if not node:
1852 if not node:
1853 node = rev
1853 node = rev
1854
1854
1855 m = repo.changectx(node).manifest()
1855 m = repo.changectx(node).manifest()
1856 files = m.keys()
1856 files = m.keys()
1857 files.sort()
1857 files.sort()
1858
1858
1859 for f in files:
1859 for f in files:
1860 if ui.debugflag:
1860 if ui.debugflag:
1861 ui.write("%40s " % hex(m[f]))
1861 ui.write("%40s " % hex(m[f]))
1862 if ui.verbose:
1862 if ui.verbose:
1863 type = m.execf(f) and "*" or m.linkf(f) and "@" or " "
1863 type = m.execf(f) and "*" or m.linkf(f) and "@" or " "
1864 perm = m.execf(f) and "755" or "644"
1864 perm = m.execf(f) and "755" or "644"
1865 ui.write("%3s %1s " % (perm, type))
1865 ui.write("%3s %1s " % (perm, type))
1866 ui.write("%s\n" % f)
1866 ui.write("%s\n" % f)
1867
1867
1868 def merge(ui, repo, node=None, force=None, rev=None):
1868 def merge(ui, repo, node=None, force=None, rev=None):
1869 """merge working directory with another revision
1869 """merge working directory with another revision
1870
1870
1871 Merge the contents of the current working directory and the
1871 Merge the contents of the current working directory and the
1872 requested revision. Files that changed between either parent are
1872 requested revision. Files that changed between either parent are
1873 marked as changed for the next commit and a commit must be
1873 marked as changed for the next commit and a commit must be
1874 performed before any further updates are allowed.
1874 performed before any further updates are allowed.
1875
1875
1876 If no revision is specified, the working directory's parent is a
1876 If no revision is specified, the working directory's parent is a
1877 head revision, and the repository contains exactly one other head,
1877 head revision, and the repository contains exactly one other head,
1878 the other head is merged with by default. Otherwise, an explicit
1878 the other head is merged with by default. Otherwise, an explicit
1879 revision to merge with must be provided.
1879 revision to merge with must be provided.
1880 """
1880 """
1881
1881
1882 if rev and node:
1882 if rev and node:
1883 raise util.Abort(_("please specify just one revision"))
1883 raise util.Abort(_("please specify just one revision"))
1884 if not node:
1884 if not node:
1885 node = rev
1885 node = rev
1886
1886
1887 if not node:
1887 if not node:
1888 heads = repo.heads()
1888 heads = repo.heads()
1889 if len(heads) > 2:
1889 if len(heads) > 2:
1890 raise util.Abort(_('repo has %d heads - '
1890 raise util.Abort(_('repo has %d heads - '
1891 'please merge with an explicit rev') %
1891 'please merge with an explicit rev') %
1892 len(heads))
1892 len(heads))
1893 parent = repo.dirstate.parents()[0]
1893 parent = repo.dirstate.parents()[0]
1894 if len(heads) == 1:
1894 if len(heads) == 1:
1895 msg = _('there is nothing to merge')
1895 msg = _('there is nothing to merge')
1896 if parent != repo.lookup(repo.workingctx().branch()):
1896 if parent != repo.lookup(repo.workingctx().branch()):
1897 msg = _('%s - use "hg update" instead') % msg
1897 msg = _('%s - use "hg update" instead') % msg
1898 raise util.Abort(msg)
1898 raise util.Abort(msg)
1899
1899
1900 if parent not in heads:
1900 if parent not in heads:
1901 raise util.Abort(_('working dir not at a head rev - '
1901 raise util.Abort(_('working dir not at a head rev - '
1902 'use "hg update" or merge with an explicit rev'))
1902 'use "hg update" or merge with an explicit rev'))
1903 node = parent == heads[0] and heads[-1] or heads[0]
1903 node = parent == heads[0] and heads[-1] or heads[0]
1904 return hg.merge(repo, node, force=force)
1904 return hg.merge(repo, node, force=force)
1905
1905
1906 def outgoing(ui, repo, dest=None, **opts):
1906 def outgoing(ui, repo, dest=None, **opts):
1907 """show changesets not found in destination
1907 """show changesets not found in destination
1908
1908
1909 Show changesets not found in the specified destination repository or
1909 Show changesets not found in the specified destination repository or
1910 the default push location. These are the changesets that would be pushed
1910 the default push location. These are the changesets that would be pushed
1911 if a push was requested.
1911 if a push was requested.
1912
1912
1913 See pull for valid destination format details.
1913 See pull for valid destination format details.
1914 """
1914 """
1915 limit = cmdutil.loglimit(opts)
1915 limit = cmdutil.loglimit(opts)
1916 dest, revs, checkout = hg.parseurl(
1916 dest, revs, checkout = hg.parseurl(
1917 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1917 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1918 cmdutil.setremoteconfig(ui, opts)
1918 cmdutil.setremoteconfig(ui, opts)
1919 if revs:
1919 if revs:
1920 revs = [repo.lookup(rev) for rev in revs]
1920 revs = [repo.lookup(rev) for rev in revs]
1921
1921
1922 other = hg.repository(ui, dest)
1922 other = hg.repository(ui, dest)
1923 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
1923 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
1924 o = repo.findoutgoing(other, force=opts['force'])
1924 o = repo.findoutgoing(other, force=opts['force'])
1925 if not o:
1925 if not o:
1926 ui.status(_("no changes found\n"))
1926 ui.status(_("no changes found\n"))
1927 return 1
1927 return 1
1928 o = repo.changelog.nodesbetween(o, revs)[0]
1928 o = repo.changelog.nodesbetween(o, revs)[0]
1929 if opts['newest_first']:
1929 if opts['newest_first']:
1930 o.reverse()
1930 o.reverse()
1931 displayer = cmdutil.show_changeset(ui, repo, opts)
1931 displayer = cmdutil.show_changeset(ui, repo, opts)
1932 count = 0
1932 count = 0
1933 for n in o:
1933 for n in o:
1934 if count >= limit:
1934 if count >= limit:
1935 break
1935 break
1936 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1936 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1937 if opts['no_merges'] and len(parents) == 2:
1937 if opts['no_merges'] and len(parents) == 2:
1938 continue
1938 continue
1939 count += 1
1939 count += 1
1940 displayer.show(changenode=n)
1940 displayer.show(changenode=n)
1941
1941
1942 def parents(ui, repo, file_=None, **opts):
1942 def parents(ui, repo, file_=None, **opts):
1943 """show the parents of the working dir or revision
1943 """show the parents of the working dir or revision
1944
1944
1945 Print the working directory's parent revisions. If a
1945 Print the working directory's parent revisions. If a
1946 revision is given via --rev, the parent of that revision
1946 revision is given via --rev, the parent of that revision
1947 will be printed. If a file argument is given, revision in
1947 will be printed. If a file argument is given, revision in
1948 which the file was last changed (before the working directory
1948 which the file was last changed (before the working directory
1949 revision or the argument to --rev if given) is printed.
1949 revision or the argument to --rev if given) is printed.
1950 """
1950 """
1951 rev = opts.get('rev')
1951 rev = opts.get('rev')
1952 if rev:
1952 if rev:
1953 ctx = repo.changectx(rev)
1953 ctx = repo.changectx(rev)
1954 else:
1954 else:
1955 ctx = repo.workingctx()
1955 ctx = repo.workingctx()
1956
1956
1957 if file_:
1957 if file_:
1958 m = cmdutil.match(repo, (file_,), opts)
1958 m = cmdutil.match(repo, (file_,), opts)
1959 if m.anypats() or len(m.files()) != 1:
1959 if m.anypats() or len(m.files()) != 1:
1960 raise util.Abort(_('can only specify an explicit file name'))
1960 raise util.Abort(_('can only specify an explicit file name'))
1961 file_ = m.files()[0]
1961 file_ = m.files()[0]
1962 filenodes = []
1962 filenodes = []
1963 for cp in ctx.parents():
1963 for cp in ctx.parents():
1964 if not cp:
1964 if not cp:
1965 continue
1965 continue
1966 try:
1966 try:
1967 filenodes.append(cp.filenode(file_))
1967 filenodes.append(cp.filenode(file_))
1968 except revlog.LookupError:
1968 except revlog.LookupError:
1969 pass
1969 pass
1970 if not filenodes:
1970 if not filenodes:
1971 raise util.Abort(_("'%s' not found in manifest!") % file_)
1971 raise util.Abort(_("'%s' not found in manifest!") % file_)
1972 fl = repo.file(file_)
1972 fl = repo.file(file_)
1973 p = [repo.lookup(fl.linkrev(fn)) for fn in filenodes]
1973 p = [repo.lookup(fl.linkrev(fn)) for fn in filenodes]
1974 else:
1974 else:
1975 p = [cp.node() for cp in ctx.parents()]
1975 p = [cp.node() for cp in ctx.parents()]
1976
1976
1977 displayer = cmdutil.show_changeset(ui, repo, opts)
1977 displayer = cmdutil.show_changeset(ui, repo, opts)
1978 for n in p:
1978 for n in p:
1979 if n != nullid:
1979 if n != nullid:
1980 displayer.show(changenode=n)
1980 displayer.show(changenode=n)
1981
1981
1982 def paths(ui, repo, search=None):
1982 def paths(ui, repo, search=None):
1983 """show definition of symbolic path names
1983 """show definition of symbolic path names
1984
1984
1985 Show definition of symbolic path name NAME. If no name is given, show
1985 Show definition of symbolic path name NAME. If no name is given, show
1986 definition of available names.
1986 definition of available names.
1987
1987
1988 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1988 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1989 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1989 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1990 """
1990 """
1991 if search:
1991 if search:
1992 for name, path in ui.configitems("paths"):
1992 for name, path in ui.configitems("paths"):
1993 if name == search:
1993 if name == search:
1994 ui.write("%s\n" % util.hidepassword(path))
1994 ui.write("%s\n" % util.hidepassword(path))
1995 return
1995 return
1996 ui.warn(_("not found!\n"))
1996 ui.warn(_("not found!\n"))
1997 return 1
1997 return 1
1998 else:
1998 else:
1999 for name, path in ui.configitems("paths"):
1999 for name, path in ui.configitems("paths"):
2000 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
2000 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
2001
2001
2002 def postincoming(ui, repo, modheads, optupdate, checkout):
2002 def postincoming(ui, repo, modheads, optupdate, checkout):
2003 if modheads == 0:
2003 if modheads == 0:
2004 return
2004 return
2005 if optupdate:
2005 if optupdate:
2006 if modheads <= 1 or checkout:
2006 if modheads <= 1 or checkout:
2007 return hg.update(repo, checkout)
2007 return hg.update(repo, checkout)
2008 else:
2008 else:
2009 ui.status(_("not updating, since new heads added\n"))
2009 ui.status(_("not updating, since new heads added\n"))
2010 if modheads > 1:
2010 if modheads > 1:
2011 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2011 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2012 else:
2012 else:
2013 ui.status(_("(run 'hg update' to get a working copy)\n"))
2013 ui.status(_("(run 'hg update' to get a working copy)\n"))
2014
2014
2015 def pull(ui, repo, source="default", **opts):
2015 def pull(ui, repo, source="default", **opts):
2016 """pull changes from the specified source
2016 """pull changes from the specified source
2017
2017
2018 Pull changes from a remote repository to a local one.
2018 Pull changes from a remote repository to a local one.
2019
2019
2020 This finds all changes from the repository at the specified path
2020 This finds all changes from the repository at the specified path
2021 or URL and adds them to the local repository. By default, this
2021 or URL and adds them to the local repository. By default, this
2022 does not update the copy of the project in the working directory.
2022 does not update the copy of the project in the working directory.
2023
2023
2024 Valid URLs are of the form:
2024 Valid URLs are of the form:
2025
2025
2026 local/filesystem/path (or file://local/filesystem/path)
2026 local/filesystem/path (or file://local/filesystem/path)
2027 http://[user@]host[:port]/[path]
2027 http://[user@]host[:port]/[path]
2028 https://[user@]host[:port]/[path]
2028 https://[user@]host[:port]/[path]
2029 ssh://[user@]host[:port]/[path]
2029 ssh://[user@]host[:port]/[path]
2030 static-http://host[:port]/[path]
2030 static-http://host[:port]/[path]
2031
2031
2032 Paths in the local filesystem can either point to Mercurial
2032 Paths in the local filesystem can either point to Mercurial
2033 repositories or to bundle files (as created by 'hg bundle' or
2033 repositories or to bundle files (as created by 'hg bundle' or
2034 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
2034 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
2035 allows access to a Mercurial repository where you simply use a web
2035 allows access to a Mercurial repository where you simply use a web
2036 server to publish the .hg directory as static content.
2036 server to publish the .hg directory as static content.
2037
2037
2038 An optional identifier after # indicates a particular branch, tag,
2038 An optional identifier after # indicates a particular branch, tag,
2039 or changeset to pull.
2039 or changeset to pull.
2040
2040
2041 Some notes about using SSH with Mercurial:
2041 Some notes about using SSH with Mercurial:
2042 - SSH requires an accessible shell account on the destination machine
2042 - SSH requires an accessible shell account on the destination machine
2043 and a copy of hg in the remote path or specified with as remotecmd.
2043 and a copy of hg in the remote path or specified with as remotecmd.
2044 - path is relative to the remote user's home directory by default.
2044 - path is relative to the remote user's home directory by default.
2045 Use an extra slash at the start of a path to specify an absolute path:
2045 Use an extra slash at the start of a path to specify an absolute path:
2046 ssh://example.com//tmp/repository
2046 ssh://example.com//tmp/repository
2047 - Mercurial doesn't use its own compression via SSH; the right thing
2047 - Mercurial doesn't use its own compression via SSH; the right thing
2048 to do is to configure it in your ~/.ssh/config, e.g.:
2048 to do is to configure it in your ~/.ssh/config, e.g.:
2049 Host *.mylocalnetwork.example.com
2049 Host *.mylocalnetwork.example.com
2050 Compression no
2050 Compression no
2051 Host *
2051 Host *
2052 Compression yes
2052 Compression yes
2053 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2053 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2054 with the --ssh command line option.
2054 with the --ssh command line option.
2055 """
2055 """
2056 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
2056 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
2057 cmdutil.setremoteconfig(ui, opts)
2057 cmdutil.setremoteconfig(ui, opts)
2058
2058
2059 other = hg.repository(ui, source)
2059 other = hg.repository(ui, source)
2060 ui.status(_('pulling from %s\n') % util.hidepassword(source))
2060 ui.status(_('pulling from %s\n') % util.hidepassword(source))
2061 if revs:
2061 if revs:
2062 try:
2062 try:
2063 revs = [other.lookup(rev) for rev in revs]
2063 revs = [other.lookup(rev) for rev in revs]
2064 except NoCapability:
2064 except NoCapability:
2065 error = _("Other repository doesn't support revision lookup, "
2065 error = _("Other repository doesn't support revision lookup, "
2066 "so a rev cannot be specified.")
2066 "so a rev cannot be specified.")
2067 raise util.Abort(error)
2067 raise util.Abort(error)
2068
2068
2069 modheads = repo.pull(other, heads=revs, force=opts['force'])
2069 modheads = repo.pull(other, heads=revs, force=opts['force'])
2070 return postincoming(ui, repo, modheads, opts['update'], checkout)
2070 return postincoming(ui, repo, modheads, opts['update'], checkout)
2071
2071
2072 def push(ui, repo, dest=None, **opts):
2072 def push(ui, repo, dest=None, **opts):
2073 """push changes to the specified destination
2073 """push changes to the specified destination
2074
2074
2075 Push changes from the local repository to the given destination.
2075 Push changes from the local repository to the given destination.
2076
2076
2077 This is the symmetrical operation for pull. It helps to move
2077 This is the symmetrical operation for pull. It helps to move
2078 changes from the current repository to a different one. If the
2078 changes from the current repository to a different one. If the
2079 destination is local this is identical to a pull in that directory
2079 destination is local this is identical to a pull in that directory
2080 from the current one.
2080 from the current one.
2081
2081
2082 By default, push will refuse to run if it detects the result would
2082 By default, push will refuse to run if it detects the result would
2083 increase the number of remote heads. This generally indicates the
2083 increase the number of remote heads. This generally indicates the
2084 the client has forgotten to sync and merge before pushing.
2084 the client has forgotten to sync and merge before pushing.
2085
2085
2086 Valid URLs are of the form:
2086 Valid URLs are of the form:
2087
2087
2088 local/filesystem/path (or file://local/filesystem/path)
2088 local/filesystem/path (or file://local/filesystem/path)
2089 ssh://[user@]host[:port]/[path]
2089 ssh://[user@]host[:port]/[path]
2090 http://[user@]host[:port]/[path]
2090 http://[user@]host[:port]/[path]
2091 https://[user@]host[:port]/[path]
2091 https://[user@]host[:port]/[path]
2092
2092
2093 An optional identifier after # indicates a particular branch, tag,
2093 An optional identifier after # indicates a particular branch, tag,
2094 or changeset to push.
2094 or changeset to push.
2095
2095
2096 Look at the help text for the pull command for important details
2096 Look at the help text for the pull command for important details
2097 about ssh:// URLs.
2097 about ssh:// URLs.
2098
2098
2099 Pushing to http:// and https:// URLs is only possible, if this
2099 Pushing to http:// and https:// URLs is only possible, if this
2100 feature is explicitly enabled on the remote Mercurial server.
2100 feature is explicitly enabled on the remote Mercurial server.
2101 """
2101 """
2102 dest, revs, checkout = hg.parseurl(
2102 dest, revs, checkout = hg.parseurl(
2103 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2103 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2104 cmdutil.setremoteconfig(ui, opts)
2104 cmdutil.setremoteconfig(ui, opts)
2105
2105
2106 other = hg.repository(ui, dest)
2106 other = hg.repository(ui, dest)
2107 ui.status('pushing to %s\n' % util.hidepassword(dest))
2107 ui.status('pushing to %s\n' % util.hidepassword(dest))
2108 if revs:
2108 if revs:
2109 revs = [repo.lookup(rev) for rev in revs]
2109 revs = [repo.lookup(rev) for rev in revs]
2110 r = repo.push(other, opts['force'], revs=revs)
2110 r = repo.push(other, opts['force'], revs=revs)
2111 return r == 0
2111 return r == 0
2112
2112
2113 def rawcommit(ui, repo, *pats, **opts):
2113 def rawcommit(ui, repo, *pats, **opts):
2114 """raw commit interface (DEPRECATED)
2114 """raw commit interface (DEPRECATED)
2115
2115
2116 (DEPRECATED)
2116 (DEPRECATED)
2117 Lowlevel commit, for use in helper scripts.
2117 Lowlevel commit, for use in helper scripts.
2118
2118
2119 This command is not intended to be used by normal users, as it is
2119 This command is not intended to be used by normal users, as it is
2120 primarily useful for importing from other SCMs.
2120 primarily useful for importing from other SCMs.
2121
2121
2122 This command is now deprecated and will be removed in a future
2122 This command is now deprecated and will be removed in a future
2123 release, please use debugsetparents and commit instead.
2123 release, please use debugsetparents and commit instead.
2124 """
2124 """
2125
2125
2126 ui.warn(_("(the rawcommit command is deprecated)\n"))
2126 ui.warn(_("(the rawcommit command is deprecated)\n"))
2127
2127
2128 message = cmdutil.logmessage(opts)
2128 message = cmdutil.logmessage(opts)
2129
2129
2130 files = cmdutil.match(repo, pats, opts).files()
2130 files = cmdutil.match(repo, pats, opts).files()
2131 if opts['files']:
2131 if opts['files']:
2132 files += open(opts['files']).read().splitlines()
2132 files += open(opts['files']).read().splitlines()
2133
2133
2134 parents = [repo.lookup(p) for p in opts['parent']]
2134 parents = [repo.lookup(p) for p in opts['parent']]
2135
2135
2136 try:
2136 try:
2137 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2137 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2138 except ValueError, inst:
2138 except ValueError, inst:
2139 raise util.Abort(str(inst))
2139 raise util.Abort(str(inst))
2140
2140
2141 def recover(ui, repo):
2141 def recover(ui, repo):
2142 """roll back an interrupted transaction
2142 """roll back an interrupted transaction
2143
2143
2144 Recover from an interrupted commit or pull.
2144 Recover from an interrupted commit or pull.
2145
2145
2146 This command tries to fix the repository status after an interrupted
2146 This command tries to fix the repository status after an interrupted
2147 operation. It should only be necessary when Mercurial suggests it.
2147 operation. It should only be necessary when Mercurial suggests it.
2148 """
2148 """
2149 if repo.recover():
2149 if repo.recover():
2150 return hg.verify(repo)
2150 return hg.verify(repo)
2151 return 1
2151 return 1
2152
2152
2153 def remove(ui, repo, *pats, **opts):
2153 def remove(ui, repo, *pats, **opts):
2154 """remove the specified files on the next commit
2154 """remove the specified files on the next commit
2155
2155
2156 Schedule the indicated files for removal from the repository.
2156 Schedule the indicated files for removal from the repository.
2157
2157
2158 This only removes files from the current branch, not from the entire
2158 This only removes files from the current branch, not from the entire
2159 project history. -A can be used to remove only files that have already
2159 project history. -A can be used to remove only files that have already
2160 been deleted, -f can be used to force deletion, and -Af can be used
2160 been deleted, -f can be used to force deletion, and -Af can be used
2161 to remove files from the next revision without deleting them.
2161 to remove files from the next revision without deleting them.
2162
2162
2163 The following table details the behavior of remove for different file
2163 The following table details the behavior of remove for different file
2164 states (columns) and option combinations (rows). The file states are
2164 states (columns) and option combinations (rows). The file states are
2165 Added, Clean, Modified and Missing (as reported by hg status). The
2165 Added, Clean, Modified and Missing (as reported by hg status). The
2166 actions are Warn, Remove (from branch) and Delete (from disk).
2166 actions are Warn, Remove (from branch) and Delete (from disk).
2167
2167
2168 A C M !
2168 A C M !
2169 none W RD W R
2169 none W RD W R
2170 -f R RD RD R
2170 -f R RD RD R
2171 -A W W W R
2171 -A W W W R
2172 -Af R R R R
2172 -Af R R R R
2173
2173
2174 This command schedules the files to be removed at the next commit.
2174 This command schedules the files to be removed at the next commit.
2175 To undo a remove before that, see hg revert.
2175 To undo a remove before that, see hg revert.
2176 """
2176 """
2177
2177
2178 after, force = opts.get('after'), opts.get('force')
2178 after, force = opts.get('after'), opts.get('force')
2179 if not pats and not after:
2179 if not pats and not after:
2180 raise util.Abort(_('no files specified'))
2180 raise util.Abort(_('no files specified'))
2181
2181
2182 m = cmdutil.match(repo, pats, opts)
2182 m = cmdutil.match(repo, pats, opts)
2183 mardu = map(dict.fromkeys, repo.status(files=m.files(), match=m))[:5]
2183 mardu = map(dict.fromkeys, repo.status(match=m))[:5]
2184 modified, added, removed, deleted, unknown = mardu
2184 modified, added, removed, deleted, unknown = mardu
2185
2185
2186 remove, forget = [], []
2186 remove, forget = [], []
2187 for abs in repo.walk(m):
2187 for abs in repo.walk(m):
2188
2188
2189 reason = None
2189 reason = None
2190 if abs in removed or abs in unknown:
2190 if abs in removed or abs in unknown:
2191 continue
2191 continue
2192
2192
2193 # last column
2193 # last column
2194 elif abs in deleted:
2194 elif abs in deleted:
2195 remove.append(abs)
2195 remove.append(abs)
2196
2196
2197 # rest of the third row
2197 # rest of the third row
2198 elif after and not force:
2198 elif after and not force:
2199 reason = _('still exists (use -f to force removal)')
2199 reason = _('still exists (use -f to force removal)')
2200
2200
2201 # rest of the first column
2201 # rest of the first column
2202 elif abs in added:
2202 elif abs in added:
2203 if not force:
2203 if not force:
2204 reason = _('has been marked for add (use -f to force removal)')
2204 reason = _('has been marked for add (use -f to force removal)')
2205 else:
2205 else:
2206 forget.append(abs)
2206 forget.append(abs)
2207
2207
2208 # rest of the third column
2208 # rest of the third column
2209 elif abs in modified:
2209 elif abs in modified:
2210 if not force:
2210 if not force:
2211 reason = _('is modified (use -f to force removal)')
2211 reason = _('is modified (use -f to force removal)')
2212 else:
2212 else:
2213 remove.append(abs)
2213 remove.append(abs)
2214
2214
2215 # rest of the second column
2215 # rest of the second column
2216 elif not reason:
2216 elif not reason:
2217 remove.append(abs)
2217 remove.append(abs)
2218
2218
2219 if reason:
2219 if reason:
2220 ui.warn(_('not removing %s: file %s\n') % (m.rel(abs), reason))
2220 ui.warn(_('not removing %s: file %s\n') % (m.rel(abs), reason))
2221 elif ui.verbose or not m.exact(abs):
2221 elif ui.verbose or not m.exact(abs):
2222 ui.status(_('removing %s\n') % m.rel(abs))
2222 ui.status(_('removing %s\n') % m.rel(abs))
2223
2223
2224 repo.forget(forget)
2224 repo.forget(forget)
2225 repo.remove(remove, unlink=not after)
2225 repo.remove(remove, unlink=not after)
2226
2226
2227 def rename(ui, repo, *pats, **opts):
2227 def rename(ui, repo, *pats, **opts):
2228 """rename files; equivalent of copy + remove
2228 """rename files; equivalent of copy + remove
2229
2229
2230 Mark dest as copies of sources; mark sources for deletion. If
2230 Mark dest as copies of sources; mark sources for deletion. If
2231 dest is a directory, copies are put in that directory. If dest is
2231 dest is a directory, copies are put in that directory. If dest is
2232 a file, there can only be one source.
2232 a file, there can only be one source.
2233
2233
2234 By default, this command copies the contents of files as they
2234 By default, this command copies the contents of files as they
2235 stand in the working directory. If invoked with --after, the
2235 stand in the working directory. If invoked with --after, the
2236 operation is recorded, but no copying is performed.
2236 operation is recorded, but no copying is performed.
2237
2237
2238 This command takes effect in the next commit. To undo a rename
2238 This command takes effect in the next commit. To undo a rename
2239 before that, see hg revert.
2239 before that, see hg revert.
2240 """
2240 """
2241 wlock = repo.wlock(False)
2241 wlock = repo.wlock(False)
2242 try:
2242 try:
2243 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2243 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2244 finally:
2244 finally:
2245 del wlock
2245 del wlock
2246
2246
2247 def resolve(ui, repo, *pats, **opts):
2247 def resolve(ui, repo, *pats, **opts):
2248 """resolve file merges from a branch merge or update
2248 """resolve file merges from a branch merge or update
2249
2249
2250 This command will attempt to resolve unresolved merges from the
2250 This command will attempt to resolve unresolved merges from the
2251 last update or merge command. This will use the local file
2251 last update or merge command. This will use the local file
2252 revision preserved at the last update or merge to cleanly retry
2252 revision preserved at the last update or merge to cleanly retry
2253 the file merge attempt. With no file or options specified, this
2253 the file merge attempt. With no file or options specified, this
2254 command will attempt to resolve all unresolved files.
2254 command will attempt to resolve all unresolved files.
2255 """
2255 """
2256
2256
2257 if len([x for x in opts if opts[x]]) > 1:
2257 if len([x for x in opts if opts[x]]) > 1:
2258 raise util.Abort(_("too many options specified"))
2258 raise util.Abort(_("too many options specified"))
2259
2259
2260 ms = merge_.mergestate(repo)
2260 ms = merge_.mergestate(repo)
2261 m = cmdutil.match(repo, pats, opts)
2261 m = cmdutil.match(repo, pats, opts)
2262
2262
2263 for f in ms:
2263 for f in ms:
2264 if m(f):
2264 if m(f):
2265 if opts.get("list"):
2265 if opts.get("list"):
2266 ui.write("%s %s\n" % (ms[f].upper(), f))
2266 ui.write("%s %s\n" % (ms[f].upper(), f))
2267 elif opts.get("mark"):
2267 elif opts.get("mark"):
2268 ms.mark(f, "r")
2268 ms.mark(f, "r")
2269 elif opts.get("unmark"):
2269 elif opts.get("unmark"):
2270 ms.mark(f, "u")
2270 ms.mark(f, "u")
2271 else:
2271 else:
2272 wctx = repo.workingctx()
2272 wctx = repo.workingctx()
2273 mctx = wctx.parents()[-1]
2273 mctx = wctx.parents()[-1]
2274 ms.resolve(f, wctx, mctx)
2274 ms.resolve(f, wctx, mctx)
2275
2275
2276 def revert(ui, repo, *pats, **opts):
2276 def revert(ui, repo, *pats, **opts):
2277 """restore individual files or dirs to an earlier state
2277 """restore individual files or dirs to an earlier state
2278
2278
2279 (use update -r to check out earlier revisions, revert does not
2279 (use update -r to check out earlier revisions, revert does not
2280 change the working dir parents)
2280 change the working dir parents)
2281
2281
2282 With no revision specified, revert the named files or directories
2282 With no revision specified, revert the named files or directories
2283 to the contents they had in the parent of the working directory.
2283 to the contents they had in the parent of the working directory.
2284 This restores the contents of the affected files to an unmodified
2284 This restores the contents of the affected files to an unmodified
2285 state and unschedules adds, removes, copies, and renames. If the
2285 state and unschedules adds, removes, copies, and renames. If the
2286 working directory has two parents, you must explicitly specify the
2286 working directory has two parents, you must explicitly specify the
2287 revision to revert to.
2287 revision to revert to.
2288
2288
2289 Using the -r option, revert the given files or directories to their
2289 Using the -r option, revert the given files or directories to their
2290 contents as of a specific revision. This can be helpful to "roll
2290 contents as of a specific revision. This can be helpful to "roll
2291 back" some or all of an earlier change.
2291 back" some or all of an earlier change.
2292 See 'hg help dates' for a list of formats valid for -d/--date.
2292 See 'hg help dates' for a list of formats valid for -d/--date.
2293
2293
2294 Revert modifies the working directory. It does not commit any
2294 Revert modifies the working directory. It does not commit any
2295 changes, or change the parent of the working directory. If you
2295 changes, or change the parent of the working directory. If you
2296 revert to a revision other than the parent of the working
2296 revert to a revision other than the parent of the working
2297 directory, the reverted files will thus appear modified
2297 directory, the reverted files will thus appear modified
2298 afterwards.
2298 afterwards.
2299
2299
2300 If a file has been deleted, it is restored. If the executable
2300 If a file has been deleted, it is restored. If the executable
2301 mode of a file was changed, it is reset.
2301 mode of a file was changed, it is reset.
2302
2302
2303 If names are given, all files matching the names are reverted.
2303 If names are given, all files matching the names are reverted.
2304 If no arguments are given, no files are reverted.
2304 If no arguments are given, no files are reverted.
2305
2305
2306 Modified files are saved with a .orig suffix before reverting.
2306 Modified files are saved with a .orig suffix before reverting.
2307 To disable these backups, use --no-backup.
2307 To disable these backups, use --no-backup.
2308 """
2308 """
2309
2309
2310 if opts["date"]:
2310 if opts["date"]:
2311 if opts["rev"]:
2311 if opts["rev"]:
2312 raise util.Abort(_("you can't specify a revision and a date"))
2312 raise util.Abort(_("you can't specify a revision and a date"))
2313 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2313 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2314
2314
2315 if not pats and not opts['all']:
2315 if not pats and not opts['all']:
2316 raise util.Abort(_('no files or directories specified; '
2316 raise util.Abort(_('no files or directories specified; '
2317 'use --all to revert the whole repo'))
2317 'use --all to revert the whole repo'))
2318
2318
2319 parent, p2 = repo.dirstate.parents()
2319 parent, p2 = repo.dirstate.parents()
2320 if not opts['rev'] and p2 != nullid:
2320 if not opts['rev'] and p2 != nullid:
2321 raise util.Abort(_('uncommitted merge - please provide a '
2321 raise util.Abort(_('uncommitted merge - please provide a '
2322 'specific revision'))
2322 'specific revision'))
2323 ctx = repo.changectx(opts['rev'])
2323 ctx = repo.changectx(opts['rev'])
2324 node = ctx.node()
2324 node = ctx.node()
2325 mf = ctx.manifest()
2325 mf = ctx.manifest()
2326 if node == parent:
2326 if node == parent:
2327 pmf = mf
2327 pmf = mf
2328 else:
2328 else:
2329 pmf = None
2329 pmf = None
2330
2330
2331 # need all matching names in dirstate and manifest of target rev,
2331 # need all matching names in dirstate and manifest of target rev,
2332 # so have to walk both. do not print errors if files exist in one
2332 # so have to walk both. do not print errors if files exist in one
2333 # but not other.
2333 # but not other.
2334
2334
2335 names = {}
2335 names = {}
2336
2336
2337 wlock = repo.wlock()
2337 wlock = repo.wlock()
2338 try:
2338 try:
2339 # walk dirstate.
2339 # walk dirstate.
2340 files = []
2340 files = []
2341
2341
2342 m = cmdutil.match(repo, pats, opts)
2342 m = cmdutil.match(repo, pats, opts)
2343 m.bad = lambda x,y: False
2343 m.bad = lambda x,y: False
2344 for abs in repo.walk(m):
2344 for abs in repo.walk(m):
2345 names[abs] = m.rel(abs), m.exact(abs)
2345 names[abs] = m.rel(abs), m.exact(abs)
2346
2346
2347 # walk target manifest.
2347 # walk target manifest.
2348
2348
2349 def badfn(path, msg):
2349 def badfn(path, msg):
2350 if path in names:
2350 if path in names:
2351 return False
2351 return False
2352 path_ = path + '/'
2352 path_ = path + '/'
2353 for f in names:
2353 for f in names:
2354 if f.startswith(path_):
2354 if f.startswith(path_):
2355 return False
2355 return False
2356 repo.ui.warn("%s: %s\n" % (m.rel(path), msg))
2356 repo.ui.warn("%s: %s\n" % (m.rel(path), msg))
2357 return False
2357 return False
2358
2358
2359 m = cmdutil.match(repo, pats, opts)
2359 m = cmdutil.match(repo, pats, opts)
2360 m.bad = badfn
2360 m.bad = badfn
2361 for abs in repo.walk(m, node=node):
2361 for abs in repo.walk(m, node=node):
2362 if abs not in names:
2362 if abs not in names:
2363 names[abs] = m.rel(abs), m.exact(abs)
2363 names[abs] = m.rel(abs), m.exact(abs)
2364
2364
2365 m = cmdutil.matchfiles(repo, names)
2365 m = cmdutil.matchfiles(repo, names)
2366 changes = repo.status(files=m.files(), match=m)[:4]
2366 changes = repo.status(match=m)[:4]
2367 modified, added, removed, deleted = map(dict.fromkeys, changes)
2367 modified, added, removed, deleted = map(dict.fromkeys, changes)
2368
2368
2369 # if f is a rename, also revert the source
2369 # if f is a rename, also revert the source
2370 cwd = repo.getcwd()
2370 cwd = repo.getcwd()
2371 for f in added:
2371 for f in added:
2372 src = repo.dirstate.copied(f)
2372 src = repo.dirstate.copied(f)
2373 if src and src not in names and repo.dirstate[src] == 'r':
2373 if src and src not in names and repo.dirstate[src] == 'r':
2374 removed[src] = None
2374 removed[src] = None
2375 names[src] = (repo.pathto(src, cwd), True)
2375 names[src] = (repo.pathto(src, cwd), True)
2376
2376
2377 def removeforget(abs):
2377 def removeforget(abs):
2378 if repo.dirstate[abs] == 'a':
2378 if repo.dirstate[abs] == 'a':
2379 return _('forgetting %s\n')
2379 return _('forgetting %s\n')
2380 return _('removing %s\n')
2380 return _('removing %s\n')
2381
2381
2382 revert = ([], _('reverting %s\n'))
2382 revert = ([], _('reverting %s\n'))
2383 add = ([], _('adding %s\n'))
2383 add = ([], _('adding %s\n'))
2384 remove = ([], removeforget)
2384 remove = ([], removeforget)
2385 undelete = ([], _('undeleting %s\n'))
2385 undelete = ([], _('undeleting %s\n'))
2386
2386
2387 disptable = (
2387 disptable = (
2388 # dispatch table:
2388 # dispatch table:
2389 # file state
2389 # file state
2390 # action if in target manifest
2390 # action if in target manifest
2391 # action if not in target manifest
2391 # action if not in target manifest
2392 # make backup if in target manifest
2392 # make backup if in target manifest
2393 # make backup if not in target manifest
2393 # make backup if not in target manifest
2394 (modified, revert, remove, True, True),
2394 (modified, revert, remove, True, True),
2395 (added, revert, remove, True, False),
2395 (added, revert, remove, True, False),
2396 (removed, undelete, None, False, False),
2396 (removed, undelete, None, False, False),
2397 (deleted, revert, remove, False, False),
2397 (deleted, revert, remove, False, False),
2398 )
2398 )
2399
2399
2400 entries = names.items()
2400 entries = names.items()
2401 entries.sort()
2401 entries.sort()
2402
2402
2403 for abs, (rel, exact) in entries:
2403 for abs, (rel, exact) in entries:
2404 mfentry = mf.get(abs)
2404 mfentry = mf.get(abs)
2405 target = repo.wjoin(abs)
2405 target = repo.wjoin(abs)
2406 def handle(xlist, dobackup):
2406 def handle(xlist, dobackup):
2407 xlist[0].append(abs)
2407 xlist[0].append(abs)
2408 if dobackup and not opts['no_backup'] and util.lexists(target):
2408 if dobackup and not opts['no_backup'] and util.lexists(target):
2409 bakname = "%s.orig" % rel
2409 bakname = "%s.orig" % rel
2410 ui.note(_('saving current version of %s as %s\n') %
2410 ui.note(_('saving current version of %s as %s\n') %
2411 (rel, bakname))
2411 (rel, bakname))
2412 if not opts.get('dry_run'):
2412 if not opts.get('dry_run'):
2413 util.copyfile(target, bakname)
2413 util.copyfile(target, bakname)
2414 if ui.verbose or not exact:
2414 if ui.verbose or not exact:
2415 msg = xlist[1]
2415 msg = xlist[1]
2416 if not isinstance(msg, basestring):
2416 if not isinstance(msg, basestring):
2417 msg = msg(abs)
2417 msg = msg(abs)
2418 ui.status(msg % rel)
2418 ui.status(msg % rel)
2419 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2419 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2420 if abs not in table: continue
2420 if abs not in table: continue
2421 # file has changed in dirstate
2421 # file has changed in dirstate
2422 if mfentry:
2422 if mfentry:
2423 handle(hitlist, backuphit)
2423 handle(hitlist, backuphit)
2424 elif misslist is not None:
2424 elif misslist is not None:
2425 handle(misslist, backupmiss)
2425 handle(misslist, backupmiss)
2426 break
2426 break
2427 else:
2427 else:
2428 if abs not in repo.dirstate:
2428 if abs not in repo.dirstate:
2429 if mfentry:
2429 if mfentry:
2430 handle(add, True)
2430 handle(add, True)
2431 elif exact:
2431 elif exact:
2432 ui.warn(_('file not managed: %s\n') % rel)
2432 ui.warn(_('file not managed: %s\n') % rel)
2433 continue
2433 continue
2434 # file has not changed in dirstate
2434 # file has not changed in dirstate
2435 if node == parent:
2435 if node == parent:
2436 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2436 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2437 continue
2437 continue
2438 if pmf is None:
2438 if pmf is None:
2439 # only need parent manifest in this unlikely case,
2439 # only need parent manifest in this unlikely case,
2440 # so do not read by default
2440 # so do not read by default
2441 pmf = repo.changectx(parent).manifest()
2441 pmf = repo.changectx(parent).manifest()
2442 if abs in pmf:
2442 if abs in pmf:
2443 if mfentry:
2443 if mfentry:
2444 # if version of file is same in parent and target
2444 # if version of file is same in parent and target
2445 # manifests, do nothing
2445 # manifests, do nothing
2446 if (pmf[abs] != mfentry or
2446 if (pmf[abs] != mfentry or
2447 pmf.flags(abs) != mf.flags(abs)):
2447 pmf.flags(abs) != mf.flags(abs)):
2448 handle(revert, False)
2448 handle(revert, False)
2449 else:
2449 else:
2450 handle(remove, False)
2450 handle(remove, False)
2451
2451
2452 if not opts.get('dry_run'):
2452 if not opts.get('dry_run'):
2453 def checkout(f):
2453 def checkout(f):
2454 fc = ctx[f]
2454 fc = ctx[f]
2455 repo.wwrite(f, fc.data(), fc.fileflags())
2455 repo.wwrite(f, fc.data(), fc.fileflags())
2456
2456
2457 audit_path = util.path_auditor(repo.root)
2457 audit_path = util.path_auditor(repo.root)
2458 for f in remove[0]:
2458 for f in remove[0]:
2459 if repo.dirstate[f] == 'a':
2459 if repo.dirstate[f] == 'a':
2460 repo.dirstate.forget(f)
2460 repo.dirstate.forget(f)
2461 continue
2461 continue
2462 audit_path(f)
2462 audit_path(f)
2463 try:
2463 try:
2464 util.unlink(repo.wjoin(f))
2464 util.unlink(repo.wjoin(f))
2465 except OSError:
2465 except OSError:
2466 pass
2466 pass
2467 repo.dirstate.remove(f)
2467 repo.dirstate.remove(f)
2468
2468
2469 normal = None
2469 normal = None
2470 if node == parent:
2470 if node == parent:
2471 # We're reverting to our parent. If possible, we'd like status
2471 # We're reverting to our parent. If possible, we'd like status
2472 # to report the file as clean. We have to use normallookup for
2472 # to report the file as clean. We have to use normallookup for
2473 # merges to avoid losing information about merged/dirty files.
2473 # merges to avoid losing information about merged/dirty files.
2474 if p2 != nullid:
2474 if p2 != nullid:
2475 normal = repo.dirstate.normallookup
2475 normal = repo.dirstate.normallookup
2476 else:
2476 else:
2477 normal = repo.dirstate.normal
2477 normal = repo.dirstate.normal
2478 for f in revert[0]:
2478 for f in revert[0]:
2479 checkout(f)
2479 checkout(f)
2480 if normal:
2480 if normal:
2481 normal(f)
2481 normal(f)
2482
2482
2483 for f in add[0]:
2483 for f in add[0]:
2484 checkout(f)
2484 checkout(f)
2485 repo.dirstate.add(f)
2485 repo.dirstate.add(f)
2486
2486
2487 normal = repo.dirstate.normallookup
2487 normal = repo.dirstate.normallookup
2488 if node == parent and p2 == nullid:
2488 if node == parent and p2 == nullid:
2489 normal = repo.dirstate.normal
2489 normal = repo.dirstate.normal
2490 for f in undelete[0]:
2490 for f in undelete[0]:
2491 checkout(f)
2491 checkout(f)
2492 normal(f)
2492 normal(f)
2493
2493
2494 finally:
2494 finally:
2495 del wlock
2495 del wlock
2496
2496
2497 def rollback(ui, repo):
2497 def rollback(ui, repo):
2498 """roll back the last transaction
2498 """roll back the last transaction
2499
2499
2500 This command should be used with care. There is only one level of
2500 This command should be used with care. There is only one level of
2501 rollback, and there is no way to undo a rollback. It will also
2501 rollback, and there is no way to undo a rollback. It will also
2502 restore the dirstate at the time of the last transaction, losing
2502 restore the dirstate at the time of the last transaction, losing
2503 any dirstate changes since that time.
2503 any dirstate changes since that time.
2504
2504
2505 Transactions are used to encapsulate the effects of all commands
2505 Transactions are used to encapsulate the effects of all commands
2506 that create new changesets or propagate existing changesets into a
2506 that create new changesets or propagate existing changesets into a
2507 repository. For example, the following commands are transactional,
2507 repository. For example, the following commands are transactional,
2508 and their effects can be rolled back:
2508 and their effects can be rolled back:
2509
2509
2510 commit
2510 commit
2511 import
2511 import
2512 pull
2512 pull
2513 push (with this repository as destination)
2513 push (with this repository as destination)
2514 unbundle
2514 unbundle
2515
2515
2516 This command is not intended for use on public repositories. Once
2516 This command is not intended for use on public repositories. Once
2517 changes are visible for pull by other users, rolling a transaction
2517 changes are visible for pull by other users, rolling a transaction
2518 back locally is ineffective (someone else may already have pulled
2518 back locally is ineffective (someone else may already have pulled
2519 the changes). Furthermore, a race is possible with readers of the
2519 the changes). Furthermore, a race is possible with readers of the
2520 repository; for example an in-progress pull from the repository
2520 repository; for example an in-progress pull from the repository
2521 may fail if a rollback is performed.
2521 may fail if a rollback is performed.
2522 """
2522 """
2523 repo.rollback()
2523 repo.rollback()
2524
2524
2525 def root(ui, repo):
2525 def root(ui, repo):
2526 """print the root (top) of the current working dir
2526 """print the root (top) of the current working dir
2527
2527
2528 Print the root directory of the current repository.
2528 Print the root directory of the current repository.
2529 """
2529 """
2530 ui.write(repo.root + "\n")
2530 ui.write(repo.root + "\n")
2531
2531
2532 def serve(ui, repo, **opts):
2532 def serve(ui, repo, **opts):
2533 """export the repository via HTTP
2533 """export the repository via HTTP
2534
2534
2535 Start a local HTTP repository browser and pull server.
2535 Start a local HTTP repository browser and pull server.
2536
2536
2537 By default, the server logs accesses to stdout and errors to
2537 By default, the server logs accesses to stdout and errors to
2538 stderr. Use the "-A" and "-E" options to log to files.
2538 stderr. Use the "-A" and "-E" options to log to files.
2539 """
2539 """
2540
2540
2541 if opts["stdio"]:
2541 if opts["stdio"]:
2542 if repo is None:
2542 if repo is None:
2543 raise RepoError(_("There is no Mercurial repository here"
2543 raise RepoError(_("There is no Mercurial repository here"
2544 " (.hg not found)"))
2544 " (.hg not found)"))
2545 s = sshserver.sshserver(ui, repo)
2545 s = sshserver.sshserver(ui, repo)
2546 s.serve_forever()
2546 s.serve_forever()
2547
2547
2548 parentui = ui.parentui or ui
2548 parentui = ui.parentui or ui
2549 optlist = ("name templates style address port prefix ipv6"
2549 optlist = ("name templates style address port prefix ipv6"
2550 " accesslog errorlog webdir_conf certificate")
2550 " accesslog errorlog webdir_conf certificate")
2551 for o in optlist.split():
2551 for o in optlist.split():
2552 if opts[o]:
2552 if opts[o]:
2553 parentui.setconfig("web", o, str(opts[o]))
2553 parentui.setconfig("web", o, str(opts[o]))
2554 if (repo is not None) and (repo.ui != parentui):
2554 if (repo is not None) and (repo.ui != parentui):
2555 repo.ui.setconfig("web", o, str(opts[o]))
2555 repo.ui.setconfig("web", o, str(opts[o]))
2556
2556
2557 if repo is None and not ui.config("web", "webdir_conf"):
2557 if repo is None and not ui.config("web", "webdir_conf"):
2558 raise RepoError(_("There is no Mercurial repository here"
2558 raise RepoError(_("There is no Mercurial repository here"
2559 " (.hg not found)"))
2559 " (.hg not found)"))
2560
2560
2561 class service:
2561 class service:
2562 def init(self):
2562 def init(self):
2563 util.set_signal_handler()
2563 util.set_signal_handler()
2564 self.httpd = hgweb.server.create_server(parentui, repo)
2564 self.httpd = hgweb.server.create_server(parentui, repo)
2565
2565
2566 if not ui.verbose: return
2566 if not ui.verbose: return
2567
2567
2568 if self.httpd.prefix:
2568 if self.httpd.prefix:
2569 prefix = self.httpd.prefix.strip('/') + '/'
2569 prefix = self.httpd.prefix.strip('/') + '/'
2570 else:
2570 else:
2571 prefix = ''
2571 prefix = ''
2572
2572
2573 port = ':%d' % self.httpd.port
2573 port = ':%d' % self.httpd.port
2574 if port == ':80':
2574 if port == ':80':
2575 port = ''
2575 port = ''
2576
2576
2577 bindaddr = self.httpd.addr
2577 bindaddr = self.httpd.addr
2578 if bindaddr == '0.0.0.0':
2578 if bindaddr == '0.0.0.0':
2579 bindaddr = '*'
2579 bindaddr = '*'
2580 elif ':' in bindaddr: # IPv6
2580 elif ':' in bindaddr: # IPv6
2581 bindaddr = '[%s]' % bindaddr
2581 bindaddr = '[%s]' % bindaddr
2582
2582
2583 fqaddr = self.httpd.fqaddr
2583 fqaddr = self.httpd.fqaddr
2584 if ':' in fqaddr:
2584 if ':' in fqaddr:
2585 fqaddr = '[%s]' % fqaddr
2585 fqaddr = '[%s]' % fqaddr
2586 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2586 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2587 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2587 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2588
2588
2589 def run(self):
2589 def run(self):
2590 self.httpd.serve_forever()
2590 self.httpd.serve_forever()
2591
2591
2592 service = service()
2592 service = service()
2593
2593
2594 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2594 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2595
2595
2596 def status(ui, repo, *pats, **opts):
2596 def status(ui, repo, *pats, **opts):
2597 """show changed files in the working directory
2597 """show changed files in the working directory
2598
2598
2599 Show status of files in the repository. If names are given, only
2599 Show status of files in the repository. If names are given, only
2600 files that match are shown. Files that are clean or ignored or
2600 files that match are shown. Files that are clean or ignored or
2601 source of a copy/move operation, are not listed unless -c (clean),
2601 source of a copy/move operation, are not listed unless -c (clean),
2602 -i (ignored), -C (copies) or -A is given. Unless options described
2602 -i (ignored), -C (copies) or -A is given. Unless options described
2603 with "show only ..." are given, the options -mardu are used.
2603 with "show only ..." are given, the options -mardu are used.
2604
2604
2605 Option -q/--quiet hides untracked (unknown and ignored) files
2605 Option -q/--quiet hides untracked (unknown and ignored) files
2606 unless explicitly requested with -u/--unknown or -i/-ignored.
2606 unless explicitly requested with -u/--unknown or -i/-ignored.
2607
2607
2608 NOTE: status may appear to disagree with diff if permissions have
2608 NOTE: status may appear to disagree with diff if permissions have
2609 changed or a merge has occurred. The standard diff format does not
2609 changed or a merge has occurred. The standard diff format does not
2610 report permission changes and diff only reports changes relative
2610 report permission changes and diff only reports changes relative
2611 to one merge parent.
2611 to one merge parent.
2612
2612
2613 If one revision is given, it is used as the base revision.
2613 If one revision is given, it is used as the base revision.
2614 If two revisions are given, the difference between them is shown.
2614 If two revisions are given, the difference between them is shown.
2615
2615
2616 The codes used to show the status of files are:
2616 The codes used to show the status of files are:
2617 M = modified
2617 M = modified
2618 A = added
2618 A = added
2619 R = removed
2619 R = removed
2620 C = clean
2620 C = clean
2621 ! = deleted, but still tracked
2621 ! = deleted, but still tracked
2622 ? = not tracked
2622 ? = not tracked
2623 I = ignored
2623 I = ignored
2624 = the previous added file was copied from here
2624 = the previous added file was copied from here
2625 """
2625 """
2626
2626
2627 all = opts['all']
2627 all = opts['all']
2628 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2628 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2629
2629
2630 matcher = cmdutil.match(repo, pats, opts)
2630 matcher = cmdutil.match(repo, pats, opts)
2631 cwd = (pats and repo.getcwd()) or ''
2631 cwd = (pats and repo.getcwd()) or ''
2632 modified, added, removed, deleted, unknown, ignored, clean = [
2632 modified, added, removed, deleted, unknown, ignored, clean = [
2633 n for n in repo.status(node1, node2, matcher.files(), matcher,
2633 n for n in repo.status(node1, node2, matcher,
2634 list_ignored=opts['ignored']
2634 list_ignored=opts['ignored']
2635 or all and not ui.quiet,
2635 or all and not ui.quiet,
2636 list_clean=opts['clean'] or all,
2636 list_clean=opts['clean'] or all,
2637 list_unknown=opts['unknown']
2637 list_unknown=opts['unknown']
2638 or not (ui.quiet or
2638 or not (ui.quiet or
2639 opts['modified'] or
2639 opts['modified'] or
2640 opts['added'] or
2640 opts['added'] or
2641 opts['removed'] or
2641 opts['removed'] or
2642 opts['deleted'] or
2642 opts['deleted'] or
2643 opts['ignored']))]
2643 opts['ignored']))]
2644
2644
2645 changetypes = (('modified', 'M', modified),
2645 changetypes = (('modified', 'M', modified),
2646 ('added', 'A', added),
2646 ('added', 'A', added),
2647 ('removed', 'R', removed),
2647 ('removed', 'R', removed),
2648 ('deleted', '!', deleted),
2648 ('deleted', '!', deleted),
2649 ('unknown', '?', unknown),
2649 ('unknown', '?', unknown),
2650 ('ignored', 'I', ignored))
2650 ('ignored', 'I', ignored))
2651
2651
2652 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2652 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2653
2653
2654 copy = {}
2654 copy = {}
2655 showcopy = {}
2655 showcopy = {}
2656 if ((all or opts.get('copies')) and not opts.get('no_status')):
2656 if ((all or opts.get('copies')) and not opts.get('no_status')):
2657 if opts.get('rev') == []:
2657 if opts.get('rev') == []:
2658 # fast path, more correct with merge parents
2658 # fast path, more correct with merge parents
2659 showcopy = copy = repo.dirstate.copies().copy()
2659 showcopy = copy = repo.dirstate.copies().copy()
2660 else:
2660 else:
2661 ctxn = repo.changectx(nullid)
2661 ctxn = repo.changectx(nullid)
2662 ctx1 = repo.changectx(node1)
2662 ctx1 = repo.changectx(node1)
2663 ctx2 = repo.changectx(node2)
2663 ctx2 = repo.changectx(node2)
2664 if node2 is None:
2664 if node2 is None:
2665 ctx2 = repo.workingctx()
2665 ctx2 = repo.workingctx()
2666 copy, diverge = copies.copies(repo, ctx1, ctx2, ctxn)
2666 copy, diverge = copies.copies(repo, ctx1, ctx2, ctxn)
2667 for k, v in copy.items():
2667 for k, v in copy.items():
2668 copy[v] = k
2668 copy[v] = k
2669
2669
2670 end = opts['print0'] and '\0' or '\n'
2670 end = opts['print0'] and '\0' or '\n'
2671
2671
2672 for opt, char, changes in ([ct for ct in explicit_changetypes
2672 for opt, char, changes in ([ct for ct in explicit_changetypes
2673 if all or opts[ct[0]]]
2673 if all or opts[ct[0]]]
2674 or changetypes):
2674 or changetypes):
2675
2675
2676 if opts['no_status']:
2676 if opts['no_status']:
2677 format = "%%s%s" % end
2677 format = "%%s%s" % end
2678 else:
2678 else:
2679 format = "%s %%s%s" % (char, end)
2679 format = "%s %%s%s" % (char, end)
2680
2680
2681 for f in changes:
2681 for f in changes:
2682 ui.write(format % repo.pathto(f, cwd))
2682 ui.write(format % repo.pathto(f, cwd))
2683 if f in copy and (f in added or f in showcopy):
2683 if f in copy and (f in added or f in showcopy):
2684 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2684 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2685
2685
2686 def tag(ui, repo, name1, *names, **opts):
2686 def tag(ui, repo, name1, *names, **opts):
2687 """add one or more tags for the current or given revision
2687 """add one or more tags for the current or given revision
2688
2688
2689 Name a particular revision using <name>.
2689 Name a particular revision using <name>.
2690
2690
2691 Tags are used to name particular revisions of the repository and are
2691 Tags are used to name particular revisions of the repository and are
2692 very useful to compare different revisions, to go back to significant
2692 very useful to compare different revisions, to go back to significant
2693 earlier versions or to mark branch points as releases, etc.
2693 earlier versions or to mark branch points as releases, etc.
2694
2694
2695 If no revision is given, the parent of the working directory is used,
2695 If no revision is given, the parent of the working directory is used,
2696 or tip if no revision is checked out.
2696 or tip if no revision is checked out.
2697
2697
2698 To facilitate version control, distribution, and merging of tags,
2698 To facilitate version control, distribution, and merging of tags,
2699 they are stored as a file named ".hgtags" which is managed
2699 they are stored as a file named ".hgtags" which is managed
2700 similarly to other project files and can be hand-edited if
2700 similarly to other project files and can be hand-edited if
2701 necessary. The file '.hg/localtags' is used for local tags (not
2701 necessary. The file '.hg/localtags' is used for local tags (not
2702 shared among repositories).
2702 shared among repositories).
2703
2703
2704 See 'hg help dates' for a list of formats valid for -d/--date.
2704 See 'hg help dates' for a list of formats valid for -d/--date.
2705 """
2705 """
2706
2706
2707 rev_ = None
2707 rev_ = None
2708 names = (name1,) + names
2708 names = (name1,) + names
2709 if len(names) != len(dict.fromkeys(names)):
2709 if len(names) != len(dict.fromkeys(names)):
2710 raise util.Abort(_('tag names must be unique'))
2710 raise util.Abort(_('tag names must be unique'))
2711 for n in names:
2711 for n in names:
2712 if n in ['tip', '.', 'null']:
2712 if n in ['tip', '.', 'null']:
2713 raise util.Abort(_('the name \'%s\' is reserved') % n)
2713 raise util.Abort(_('the name \'%s\' is reserved') % n)
2714 if opts['rev'] and opts['remove']:
2714 if opts['rev'] and opts['remove']:
2715 raise util.Abort(_("--rev and --remove are incompatible"))
2715 raise util.Abort(_("--rev and --remove are incompatible"))
2716 if opts['rev']:
2716 if opts['rev']:
2717 rev_ = opts['rev']
2717 rev_ = opts['rev']
2718 message = opts['message']
2718 message = opts['message']
2719 if opts['remove']:
2719 if opts['remove']:
2720 expectedtype = opts['local'] and 'local' or 'global'
2720 expectedtype = opts['local'] and 'local' or 'global'
2721 for n in names:
2721 for n in names:
2722 if not repo.tagtype(n):
2722 if not repo.tagtype(n):
2723 raise util.Abort(_('tag \'%s\' does not exist') % n)
2723 raise util.Abort(_('tag \'%s\' does not exist') % n)
2724 if repo.tagtype(n) != expectedtype:
2724 if repo.tagtype(n) != expectedtype:
2725 raise util.Abort(_('tag \'%s\' is not a %s tag') %
2725 raise util.Abort(_('tag \'%s\' is not a %s tag') %
2726 (n, expectedtype))
2726 (n, expectedtype))
2727 rev_ = nullid
2727 rev_ = nullid
2728 if not message:
2728 if not message:
2729 message = _('Removed tag %s') % ', '.join(names)
2729 message = _('Removed tag %s') % ', '.join(names)
2730 elif not opts['force']:
2730 elif not opts['force']:
2731 for n in names:
2731 for n in names:
2732 if n in repo.tags():
2732 if n in repo.tags():
2733 raise util.Abort(_('tag \'%s\' already exists '
2733 raise util.Abort(_('tag \'%s\' already exists '
2734 '(use -f to force)') % n)
2734 '(use -f to force)') % n)
2735 if not rev_ and repo.dirstate.parents()[1] != nullid:
2735 if not rev_ and repo.dirstate.parents()[1] != nullid:
2736 raise util.Abort(_('uncommitted merge - please provide a '
2736 raise util.Abort(_('uncommitted merge - please provide a '
2737 'specific revision'))
2737 'specific revision'))
2738 r = repo.changectx(rev_).node()
2738 r = repo.changectx(rev_).node()
2739
2739
2740 if not message:
2740 if not message:
2741 message = (_('Added tag %s for changeset %s') %
2741 message = (_('Added tag %s for changeset %s') %
2742 (', '.join(names), short(r)))
2742 (', '.join(names), short(r)))
2743
2743
2744 date = opts.get('date')
2744 date = opts.get('date')
2745 if date:
2745 if date:
2746 date = util.parsedate(date)
2746 date = util.parsedate(date)
2747
2747
2748 repo.tag(names, r, message, opts['local'], opts['user'], date)
2748 repo.tag(names, r, message, opts['local'], opts['user'], date)
2749
2749
2750 def tags(ui, repo):
2750 def tags(ui, repo):
2751 """list repository tags
2751 """list repository tags
2752
2752
2753 List the repository tags.
2753 List the repository tags.
2754
2754
2755 This lists both regular and local tags. When the -v/--verbose switch
2755 This lists both regular and local tags. When the -v/--verbose switch
2756 is used, a third column "local" is printed for local tags.
2756 is used, a third column "local" is printed for local tags.
2757 """
2757 """
2758
2758
2759 l = repo.tagslist()
2759 l = repo.tagslist()
2760 l.reverse()
2760 l.reverse()
2761 hexfunc = ui.debugflag and hex or short
2761 hexfunc = ui.debugflag and hex or short
2762 tagtype = ""
2762 tagtype = ""
2763
2763
2764 for t, n in l:
2764 for t, n in l:
2765 if ui.quiet:
2765 if ui.quiet:
2766 ui.write("%s\n" % t)
2766 ui.write("%s\n" % t)
2767 continue
2767 continue
2768
2768
2769 try:
2769 try:
2770 hn = hexfunc(n)
2770 hn = hexfunc(n)
2771 r = "%5d:%s" % (repo.changelog.rev(n), hn)
2771 r = "%5d:%s" % (repo.changelog.rev(n), hn)
2772 except revlog.LookupError:
2772 except revlog.LookupError:
2773 r = " ?:%s" % hn
2773 r = " ?:%s" % hn
2774 else:
2774 else:
2775 spaces = " " * (30 - util.locallen(t))
2775 spaces = " " * (30 - util.locallen(t))
2776 if ui.verbose:
2776 if ui.verbose:
2777 if repo.tagtype(t) == 'local':
2777 if repo.tagtype(t) == 'local':
2778 tagtype = " local"
2778 tagtype = " local"
2779 else:
2779 else:
2780 tagtype = ""
2780 tagtype = ""
2781 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
2781 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
2782
2782
2783 def tip(ui, repo, **opts):
2783 def tip(ui, repo, **opts):
2784 """show the tip revision
2784 """show the tip revision
2785
2785
2786 The tip revision (usually just called the tip) is the most
2786 The tip revision (usually just called the tip) is the most
2787 recently added changeset in the repository, the most recently
2787 recently added changeset in the repository, the most recently
2788 changed head.
2788 changed head.
2789
2789
2790 If you have just made a commit, that commit will be the tip. If
2790 If you have just made a commit, that commit will be the tip. If
2791 you have just pulled changes from another repository, the tip of
2791 you have just pulled changes from another repository, the tip of
2792 that repository becomes the current tip. The "tip" tag is special
2792 that repository becomes the current tip. The "tip" tag is special
2793 and cannot be renamed or assigned to a different changeset.
2793 and cannot be renamed or assigned to a different changeset.
2794 """
2794 """
2795 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2795 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2796
2796
2797 def unbundle(ui, repo, fname1, *fnames, **opts):
2797 def unbundle(ui, repo, fname1, *fnames, **opts):
2798 """apply one or more changegroup files
2798 """apply one or more changegroup files
2799
2799
2800 Apply one or more compressed changegroup files generated by the
2800 Apply one or more compressed changegroup files generated by the
2801 bundle command.
2801 bundle command.
2802 """
2802 """
2803 fnames = (fname1,) + fnames
2803 fnames = (fname1,) + fnames
2804
2804
2805 lock = None
2805 lock = None
2806 try:
2806 try:
2807 lock = repo.lock()
2807 lock = repo.lock()
2808 for fname in fnames:
2808 for fname in fnames:
2809 if os.path.exists(fname):
2809 if os.path.exists(fname):
2810 f = open(fname, "rb")
2810 f = open(fname, "rb")
2811 else:
2811 else:
2812 f = urllib.urlopen(fname)
2812 f = urllib.urlopen(fname)
2813 gen = changegroup.readbundle(f, fname)
2813 gen = changegroup.readbundle(f, fname)
2814 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2814 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2815 finally:
2815 finally:
2816 del lock
2816 del lock
2817
2817
2818 return postincoming(ui, repo, modheads, opts['update'], None)
2818 return postincoming(ui, repo, modheads, opts['update'], None)
2819
2819
2820 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2820 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2821 """update working directory
2821 """update working directory
2822
2822
2823 Update the working directory to the specified revision, or the
2823 Update the working directory to the specified revision, or the
2824 tip of the current branch if none is specified.
2824 tip of the current branch if none is specified.
2825
2825
2826 If the requested revision is a descendant of the working
2826 If the requested revision is a descendant of the working
2827 directory, any outstanding changes in the working directory will
2827 directory, any outstanding changes in the working directory will
2828 be merged into the result. If it is not directly descended but is
2828 be merged into the result. If it is not directly descended but is
2829 on the same named branch, update aborts with a suggestion to use
2829 on the same named branch, update aborts with a suggestion to use
2830 merge or update -C instead.
2830 merge or update -C instead.
2831
2831
2832 If the requested revision is on a different named branch and the
2832 If the requested revision is on a different named branch and the
2833 working directory is clean, update quietly switches branches.
2833 working directory is clean, update quietly switches branches.
2834
2834
2835 See 'hg help dates' for a list of formats valid for --date.
2835 See 'hg help dates' for a list of formats valid for --date.
2836 """
2836 """
2837 if rev and node:
2837 if rev and node:
2838 raise util.Abort(_("please specify just one revision"))
2838 raise util.Abort(_("please specify just one revision"))
2839
2839
2840 if not rev:
2840 if not rev:
2841 rev = node
2841 rev = node
2842
2842
2843 if date:
2843 if date:
2844 if rev:
2844 if rev:
2845 raise util.Abort(_("you can't specify a revision and a date"))
2845 raise util.Abort(_("you can't specify a revision and a date"))
2846 rev = cmdutil.finddate(ui, repo, date)
2846 rev = cmdutil.finddate(ui, repo, date)
2847
2847
2848 if clean:
2848 if clean:
2849 return hg.clean(repo, rev)
2849 return hg.clean(repo, rev)
2850 else:
2850 else:
2851 return hg.update(repo, rev)
2851 return hg.update(repo, rev)
2852
2852
2853 def verify(ui, repo):
2853 def verify(ui, repo):
2854 """verify the integrity of the repository
2854 """verify the integrity of the repository
2855
2855
2856 Verify the integrity of the current repository.
2856 Verify the integrity of the current repository.
2857
2857
2858 This will perform an extensive check of the repository's
2858 This will perform an extensive check of the repository's
2859 integrity, validating the hashes and checksums of each entry in
2859 integrity, validating the hashes and checksums of each entry in
2860 the changelog, manifest, and tracked files, as well as the
2860 the changelog, manifest, and tracked files, as well as the
2861 integrity of their crosslinks and indices.
2861 integrity of their crosslinks and indices.
2862 """
2862 """
2863 return hg.verify(repo)
2863 return hg.verify(repo)
2864
2864
2865 def version_(ui):
2865 def version_(ui):
2866 """output version and copyright information"""
2866 """output version and copyright information"""
2867 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2867 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2868 % version.get_version())
2868 % version.get_version())
2869 ui.status(_(
2869 ui.status(_(
2870 "\nCopyright (C) 2005-2008 Matt Mackall <mpm@selenic.com> and others\n"
2870 "\nCopyright (C) 2005-2008 Matt Mackall <mpm@selenic.com> and others\n"
2871 "This is free software; see the source for copying conditions. "
2871 "This is free software; see the source for copying conditions. "
2872 "There is NO\nwarranty; "
2872 "There is NO\nwarranty; "
2873 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2873 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2874 ))
2874 ))
2875
2875
2876 # Command options and aliases are listed here, alphabetically
2876 # Command options and aliases are listed here, alphabetically
2877
2877
2878 globalopts = [
2878 globalopts = [
2879 ('R', 'repository', '',
2879 ('R', 'repository', '',
2880 _('repository root directory or symbolic path name')),
2880 _('repository root directory or symbolic path name')),
2881 ('', 'cwd', '', _('change working directory')),
2881 ('', 'cwd', '', _('change working directory')),
2882 ('y', 'noninteractive', None,
2882 ('y', 'noninteractive', None,
2883 _('do not prompt, assume \'yes\' for any required answers')),
2883 _('do not prompt, assume \'yes\' for any required answers')),
2884 ('q', 'quiet', None, _('suppress output')),
2884 ('q', 'quiet', None, _('suppress output')),
2885 ('v', 'verbose', None, _('enable additional output')),
2885 ('v', 'verbose', None, _('enable additional output')),
2886 ('', 'config', [], _('set/override config option')),
2886 ('', 'config', [], _('set/override config option')),
2887 ('', 'debug', None, _('enable debugging output')),
2887 ('', 'debug', None, _('enable debugging output')),
2888 ('', 'debugger', None, _('start debugger')),
2888 ('', 'debugger', None, _('start debugger')),
2889 ('', 'encoding', util._encoding, _('set the charset encoding')),
2889 ('', 'encoding', util._encoding, _('set the charset encoding')),
2890 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2890 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2891 ('', 'lsprof', None, _('print improved command execution profile')),
2891 ('', 'lsprof', None, _('print improved command execution profile')),
2892 ('', 'traceback', None, _('print traceback on exception')),
2892 ('', 'traceback', None, _('print traceback on exception')),
2893 ('', 'time', None, _('time how long the command takes')),
2893 ('', 'time', None, _('time how long the command takes')),
2894 ('', 'profile', None, _('print command execution profile')),
2894 ('', 'profile', None, _('print command execution profile')),
2895 ('', 'version', None, _('output version information and exit')),
2895 ('', 'version', None, _('output version information and exit')),
2896 ('h', 'help', None, _('display help and exit')),
2896 ('h', 'help', None, _('display help and exit')),
2897 ]
2897 ]
2898
2898
2899 dryrunopts = [('n', 'dry-run', None,
2899 dryrunopts = [('n', 'dry-run', None,
2900 _('do not perform actions, just print output'))]
2900 _('do not perform actions, just print output'))]
2901
2901
2902 remoteopts = [
2902 remoteopts = [
2903 ('e', 'ssh', '', _('specify ssh command to use')),
2903 ('e', 'ssh', '', _('specify ssh command to use')),
2904 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2904 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2905 ]
2905 ]
2906
2906
2907 walkopts = [
2907 walkopts = [
2908 ('I', 'include', [], _('include names matching the given patterns')),
2908 ('I', 'include', [], _('include names matching the given patterns')),
2909 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2909 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2910 ]
2910 ]
2911
2911
2912 commitopts = [
2912 commitopts = [
2913 ('m', 'message', '', _('use <text> as commit message')),
2913 ('m', 'message', '', _('use <text> as commit message')),
2914 ('l', 'logfile', '', _('read commit message from <file>')),
2914 ('l', 'logfile', '', _('read commit message from <file>')),
2915 ]
2915 ]
2916
2916
2917 commitopts2 = [
2917 commitopts2 = [
2918 ('d', 'date', '', _('record datecode as commit date')),
2918 ('d', 'date', '', _('record datecode as commit date')),
2919 ('u', 'user', '', _('record user as committer')),
2919 ('u', 'user', '', _('record user as committer')),
2920 ]
2920 ]
2921
2921
2922 templateopts = [
2922 templateopts = [
2923 ('', 'style', '', _('display using template map file')),
2923 ('', 'style', '', _('display using template map file')),
2924 ('', 'template', '', _('display with template')),
2924 ('', 'template', '', _('display with template')),
2925 ]
2925 ]
2926
2926
2927 logopts = [
2927 logopts = [
2928 ('p', 'patch', None, _('show patch')),
2928 ('p', 'patch', None, _('show patch')),
2929 ('l', 'limit', '', _('limit number of changes displayed')),
2929 ('l', 'limit', '', _('limit number of changes displayed')),
2930 ('M', 'no-merges', None, _('do not show merges')),
2930 ('M', 'no-merges', None, _('do not show merges')),
2931 ] + templateopts
2931 ] + templateopts
2932
2932
2933 table = {
2933 table = {
2934 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2934 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2935 "addremove":
2935 "addremove":
2936 (addremove,
2936 (addremove,
2937 [('s', 'similarity', '',
2937 [('s', 'similarity', '',
2938 _('guess renamed files by similarity (0<=s<=100)')),
2938 _('guess renamed files by similarity (0<=s<=100)')),
2939 ] + walkopts + dryrunopts,
2939 ] + walkopts + dryrunopts,
2940 _('hg addremove [OPTION]... [FILE]...')),
2940 _('hg addremove [OPTION]... [FILE]...')),
2941 "^annotate|blame":
2941 "^annotate|blame":
2942 (annotate,
2942 (annotate,
2943 [('r', 'rev', '', _('annotate the specified revision')),
2943 [('r', 'rev', '', _('annotate the specified revision')),
2944 ('f', 'follow', None, _('follow file copies and renames')),
2944 ('f', 'follow', None, _('follow file copies and renames')),
2945 ('a', 'text', None, _('treat all files as text')),
2945 ('a', 'text', None, _('treat all files as text')),
2946 ('u', 'user', None, _('list the author (long with -v)')),
2946 ('u', 'user', None, _('list the author (long with -v)')),
2947 ('d', 'date', None, _('list the date (short with -q)')),
2947 ('d', 'date', None, _('list the date (short with -q)')),
2948 ('n', 'number', None, _('list the revision number (default)')),
2948 ('n', 'number', None, _('list the revision number (default)')),
2949 ('c', 'changeset', None, _('list the changeset')),
2949 ('c', 'changeset', None, _('list the changeset')),
2950 ('l', 'line-number', None,
2950 ('l', 'line-number', None,
2951 _('show line number at the first appearance'))
2951 _('show line number at the first appearance'))
2952 ] + walkopts,
2952 ] + walkopts,
2953 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
2953 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
2954 "archive":
2954 "archive":
2955 (archive,
2955 (archive,
2956 [('', 'no-decode', None, _('do not pass files through decoders')),
2956 [('', 'no-decode', None, _('do not pass files through decoders')),
2957 ('p', 'prefix', '', _('directory prefix for files in archive')),
2957 ('p', 'prefix', '', _('directory prefix for files in archive')),
2958 ('r', 'rev', '', _('revision to distribute')),
2958 ('r', 'rev', '', _('revision to distribute')),
2959 ('t', 'type', '', _('type of distribution to create')),
2959 ('t', 'type', '', _('type of distribution to create')),
2960 ] + walkopts,
2960 ] + walkopts,
2961 _('hg archive [OPTION]... DEST')),
2961 _('hg archive [OPTION]... DEST')),
2962 "backout":
2962 "backout":
2963 (backout,
2963 (backout,
2964 [('', 'merge', None,
2964 [('', 'merge', None,
2965 _('merge with old dirstate parent after backout')),
2965 _('merge with old dirstate parent after backout')),
2966 ('', 'parent', '', _('parent to choose when backing out merge')),
2966 ('', 'parent', '', _('parent to choose when backing out merge')),
2967 ('r', 'rev', '', _('revision to backout')),
2967 ('r', 'rev', '', _('revision to backout')),
2968 ] + walkopts + commitopts + commitopts2,
2968 ] + walkopts + commitopts + commitopts2,
2969 _('hg backout [OPTION]... [-r] REV')),
2969 _('hg backout [OPTION]... [-r] REV')),
2970 "bisect":
2970 "bisect":
2971 (bisect,
2971 (bisect,
2972 [('r', 'reset', False, _('reset bisect state')),
2972 [('r', 'reset', False, _('reset bisect state')),
2973 ('g', 'good', False, _('mark changeset good')),
2973 ('g', 'good', False, _('mark changeset good')),
2974 ('b', 'bad', False, _('mark changeset bad')),
2974 ('b', 'bad', False, _('mark changeset bad')),
2975 ('s', 'skip', False, _('skip testing changeset')),
2975 ('s', 'skip', False, _('skip testing changeset')),
2976 ('U', 'noupdate', False, _('do not update to target'))],
2976 ('U', 'noupdate', False, _('do not update to target'))],
2977 _("hg bisect [-gbsr] [REV]")),
2977 _("hg bisect [-gbsr] [REV]")),
2978 "branch":
2978 "branch":
2979 (branch,
2979 (branch,
2980 [('f', 'force', None,
2980 [('f', 'force', None,
2981 _('set branch name even if it shadows an existing branch'))],
2981 _('set branch name even if it shadows an existing branch'))],
2982 _('hg branch [-f] [NAME]')),
2982 _('hg branch [-f] [NAME]')),
2983 "branches":
2983 "branches":
2984 (branches,
2984 (branches,
2985 [('a', 'active', False,
2985 [('a', 'active', False,
2986 _('show only branches that have unmerged heads'))],
2986 _('show only branches that have unmerged heads'))],
2987 _('hg branches [-a]')),
2987 _('hg branches [-a]')),
2988 "bundle":
2988 "bundle":
2989 (bundle,
2989 (bundle,
2990 [('f', 'force', None,
2990 [('f', 'force', None,
2991 _('run even when remote repository is unrelated')),
2991 _('run even when remote repository is unrelated')),
2992 ('r', 'rev', [],
2992 ('r', 'rev', [],
2993 _('a changeset up to which you would like to bundle')),
2993 _('a changeset up to which you would like to bundle')),
2994 ('', 'base', [],
2994 ('', 'base', [],
2995 _('a base changeset to specify instead of a destination')),
2995 _('a base changeset to specify instead of a destination')),
2996 ('a', 'all', None, _('bundle all changesets in the repository')),
2996 ('a', 'all', None, _('bundle all changesets in the repository')),
2997 ('t', 'type', 'bzip2', _('bundle compression type to use')),
2997 ('t', 'type', 'bzip2', _('bundle compression type to use')),
2998 ] + remoteopts,
2998 ] + remoteopts,
2999 _('hg bundle [-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
2999 _('hg bundle [-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3000 "cat":
3000 "cat":
3001 (cat,
3001 (cat,
3002 [('o', 'output', '', _('print output to file with formatted name')),
3002 [('o', 'output', '', _('print output to file with formatted name')),
3003 ('r', 'rev', '', _('print the given revision')),
3003 ('r', 'rev', '', _('print the given revision')),
3004 ('', 'decode', None, _('apply any matching decode filter')),
3004 ('', 'decode', None, _('apply any matching decode filter')),
3005 ] + walkopts,
3005 ] + walkopts,
3006 _('hg cat [OPTION]... FILE...')),
3006 _('hg cat [OPTION]... FILE...')),
3007 "^clone":
3007 "^clone":
3008 (clone,
3008 (clone,
3009 [('U', 'noupdate', None, _('do not update the new working directory')),
3009 [('U', 'noupdate', None, _('do not update the new working directory')),
3010 ('r', 'rev', [],
3010 ('r', 'rev', [],
3011 _('a changeset you would like to have after cloning')),
3011 _('a changeset you would like to have after cloning')),
3012 ('', 'pull', None, _('use pull protocol to copy metadata')),
3012 ('', 'pull', None, _('use pull protocol to copy metadata')),
3013 ('', 'uncompressed', None,
3013 ('', 'uncompressed', None,
3014 _('use uncompressed transfer (fast over LAN)')),
3014 _('use uncompressed transfer (fast over LAN)')),
3015 ] + remoteopts,
3015 ] + remoteopts,
3016 _('hg clone [OPTION]... SOURCE [DEST]')),
3016 _('hg clone [OPTION]... SOURCE [DEST]')),
3017 "^commit|ci":
3017 "^commit|ci":
3018 (commit,
3018 (commit,
3019 [('A', 'addremove', None,
3019 [('A', 'addremove', None,
3020 _('mark new/missing files as added/removed before committing')),
3020 _('mark new/missing files as added/removed before committing')),
3021 ] + walkopts + commitopts + commitopts2,
3021 ] + walkopts + commitopts + commitopts2,
3022 _('hg commit [OPTION]... [FILE]...')),
3022 _('hg commit [OPTION]... [FILE]...')),
3023 "copy|cp":
3023 "copy|cp":
3024 (copy,
3024 (copy,
3025 [('A', 'after', None, _('record a copy that has already occurred')),
3025 [('A', 'after', None, _('record a copy that has already occurred')),
3026 ('f', 'force', None,
3026 ('f', 'force', None,
3027 _('forcibly copy over an existing managed file')),
3027 _('forcibly copy over an existing managed file')),
3028 ] + walkopts + dryrunopts,
3028 ] + walkopts + dryrunopts,
3029 _('hg copy [OPTION]... [SOURCE]... DEST')),
3029 _('hg copy [OPTION]... [SOURCE]... DEST')),
3030 "debugancestor": (debugancestor, [],
3030 "debugancestor": (debugancestor, [],
3031 _('hg debugancestor [INDEX] REV1 REV2')),
3031 _('hg debugancestor [INDEX] REV1 REV2')),
3032 "debugcheckstate": (debugcheckstate, [], _('hg debugcheckstate')),
3032 "debugcheckstate": (debugcheckstate, [], _('hg debugcheckstate')),
3033 "debugcomplete":
3033 "debugcomplete":
3034 (debugcomplete,
3034 (debugcomplete,
3035 [('o', 'options', None, _('show the command options'))],
3035 [('o', 'options', None, _('show the command options'))],
3036 _('hg debugcomplete [-o] CMD')),
3036 _('hg debugcomplete [-o] CMD')),
3037 "debugdate":
3037 "debugdate":
3038 (debugdate,
3038 (debugdate,
3039 [('e', 'extended', None, _('try extended date formats'))],
3039 [('e', 'extended', None, _('try extended date formats'))],
3040 _('hg debugdate [-e] DATE [RANGE]')),
3040 _('hg debugdate [-e] DATE [RANGE]')),
3041 "debugdata": (debugdata, [], _('hg debugdata FILE REV')),
3041 "debugdata": (debugdata, [], _('hg debugdata FILE REV')),
3042 "debugfsinfo": (debugfsinfo, [], _('hg debugfsinfo [PATH]')),
3042 "debugfsinfo": (debugfsinfo, [], _('hg debugfsinfo [PATH]')),
3043 "debugindex": (debugindex, [], _('hg debugindex FILE')),
3043 "debugindex": (debugindex, [], _('hg debugindex FILE')),
3044 "debugindexdot": (debugindexdot, [], _('hg debugindexdot FILE')),
3044 "debugindexdot": (debugindexdot, [], _('hg debugindexdot FILE')),
3045 "debuginstall": (debuginstall, [], _('hg debuginstall')),
3045 "debuginstall": (debuginstall, [], _('hg debuginstall')),
3046 "debugrawcommit|rawcommit":
3046 "debugrawcommit|rawcommit":
3047 (rawcommit,
3047 (rawcommit,
3048 [('p', 'parent', [], _('parent')),
3048 [('p', 'parent', [], _('parent')),
3049 ('F', 'files', '', _('file list'))
3049 ('F', 'files', '', _('file list'))
3050 ] + commitopts + commitopts2,
3050 ] + commitopts + commitopts2,
3051 _('hg debugrawcommit [OPTION]... [FILE]...')),
3051 _('hg debugrawcommit [OPTION]... [FILE]...')),
3052 "debugrebuildstate":
3052 "debugrebuildstate":
3053 (debugrebuildstate,
3053 (debugrebuildstate,
3054 [('r', 'rev', '', _('revision to rebuild to'))],
3054 [('r', 'rev', '', _('revision to rebuild to'))],
3055 _('hg debugrebuildstate [-r REV] [REV]')),
3055 _('hg debugrebuildstate [-r REV] [REV]')),
3056 "debugrename":
3056 "debugrename":
3057 (debugrename,
3057 (debugrename,
3058 [('r', 'rev', '', _('revision to debug'))],
3058 [('r', 'rev', '', _('revision to debug'))],
3059 _('hg debugrename [-r REV] FILE')),
3059 _('hg debugrename [-r REV] FILE')),
3060 "debugsetparents":
3060 "debugsetparents":
3061 (debugsetparents,
3061 (debugsetparents,
3062 [],
3062 [],
3063 _('hg debugsetparents REV1 [REV2]')),
3063 _('hg debugsetparents REV1 [REV2]')),
3064 "debugstate":
3064 "debugstate":
3065 (debugstate,
3065 (debugstate,
3066 [('', 'nodates', None, _('do not display the saved mtime'))],
3066 [('', 'nodates', None, _('do not display the saved mtime'))],
3067 _('hg debugstate [OPTS]')),
3067 _('hg debugstate [OPTS]')),
3068 "debugwalk": (debugwalk, walkopts, _('hg debugwalk [OPTION]... [FILE]...')),
3068 "debugwalk": (debugwalk, walkopts, _('hg debugwalk [OPTION]... [FILE]...')),
3069 "^diff":
3069 "^diff":
3070 (diff,
3070 (diff,
3071 [('r', 'rev', [], _('revision')),
3071 [('r', 'rev', [], _('revision')),
3072 ('a', 'text', None, _('treat all files as text')),
3072 ('a', 'text', None, _('treat all files as text')),
3073 ('p', 'show-function', None,
3073 ('p', 'show-function', None,
3074 _('show which function each change is in')),
3074 _('show which function each change is in')),
3075 ('g', 'git', None, _('use git extended diff format')),
3075 ('g', 'git', None, _('use git extended diff format')),
3076 ('', 'nodates', None, _("don't include dates in diff headers")),
3076 ('', 'nodates', None, _("don't include dates in diff headers")),
3077 ('w', 'ignore-all-space', None,
3077 ('w', 'ignore-all-space', None,
3078 _('ignore white space when comparing lines')),
3078 _('ignore white space when comparing lines')),
3079 ('b', 'ignore-space-change', None,
3079 ('b', 'ignore-space-change', None,
3080 _('ignore changes in the amount of white space')),
3080 _('ignore changes in the amount of white space')),
3081 ('B', 'ignore-blank-lines', None,
3081 ('B', 'ignore-blank-lines', None,
3082 _('ignore changes whose lines are all blank')),
3082 _('ignore changes whose lines are all blank')),
3083 ('U', 'unified', '',
3083 ('U', 'unified', '',
3084 _('number of lines of context to show'))
3084 _('number of lines of context to show'))
3085 ] + walkopts,
3085 ] + walkopts,
3086 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3086 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3087 "^export":
3087 "^export":
3088 (export,
3088 (export,
3089 [('o', 'output', '', _('print output to file with formatted name')),
3089 [('o', 'output', '', _('print output to file with formatted name')),
3090 ('a', 'text', None, _('treat all files as text')),
3090 ('a', 'text', None, _('treat all files as text')),
3091 ('g', 'git', None, _('use git extended diff format')),
3091 ('g', 'git', None, _('use git extended diff format')),
3092 ('', 'nodates', None, _("don't include dates in diff headers")),
3092 ('', 'nodates', None, _("don't include dates in diff headers")),
3093 ('', 'switch-parent', None, _('diff against the second parent'))],
3093 ('', 'switch-parent', None, _('diff against the second parent'))],
3094 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
3094 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
3095 "grep":
3095 "grep":
3096 (grep,
3096 (grep,
3097 [('0', 'print0', None, _('end fields with NUL')),
3097 [('0', 'print0', None, _('end fields with NUL')),
3098 ('', 'all', None, _('print all revisions that match')),
3098 ('', 'all', None, _('print all revisions that match')),
3099 ('f', 'follow', None,
3099 ('f', 'follow', None,
3100 _('follow changeset history, or file history across copies and renames')),
3100 _('follow changeset history, or file history across copies and renames')),
3101 ('i', 'ignore-case', None, _('ignore case when matching')),
3101 ('i', 'ignore-case', None, _('ignore case when matching')),
3102 ('l', 'files-with-matches', None,
3102 ('l', 'files-with-matches', None,
3103 _('print only filenames and revs that match')),
3103 _('print only filenames and revs that match')),
3104 ('n', 'line-number', None, _('print matching line numbers')),
3104 ('n', 'line-number', None, _('print matching line numbers')),
3105 ('r', 'rev', [], _('search in given revision range')),
3105 ('r', 'rev', [], _('search in given revision range')),
3106 ('u', 'user', None, _('list the author (long with -v)')),
3106 ('u', 'user', None, _('list the author (long with -v)')),
3107 ('d', 'date', None, _('list the date (short with -q)')),
3107 ('d', 'date', None, _('list the date (short with -q)')),
3108 ] + walkopts,
3108 ] + walkopts,
3109 _('hg grep [OPTION]... PATTERN [FILE]...')),
3109 _('hg grep [OPTION]... PATTERN [FILE]...')),
3110 "heads":
3110 "heads":
3111 (heads,
3111 (heads,
3112 [('r', 'rev', '', _('show only heads which are descendants of rev')),
3112 [('r', 'rev', '', _('show only heads which are descendants of rev')),
3113 ] + templateopts,
3113 ] + templateopts,
3114 _('hg heads [-r REV] [REV]...')),
3114 _('hg heads [-r REV] [REV]...')),
3115 "help": (help_, [], _('hg help [COMMAND]')),
3115 "help": (help_, [], _('hg help [COMMAND]')),
3116 "identify|id":
3116 "identify|id":
3117 (identify,
3117 (identify,
3118 [('r', 'rev', '', _('identify the specified rev')),
3118 [('r', 'rev', '', _('identify the specified rev')),
3119 ('n', 'num', None, _('show local revision number')),
3119 ('n', 'num', None, _('show local revision number')),
3120 ('i', 'id', None, _('show global revision id')),
3120 ('i', 'id', None, _('show global revision id')),
3121 ('b', 'branch', None, _('show branch')),
3121 ('b', 'branch', None, _('show branch')),
3122 ('t', 'tags', None, _('show tags'))],
3122 ('t', 'tags', None, _('show tags'))],
3123 _('hg identify [-nibt] [-r REV] [SOURCE]')),
3123 _('hg identify [-nibt] [-r REV] [SOURCE]')),
3124 "import|patch":
3124 "import|patch":
3125 (import_,
3125 (import_,
3126 [('p', 'strip', 1,
3126 [('p', 'strip', 1,
3127 _('directory strip option for patch. This has the same\n'
3127 _('directory strip option for patch. This has the same\n'
3128 'meaning as the corresponding patch option')),
3128 'meaning as the corresponding patch option')),
3129 ('b', 'base', '', _('base path')),
3129 ('b', 'base', '', _('base path')),
3130 ('f', 'force', None,
3130 ('f', 'force', None,
3131 _('skip check for outstanding uncommitted changes')),
3131 _('skip check for outstanding uncommitted changes')),
3132 ('', 'no-commit', None, _("don't commit, just update the working directory")),
3132 ('', 'no-commit', None, _("don't commit, just update the working directory")),
3133 ('', 'exact', None,
3133 ('', 'exact', None,
3134 _('apply patch to the nodes from which it was generated')),
3134 _('apply patch to the nodes from which it was generated')),
3135 ('', 'import-branch', None,
3135 ('', 'import-branch', None,
3136 _('Use any branch information in patch (implied by --exact)'))] +
3136 _('Use any branch information in patch (implied by --exact)'))] +
3137 commitopts + commitopts2,
3137 commitopts + commitopts2,
3138 _('hg import [OPTION]... PATCH...')),
3138 _('hg import [OPTION]... PATCH...')),
3139 "incoming|in":
3139 "incoming|in":
3140 (incoming,
3140 (incoming,
3141 [('f', 'force', None,
3141 [('f', 'force', None,
3142 _('run even when remote repository is unrelated')),
3142 _('run even when remote repository is unrelated')),
3143 ('n', 'newest-first', None, _('show newest record first')),
3143 ('n', 'newest-first', None, _('show newest record first')),
3144 ('', 'bundle', '', _('file to store the bundles into')),
3144 ('', 'bundle', '', _('file to store the bundles into')),
3145 ('r', 'rev', [],
3145 ('r', 'rev', [],
3146 _('a specific revision up to which you would like to pull')),
3146 _('a specific revision up to which you would like to pull')),
3147 ] + logopts + remoteopts,
3147 ] + logopts + remoteopts,
3148 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
3148 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
3149 ' [--bundle FILENAME] [SOURCE]')),
3149 ' [--bundle FILENAME] [SOURCE]')),
3150 "^init":
3150 "^init":
3151 (init,
3151 (init,
3152 remoteopts,
3152 remoteopts,
3153 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
3153 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
3154 "locate":
3154 "locate":
3155 (locate,
3155 (locate,
3156 [('r', 'rev', '', _('search the repository as it stood at rev')),
3156 [('r', 'rev', '', _('search the repository as it stood at rev')),
3157 ('0', 'print0', None,
3157 ('0', 'print0', None,
3158 _('end filenames with NUL, for use with xargs')),
3158 _('end filenames with NUL, for use with xargs')),
3159 ('f', 'fullpath', None,
3159 ('f', 'fullpath', None,
3160 _('print complete paths from the filesystem root')),
3160 _('print complete paths from the filesystem root')),
3161 ] + walkopts,
3161 ] + walkopts,
3162 _('hg locate [OPTION]... [PATTERN]...')),
3162 _('hg locate [OPTION]... [PATTERN]...')),
3163 "^log|history":
3163 "^log|history":
3164 (log,
3164 (log,
3165 [('f', 'follow', None,
3165 [('f', 'follow', None,
3166 _('follow changeset history, or file history across copies and renames')),
3166 _('follow changeset history, or file history across copies and renames')),
3167 ('', 'follow-first', None,
3167 ('', 'follow-first', None,
3168 _('only follow the first parent of merge changesets')),
3168 _('only follow the first parent of merge changesets')),
3169 ('d', 'date', '', _('show revs matching date spec')),
3169 ('d', 'date', '', _('show revs matching date spec')),
3170 ('C', 'copies', None, _('show copied files')),
3170 ('C', 'copies', None, _('show copied files')),
3171 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3171 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3172 ('r', 'rev', [], _('show the specified revision or range')),
3172 ('r', 'rev', [], _('show the specified revision or range')),
3173 ('', 'removed', None, _('include revs where files were removed')),
3173 ('', 'removed', None, _('include revs where files were removed')),
3174 ('m', 'only-merges', None, _('show only merges')),
3174 ('m', 'only-merges', None, _('show only merges')),
3175 ('b', 'only-branch', [],
3175 ('b', 'only-branch', [],
3176 _('show only changesets within the given named branch')),
3176 _('show only changesets within the given named branch')),
3177 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3177 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3178 ] + logopts + walkopts,
3178 ] + logopts + walkopts,
3179 _('hg log [OPTION]... [FILE]')),
3179 _('hg log [OPTION]... [FILE]')),
3180 "manifest":
3180 "manifest":
3181 (manifest,
3181 (manifest,
3182 [('r', 'rev', '', _('revision to display'))],
3182 [('r', 'rev', '', _('revision to display'))],
3183 _('hg manifest [-r REV]')),
3183 _('hg manifest [-r REV]')),
3184 "^merge":
3184 "^merge":
3185 (merge,
3185 (merge,
3186 [('f', 'force', None, _('force a merge with outstanding changes')),
3186 [('f', 'force', None, _('force a merge with outstanding changes')),
3187 ('r', 'rev', '', _('revision to merge')),
3187 ('r', 'rev', '', _('revision to merge')),
3188 ],
3188 ],
3189 _('hg merge [-f] [[-r] REV]')),
3189 _('hg merge [-f] [[-r] REV]')),
3190 "outgoing|out":
3190 "outgoing|out":
3191 (outgoing,
3191 (outgoing,
3192 [('f', 'force', None,
3192 [('f', 'force', None,
3193 _('run even when remote repository is unrelated')),
3193 _('run even when remote repository is unrelated')),
3194 ('r', 'rev', [],
3194 ('r', 'rev', [],
3195 _('a specific revision up to which you would like to push')),
3195 _('a specific revision up to which you would like to push')),
3196 ('n', 'newest-first', None, _('show newest record first')),
3196 ('n', 'newest-first', None, _('show newest record first')),
3197 ] + logopts + remoteopts,
3197 ] + logopts + remoteopts,
3198 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3198 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3199 "^parents":
3199 "^parents":
3200 (parents,
3200 (parents,
3201 [('r', 'rev', '', _('show parents from the specified rev')),
3201 [('r', 'rev', '', _('show parents from the specified rev')),
3202 ] + templateopts,
3202 ] + templateopts,
3203 _('hg parents [-r REV] [FILE]')),
3203 _('hg parents [-r REV] [FILE]')),
3204 "paths": (paths, [], _('hg paths [NAME]')),
3204 "paths": (paths, [], _('hg paths [NAME]')),
3205 "^pull":
3205 "^pull":
3206 (pull,
3206 (pull,
3207 [('u', 'update', None,
3207 [('u', 'update', None,
3208 _('update to new tip if changesets were pulled')),
3208 _('update to new tip if changesets were pulled')),
3209 ('f', 'force', None,
3209 ('f', 'force', None,
3210 _('run even when remote repository is unrelated')),
3210 _('run even when remote repository is unrelated')),
3211 ('r', 'rev', [],
3211 ('r', 'rev', [],
3212 _('a specific revision up to which you would like to pull')),
3212 _('a specific revision up to which you would like to pull')),
3213 ] + remoteopts,
3213 ] + remoteopts,
3214 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3214 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3215 "^push":
3215 "^push":
3216 (push,
3216 (push,
3217 [('f', 'force', None, _('force push')),
3217 [('f', 'force', None, _('force push')),
3218 ('r', 'rev', [],
3218 ('r', 'rev', [],
3219 _('a specific revision up to which you would like to push')),
3219 _('a specific revision up to which you would like to push')),
3220 ] + remoteopts,
3220 ] + remoteopts,
3221 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3221 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3222 "recover": (recover, [], _('hg recover')),
3222 "recover": (recover, [], _('hg recover')),
3223 "^remove|rm":
3223 "^remove|rm":
3224 (remove,
3224 (remove,
3225 [('A', 'after', None, _('record delete for missing files')),
3225 [('A', 'after', None, _('record delete for missing files')),
3226 ('f', 'force', None,
3226 ('f', 'force', None,
3227 _('remove (and delete) file even if added or modified')),
3227 _('remove (and delete) file even if added or modified')),
3228 ] + walkopts,
3228 ] + walkopts,
3229 _('hg remove [OPTION]... FILE...')),
3229 _('hg remove [OPTION]... FILE...')),
3230 "rename|mv":
3230 "rename|mv":
3231 (rename,
3231 (rename,
3232 [('A', 'after', None, _('record a rename that has already occurred')),
3232 [('A', 'after', None, _('record a rename that has already occurred')),
3233 ('f', 'force', None,
3233 ('f', 'force', None,
3234 _('forcibly copy over an existing managed file')),
3234 _('forcibly copy over an existing managed file')),
3235 ] + walkopts + dryrunopts,
3235 ] + walkopts + dryrunopts,
3236 _('hg rename [OPTION]... SOURCE... DEST')),
3236 _('hg rename [OPTION]... SOURCE... DEST')),
3237 "resolve":
3237 "resolve":
3238 (resolve,
3238 (resolve,
3239 [('l', 'list', None, _('list state of files needing merge')),
3239 [('l', 'list', None, _('list state of files needing merge')),
3240 ('m', 'mark', None, _('mark files as resolved')),
3240 ('m', 'mark', None, _('mark files as resolved')),
3241 ('u', 'unmark', None, _('unmark files as resolved'))],
3241 ('u', 'unmark', None, _('unmark files as resolved'))],
3242 ('hg resolve [OPTION] [FILES...]')),
3242 ('hg resolve [OPTION] [FILES...]')),
3243 "revert":
3243 "revert":
3244 (revert,
3244 (revert,
3245 [('a', 'all', None, _('revert all changes when no arguments given')),
3245 [('a', 'all', None, _('revert all changes when no arguments given')),
3246 ('d', 'date', '', _('tipmost revision matching date')),
3246 ('d', 'date', '', _('tipmost revision matching date')),
3247 ('r', 'rev', '', _('revision to revert to')),
3247 ('r', 'rev', '', _('revision to revert to')),
3248 ('', 'no-backup', None, _('do not save backup copies of files')),
3248 ('', 'no-backup', None, _('do not save backup copies of files')),
3249 ] + walkopts + dryrunopts,
3249 ] + walkopts + dryrunopts,
3250 _('hg revert [OPTION]... [-r REV] [NAME]...')),
3250 _('hg revert [OPTION]... [-r REV] [NAME]...')),
3251 "rollback": (rollback, [], _('hg rollback')),
3251 "rollback": (rollback, [], _('hg rollback')),
3252 "root": (root, [], _('hg root')),
3252 "root": (root, [], _('hg root')),
3253 "^serve":
3253 "^serve":
3254 (serve,
3254 (serve,
3255 [('A', 'accesslog', '', _('name of access log file to write to')),
3255 [('A', 'accesslog', '', _('name of access log file to write to')),
3256 ('d', 'daemon', None, _('run server in background')),
3256 ('d', 'daemon', None, _('run server in background')),
3257 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3257 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3258 ('E', 'errorlog', '', _('name of error log file to write to')),
3258 ('E', 'errorlog', '', _('name of error log file to write to')),
3259 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3259 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3260 ('a', 'address', '', _('address to listen on (default: all interfaces)')),
3260 ('a', 'address', '', _('address to listen on (default: all interfaces)')),
3261 ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
3261 ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
3262 ('n', 'name', '',
3262 ('n', 'name', '',
3263 _('name to show in web pages (default: working dir)')),
3263 _('name to show in web pages (default: working dir)')),
3264 ('', 'webdir-conf', '', _('name of the webdir config file'
3264 ('', 'webdir-conf', '', _('name of the webdir config file'
3265 ' (serve more than one repo)')),
3265 ' (serve more than one repo)')),
3266 ('', 'pid-file', '', _('name of file to write process ID to')),
3266 ('', 'pid-file', '', _('name of file to write process ID to')),
3267 ('', 'stdio', None, _('for remote clients')),
3267 ('', 'stdio', None, _('for remote clients')),
3268 ('t', 'templates', '', _('web templates to use')),
3268 ('t', 'templates', '', _('web templates to use')),
3269 ('', 'style', '', _('template style to use')),
3269 ('', 'style', '', _('template style to use')),
3270 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3270 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3271 ('', 'certificate', '', _('SSL certificate file'))],
3271 ('', 'certificate', '', _('SSL certificate file'))],
3272 _('hg serve [OPTION]...')),
3272 _('hg serve [OPTION]...')),
3273 "showconfig|debugconfig":
3273 "showconfig|debugconfig":
3274 (showconfig,
3274 (showconfig,
3275 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3275 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3276 _('hg showconfig [-u] [NAME]...')),
3276 _('hg showconfig [-u] [NAME]...')),
3277 "^status|st":
3277 "^status|st":
3278 (status,
3278 (status,
3279 [('A', 'all', None, _('show status of all files')),
3279 [('A', 'all', None, _('show status of all files')),
3280 ('m', 'modified', None, _('show only modified files')),
3280 ('m', 'modified', None, _('show only modified files')),
3281 ('a', 'added', None, _('show only added files')),
3281 ('a', 'added', None, _('show only added files')),
3282 ('r', 'removed', None, _('show only removed files')),
3282 ('r', 'removed', None, _('show only removed files')),
3283 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3283 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3284 ('c', 'clean', None, _('show only files without changes')),
3284 ('c', 'clean', None, _('show only files without changes')),
3285 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3285 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3286 ('i', 'ignored', None, _('show only ignored files')),
3286 ('i', 'ignored', None, _('show only ignored files')),
3287 ('n', 'no-status', None, _('hide status prefix')),
3287 ('n', 'no-status', None, _('hide status prefix')),
3288 ('C', 'copies', None, _('show source of copied files')),
3288 ('C', 'copies', None, _('show source of copied files')),
3289 ('0', 'print0', None,
3289 ('0', 'print0', None,
3290 _('end filenames with NUL, for use with xargs')),
3290 _('end filenames with NUL, for use with xargs')),
3291 ('', 'rev', [], _('show difference from revision')),
3291 ('', 'rev', [], _('show difference from revision')),
3292 ] + walkopts,
3292 ] + walkopts,
3293 _('hg status [OPTION]... [FILE]...')),
3293 _('hg status [OPTION]... [FILE]...')),
3294 "tag":
3294 "tag":
3295 (tag,
3295 (tag,
3296 [('f', 'force', None, _('replace existing tag')),
3296 [('f', 'force', None, _('replace existing tag')),
3297 ('l', 'local', None, _('make the tag local')),
3297 ('l', 'local', None, _('make the tag local')),
3298 ('r', 'rev', '', _('revision to tag')),
3298 ('r', 'rev', '', _('revision to tag')),
3299 ('', 'remove', None, _('remove a tag')),
3299 ('', 'remove', None, _('remove a tag')),
3300 # -l/--local is already there, commitopts cannot be used
3300 # -l/--local is already there, commitopts cannot be used
3301 ('m', 'message', '', _('use <text> as commit message')),
3301 ('m', 'message', '', _('use <text> as commit message')),
3302 ] + commitopts2,
3302 ] + commitopts2,
3303 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3303 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3304 "tags": (tags, [], _('hg tags')),
3304 "tags": (tags, [], _('hg tags')),
3305 "tip":
3305 "tip":
3306 (tip,
3306 (tip,
3307 [('p', 'patch', None, _('show patch')),
3307 [('p', 'patch', None, _('show patch')),
3308 ] + templateopts,
3308 ] + templateopts,
3309 _('hg tip [-p]')),
3309 _('hg tip [-p]')),
3310 "unbundle":
3310 "unbundle":
3311 (unbundle,
3311 (unbundle,
3312 [('u', 'update', None,
3312 [('u', 'update', None,
3313 _('update to new tip if changesets were unbundled'))],
3313 _('update to new tip if changesets were unbundled'))],
3314 _('hg unbundle [-u] FILE...')),
3314 _('hg unbundle [-u] FILE...')),
3315 "^update|up|checkout|co":
3315 "^update|up|checkout|co":
3316 (update,
3316 (update,
3317 [('C', 'clean', None, _('overwrite locally modified files')),
3317 [('C', 'clean', None, _('overwrite locally modified files')),
3318 ('d', 'date', '', _('tipmost revision matching date')),
3318 ('d', 'date', '', _('tipmost revision matching date')),
3319 ('r', 'rev', '', _('revision'))],
3319 ('r', 'rev', '', _('revision'))],
3320 _('hg update [-C] [-d DATE] [[-r] REV]')),
3320 _('hg update [-C] [-d DATE] [[-r] REV]')),
3321 "verify": (verify, [], _('hg verify')),
3321 "verify": (verify, [], _('hg verify')),
3322 "version": (version_, [], _('hg version')),
3322 "version": (version_, [], _('hg version')),
3323 }
3323 }
3324
3324
3325 norepo = ("clone init version help debugcomplete debugdata"
3325 norepo = ("clone init version help debugcomplete debugdata"
3326 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3326 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3327 optionalrepo = ("identify paths serve showconfig debugancestor")
3327 optionalrepo = ("identify paths serve showconfig debugancestor")
@@ -1,639 +1,639 b''
1 """
1 """
2 dirstate.py - working directory tracking for mercurial
2 dirstate.py - working directory tracking for mercurial
3
3
4 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5
5
6 This software may be used and distributed according to the terms
6 This software may be used and distributed according to the terms
7 of the GNU General Public License, incorporated herein by reference.
7 of the GNU General Public License, incorporated herein by reference.
8 """
8 """
9
9
10 from node import nullid
10 from node import nullid
11 from i18n import _
11 from i18n import _
12 import struct, os, bisect, stat, strutil, util, errno, ignore
12 import struct, os, bisect, stat, strutil, util, errno, ignore
13 import cStringIO, osutil, sys
13 import cStringIO, osutil, sys
14
14
15 _unknown = ('?', 0, 0, 0)
15 _unknown = ('?', 0, 0, 0)
16 _format = ">cllll"
16 _format = ">cllll"
17
17
18 class dirstate(object):
18 class dirstate(object):
19
19
20 def __init__(self, opener, ui, root):
20 def __init__(self, opener, ui, root):
21 self._opener = opener
21 self._opener = opener
22 self._root = root
22 self._root = root
23 self._dirty = False
23 self._dirty = False
24 self._dirtypl = False
24 self._dirtypl = False
25 self._ui = ui
25 self._ui = ui
26
26
27 def __getattr__(self, name):
27 def __getattr__(self, name):
28 if name == '_map':
28 if name == '_map':
29 self._read()
29 self._read()
30 return self._map
30 return self._map
31 elif name == '_copymap':
31 elif name == '_copymap':
32 self._read()
32 self._read()
33 return self._copymap
33 return self._copymap
34 elif name == '_branch':
34 elif name == '_branch':
35 try:
35 try:
36 self._branch = (self._opener("branch").read().strip()
36 self._branch = (self._opener("branch").read().strip()
37 or "default")
37 or "default")
38 except IOError:
38 except IOError:
39 self._branch = "default"
39 self._branch = "default"
40 return self._branch
40 return self._branch
41 elif name == '_pl':
41 elif name == '_pl':
42 self._pl = [nullid, nullid]
42 self._pl = [nullid, nullid]
43 try:
43 try:
44 st = self._opener("dirstate").read(40)
44 st = self._opener("dirstate").read(40)
45 if len(st) == 40:
45 if len(st) == 40:
46 self._pl = st[:20], st[20:40]
46 self._pl = st[:20], st[20:40]
47 except IOError, err:
47 except IOError, err:
48 if err.errno != errno.ENOENT: raise
48 if err.errno != errno.ENOENT: raise
49 return self._pl
49 return self._pl
50 elif name == '_dirs':
50 elif name == '_dirs':
51 self._dirs = {}
51 self._dirs = {}
52 for f in self._map:
52 for f in self._map:
53 if self[f] != 'r':
53 if self[f] != 'r':
54 self._incpath(f)
54 self._incpath(f)
55 return self._dirs
55 return self._dirs
56 elif name == '_ignore':
56 elif name == '_ignore':
57 files = [self._join('.hgignore')]
57 files = [self._join('.hgignore')]
58 for name, path in self._ui.configitems("ui"):
58 for name, path in self._ui.configitems("ui"):
59 if name == 'ignore' or name.startswith('ignore.'):
59 if name == 'ignore' or name.startswith('ignore.'):
60 files.append(os.path.expanduser(path))
60 files.append(os.path.expanduser(path))
61 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
61 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
62 return self._ignore
62 return self._ignore
63 elif name == '_slash':
63 elif name == '_slash':
64 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
64 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
65 return self._slash
65 return self._slash
66 elif name == '_checkexec':
66 elif name == '_checkexec':
67 self._checkexec = util.checkexec(self._root)
67 self._checkexec = util.checkexec(self._root)
68 return self._checkexec
68 return self._checkexec
69 else:
69 else:
70 raise AttributeError, name
70 raise AttributeError, name
71
71
72 def _join(self, f):
72 def _join(self, f):
73 return os.path.join(self._root, f)
73 return os.path.join(self._root, f)
74
74
75 def getcwd(self):
75 def getcwd(self):
76 cwd = os.getcwd()
76 cwd = os.getcwd()
77 if cwd == self._root: return ''
77 if cwd == self._root: return ''
78 # self._root ends with a path separator if self._root is '/' or 'C:\'
78 # self._root ends with a path separator if self._root is '/' or 'C:\'
79 rootsep = self._root
79 rootsep = self._root
80 if not util.endswithsep(rootsep):
80 if not util.endswithsep(rootsep):
81 rootsep += os.sep
81 rootsep += os.sep
82 if cwd.startswith(rootsep):
82 if cwd.startswith(rootsep):
83 return cwd[len(rootsep):]
83 return cwd[len(rootsep):]
84 else:
84 else:
85 # we're outside the repo. return an absolute path.
85 # we're outside the repo. return an absolute path.
86 return cwd
86 return cwd
87
87
88 def pathto(self, f, cwd=None):
88 def pathto(self, f, cwd=None):
89 if cwd is None:
89 if cwd is None:
90 cwd = self.getcwd()
90 cwd = self.getcwd()
91 path = util.pathto(self._root, cwd, f)
91 path = util.pathto(self._root, cwd, f)
92 if self._slash:
92 if self._slash:
93 return util.normpath(path)
93 return util.normpath(path)
94 return path
94 return path
95
95
96 def __getitem__(self, key):
96 def __getitem__(self, key):
97 ''' current states:
97 ''' current states:
98 n normal
98 n normal
99 m needs merging
99 m needs merging
100 r marked for removal
100 r marked for removal
101 a marked for addition
101 a marked for addition
102 ? not tracked'''
102 ? not tracked'''
103 return self._map.get(key, ("?",))[0]
103 return self._map.get(key, ("?",))[0]
104
104
105 def __contains__(self, key):
105 def __contains__(self, key):
106 return key in self._map
106 return key in self._map
107
107
108 def __iter__(self):
108 def __iter__(self):
109 a = self._map.keys()
109 a = self._map.keys()
110 a.sort()
110 a.sort()
111 for x in a:
111 for x in a:
112 yield x
112 yield x
113
113
114 def parents(self):
114 def parents(self):
115 return self._pl
115 return self._pl
116
116
117 def branch(self):
117 def branch(self):
118 return self._branch
118 return self._branch
119
119
120 def setparents(self, p1, p2=nullid):
120 def setparents(self, p1, p2=nullid):
121 self._dirty = self._dirtypl = True
121 self._dirty = self._dirtypl = True
122 self._pl = p1, p2
122 self._pl = p1, p2
123
123
124 def setbranch(self, branch):
124 def setbranch(self, branch):
125 self._branch = branch
125 self._branch = branch
126 self._opener("branch", "w").write(branch + '\n')
126 self._opener("branch", "w").write(branch + '\n')
127
127
128 def _read(self):
128 def _read(self):
129 self._map = {}
129 self._map = {}
130 self._copymap = {}
130 self._copymap = {}
131 if not self._dirtypl:
131 if not self._dirtypl:
132 self._pl = [nullid, nullid]
132 self._pl = [nullid, nullid]
133 try:
133 try:
134 st = self._opener("dirstate").read()
134 st = self._opener("dirstate").read()
135 except IOError, err:
135 except IOError, err:
136 if err.errno != errno.ENOENT: raise
136 if err.errno != errno.ENOENT: raise
137 return
137 return
138 if not st:
138 if not st:
139 return
139 return
140
140
141 if not self._dirtypl:
141 if not self._dirtypl:
142 self._pl = [st[:20], st[20: 40]]
142 self._pl = [st[:20], st[20: 40]]
143
143
144 # deref fields so they will be local in loop
144 # deref fields so they will be local in loop
145 dmap = self._map
145 dmap = self._map
146 copymap = self._copymap
146 copymap = self._copymap
147 unpack = struct.unpack
147 unpack = struct.unpack
148 e_size = struct.calcsize(_format)
148 e_size = struct.calcsize(_format)
149 pos1 = 40
149 pos1 = 40
150 l = len(st)
150 l = len(st)
151
151
152 # the inner loop
152 # the inner loop
153 while pos1 < l:
153 while pos1 < l:
154 pos2 = pos1 + e_size
154 pos2 = pos1 + e_size
155 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
155 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
156 pos1 = pos2 + e[4]
156 pos1 = pos2 + e[4]
157 f = st[pos2:pos1]
157 f = st[pos2:pos1]
158 if '\0' in f:
158 if '\0' in f:
159 f, c = f.split('\0')
159 f, c = f.split('\0')
160 copymap[f] = c
160 copymap[f] = c
161 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
161 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
162
162
163 def invalidate(self):
163 def invalidate(self):
164 for a in "_map _copymap _branch _pl _dirs _ignore".split():
164 for a in "_map _copymap _branch _pl _dirs _ignore".split():
165 if a in self.__dict__:
165 if a in self.__dict__:
166 delattr(self, a)
166 delattr(self, a)
167 self._dirty = False
167 self._dirty = False
168
168
169 def copy(self, source, dest):
169 def copy(self, source, dest):
170 self._dirty = True
170 self._dirty = True
171 self._copymap[dest] = source
171 self._copymap[dest] = source
172
172
173 def copied(self, file):
173 def copied(self, file):
174 return self._copymap.get(file, None)
174 return self._copymap.get(file, None)
175
175
176 def copies(self):
176 def copies(self):
177 return self._copymap
177 return self._copymap
178
178
179 def _incpath(self, path):
179 def _incpath(self, path):
180 c = path.rfind('/')
180 c = path.rfind('/')
181 if c >= 0:
181 if c >= 0:
182 dirs = self._dirs
182 dirs = self._dirs
183 base = path[:c]
183 base = path[:c]
184 if base not in dirs:
184 if base not in dirs:
185 self._incpath(base)
185 self._incpath(base)
186 dirs[base] = 1
186 dirs[base] = 1
187 else:
187 else:
188 dirs[base] += 1
188 dirs[base] += 1
189
189
190 def _decpath(self, path):
190 def _decpath(self, path):
191 c = path.rfind('/')
191 c = path.rfind('/')
192 if c >= 0:
192 if c >= 0:
193 base = path[:c]
193 base = path[:c]
194 dirs = self._dirs
194 dirs = self._dirs
195 if dirs[base] == 1:
195 if dirs[base] == 1:
196 del dirs[base]
196 del dirs[base]
197 self._decpath(base)
197 self._decpath(base)
198 else:
198 else:
199 dirs[base] -= 1
199 dirs[base] -= 1
200
200
201 def _incpathcheck(self, f):
201 def _incpathcheck(self, f):
202 if '\r' in f or '\n' in f:
202 if '\r' in f or '\n' in f:
203 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
203 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
204 % f)
204 % f)
205 # shadows
205 # shadows
206 if f in self._dirs:
206 if f in self._dirs:
207 raise util.Abort(_('directory %r already in dirstate') % f)
207 raise util.Abort(_('directory %r already in dirstate') % f)
208 for c in strutil.rfindall(f, '/'):
208 for c in strutil.rfindall(f, '/'):
209 d = f[:c]
209 d = f[:c]
210 if d in self._dirs:
210 if d in self._dirs:
211 break
211 break
212 if d in self._map and self[d] != 'r':
212 if d in self._map and self[d] != 'r':
213 raise util.Abort(_('file %r in dirstate clashes with %r') %
213 raise util.Abort(_('file %r in dirstate clashes with %r') %
214 (d, f))
214 (d, f))
215 self._incpath(f)
215 self._incpath(f)
216
216
217 def _changepath(self, f, newstate, relaxed=False):
217 def _changepath(self, f, newstate, relaxed=False):
218 # handle upcoming path changes
218 # handle upcoming path changes
219 oldstate = self[f]
219 oldstate = self[f]
220 if oldstate not in "?r" and newstate in "?r":
220 if oldstate not in "?r" and newstate in "?r":
221 if "_dirs" in self.__dict__:
221 if "_dirs" in self.__dict__:
222 self._decpath(f)
222 self._decpath(f)
223 return
223 return
224 if oldstate in "?r" and newstate not in "?r":
224 if oldstate in "?r" and newstate not in "?r":
225 if relaxed and oldstate == '?':
225 if relaxed and oldstate == '?':
226 # XXX
226 # XXX
227 # in relaxed mode we assume the caller knows
227 # in relaxed mode we assume the caller knows
228 # what it is doing, workaround for updating
228 # what it is doing, workaround for updating
229 # dir-to-file revisions
229 # dir-to-file revisions
230 if "_dirs" in self.__dict__:
230 if "_dirs" in self.__dict__:
231 self._incpath(f)
231 self._incpath(f)
232 return
232 return
233 self._incpathcheck(f)
233 self._incpathcheck(f)
234 return
234 return
235
235
236 def normal(self, f):
236 def normal(self, f):
237 'mark a file normal and clean'
237 'mark a file normal and clean'
238 self._dirty = True
238 self._dirty = True
239 self._changepath(f, 'n', True)
239 self._changepath(f, 'n', True)
240 s = os.lstat(self._join(f))
240 s = os.lstat(self._join(f))
241 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
241 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
242 if f in self._copymap:
242 if f in self._copymap:
243 del self._copymap[f]
243 del self._copymap[f]
244
244
245 def normallookup(self, f):
245 def normallookup(self, f):
246 'mark a file normal, but possibly dirty'
246 'mark a file normal, but possibly dirty'
247 if self._pl[1] != nullid and f in self._map:
247 if self._pl[1] != nullid and f in self._map:
248 # if there is a merge going on and the file was either
248 # if there is a merge going on and the file was either
249 # in state 'm' or dirty before being removed, restore that state.
249 # in state 'm' or dirty before being removed, restore that state.
250 entry = self._map[f]
250 entry = self._map[f]
251 if entry[0] == 'r' and entry[2] in (-1, -2):
251 if entry[0] == 'r' and entry[2] in (-1, -2):
252 source = self._copymap.get(f)
252 source = self._copymap.get(f)
253 if entry[2] == -1:
253 if entry[2] == -1:
254 self.merge(f)
254 self.merge(f)
255 elif entry[2] == -2:
255 elif entry[2] == -2:
256 self.normaldirty(f)
256 self.normaldirty(f)
257 if source:
257 if source:
258 self.copy(source, f)
258 self.copy(source, f)
259 return
259 return
260 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
260 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
261 return
261 return
262 self._dirty = True
262 self._dirty = True
263 self._changepath(f, 'n', True)
263 self._changepath(f, 'n', True)
264 self._map[f] = ('n', 0, -1, -1, 0)
264 self._map[f] = ('n', 0, -1, -1, 0)
265 if f in self._copymap:
265 if f in self._copymap:
266 del self._copymap[f]
266 del self._copymap[f]
267
267
268 def normaldirty(self, f):
268 def normaldirty(self, f):
269 'mark a file normal, but dirty'
269 'mark a file normal, but dirty'
270 self._dirty = True
270 self._dirty = True
271 self._changepath(f, 'n', True)
271 self._changepath(f, 'n', True)
272 self._map[f] = ('n', 0, -2, -1, 0)
272 self._map[f] = ('n', 0, -2, -1, 0)
273 if f in self._copymap:
273 if f in self._copymap:
274 del self._copymap[f]
274 del self._copymap[f]
275
275
276 def add(self, f):
276 def add(self, f):
277 'mark a file added'
277 'mark a file added'
278 self._dirty = True
278 self._dirty = True
279 self._changepath(f, 'a')
279 self._changepath(f, 'a')
280 self._map[f] = ('a', 0, -1, -1, 0)
280 self._map[f] = ('a', 0, -1, -1, 0)
281 if f in self._copymap:
281 if f in self._copymap:
282 del self._copymap[f]
282 del self._copymap[f]
283
283
284 def remove(self, f):
284 def remove(self, f):
285 'mark a file removed'
285 'mark a file removed'
286 self._dirty = True
286 self._dirty = True
287 self._changepath(f, 'r')
287 self._changepath(f, 'r')
288 size = 0
288 size = 0
289 if self._pl[1] != nullid and f in self._map:
289 if self._pl[1] != nullid and f in self._map:
290 entry = self._map[f]
290 entry = self._map[f]
291 if entry[0] == 'm':
291 if entry[0] == 'm':
292 size = -1
292 size = -1
293 elif entry[0] == 'n' and entry[2] == -2:
293 elif entry[0] == 'n' and entry[2] == -2:
294 size = -2
294 size = -2
295 self._map[f] = ('r', 0, size, 0, 0)
295 self._map[f] = ('r', 0, size, 0, 0)
296 if size == 0 and f in self._copymap:
296 if size == 0 and f in self._copymap:
297 del self._copymap[f]
297 del self._copymap[f]
298
298
299 def merge(self, f):
299 def merge(self, f):
300 'mark a file merged'
300 'mark a file merged'
301 self._dirty = True
301 self._dirty = True
302 s = os.lstat(self._join(f))
302 s = os.lstat(self._join(f))
303 self._changepath(f, 'm', True)
303 self._changepath(f, 'm', True)
304 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
304 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
305 if f in self._copymap:
305 if f in self._copymap:
306 del self._copymap[f]
306 del self._copymap[f]
307
307
308 def forget(self, f):
308 def forget(self, f):
309 'forget a file'
309 'forget a file'
310 self._dirty = True
310 self._dirty = True
311 try:
311 try:
312 self._changepath(f, '?')
312 self._changepath(f, '?')
313 del self._map[f]
313 del self._map[f]
314 except KeyError:
314 except KeyError:
315 self._ui.warn(_("not in dirstate: %s\n") % f)
315 self._ui.warn(_("not in dirstate: %s\n") % f)
316
316
317 def clear(self):
317 def clear(self):
318 self._map = {}
318 self._map = {}
319 if "_dirs" in self.__dict__:
319 if "_dirs" in self.__dict__:
320 delattr(self, "_dirs");
320 delattr(self, "_dirs");
321 self._copymap = {}
321 self._copymap = {}
322 self._pl = [nullid, nullid]
322 self._pl = [nullid, nullid]
323 self._dirty = True
323 self._dirty = True
324
324
325 def rebuild(self, parent, files):
325 def rebuild(self, parent, files):
326 self.clear()
326 self.clear()
327 for f in files:
327 for f in files:
328 if files.execf(f):
328 if files.execf(f):
329 self._map[f] = ('n', 0777, -1, 0, 0)
329 self._map[f] = ('n', 0777, -1, 0, 0)
330 else:
330 else:
331 self._map[f] = ('n', 0666, -1, 0, 0)
331 self._map[f] = ('n', 0666, -1, 0, 0)
332 self._pl = (parent, nullid)
332 self._pl = (parent, nullid)
333 self._dirty = True
333 self._dirty = True
334
334
335 def write(self):
335 def write(self):
336 if not self._dirty:
336 if not self._dirty:
337 return
337 return
338 st = self._opener("dirstate", "w", atomictemp=True)
338 st = self._opener("dirstate", "w", atomictemp=True)
339
339
340 try:
340 try:
341 gran = int(self._ui.config('dirstate', 'granularity', 1))
341 gran = int(self._ui.config('dirstate', 'granularity', 1))
342 except ValueError:
342 except ValueError:
343 gran = 1
343 gran = 1
344 limit = sys.maxint
344 limit = sys.maxint
345 if gran > 0:
345 if gran > 0:
346 limit = util.fstat(st).st_mtime - gran
346 limit = util.fstat(st).st_mtime - gran
347
347
348 cs = cStringIO.StringIO()
348 cs = cStringIO.StringIO()
349 copymap = self._copymap
349 copymap = self._copymap
350 pack = struct.pack
350 pack = struct.pack
351 write = cs.write
351 write = cs.write
352 write("".join(self._pl))
352 write("".join(self._pl))
353 for f, e in self._map.iteritems():
353 for f, e in self._map.iteritems():
354 if f in copymap:
354 if f in copymap:
355 f = "%s\0%s" % (f, copymap[f])
355 f = "%s\0%s" % (f, copymap[f])
356 if e[3] > limit and e[0] == 'n':
356 if e[3] > limit and e[0] == 'n':
357 e = (e[0], 0, -1, -1, 0)
357 e = (e[0], 0, -1, -1, 0)
358 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
358 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
359 write(e)
359 write(e)
360 write(f)
360 write(f)
361 st.write(cs.getvalue())
361 st.write(cs.getvalue())
362 st.rename()
362 st.rename()
363 self._dirty = self._dirtypl = False
363 self._dirty = self._dirtypl = False
364
364
365 def _filter(self, files):
365 def _filter(self, files):
366 ret = {}
366 ret = {}
367 unknown = []
367 unknown = []
368
368
369 for x in files:
369 for x in files:
370 if x == '.':
370 if x == '.':
371 return self._map.copy()
371 return self._map.copy()
372 if x not in self._map:
372 if x not in self._map:
373 unknown.append(x)
373 unknown.append(x)
374 else:
374 else:
375 ret[x] = self._map[x]
375 ret[x] = self._map[x]
376
376
377 if not unknown:
377 if not unknown:
378 return ret
378 return ret
379
379
380 b = self._map.keys()
380 b = self._map.keys()
381 b.sort()
381 b.sort()
382 blen = len(b)
382 blen = len(b)
383
383
384 for x in unknown:
384 for x in unknown:
385 bs = bisect.bisect(b, "%s%s" % (x, '/'))
385 bs = bisect.bisect(b, "%s%s" % (x, '/'))
386 while bs < blen:
386 while bs < blen:
387 s = b[bs]
387 s = b[bs]
388 if len(s) > len(x) and s.startswith(x):
388 if len(s) > len(x) and s.startswith(x):
389 ret[s] = self._map[s]
389 ret[s] = self._map[s]
390 else:
390 else:
391 break
391 break
392 bs += 1
392 bs += 1
393 return ret
393 return ret
394
394
395 def _supported(self, f, mode, verbose=False):
395 def _supported(self, f, mode, verbose=False):
396 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
396 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
397 return True
397 return True
398 if verbose:
398 if verbose:
399 kind = 'unknown'
399 kind = 'unknown'
400 if stat.S_ISCHR(mode): kind = _('character device')
400 if stat.S_ISCHR(mode): kind = _('character device')
401 elif stat.S_ISBLK(mode): kind = _('block device')
401 elif stat.S_ISBLK(mode): kind = _('block device')
402 elif stat.S_ISFIFO(mode): kind = _('fifo')
402 elif stat.S_ISFIFO(mode): kind = _('fifo')
403 elif stat.S_ISSOCK(mode): kind = _('socket')
403 elif stat.S_ISSOCK(mode): kind = _('socket')
404 elif stat.S_ISDIR(mode): kind = _('directory')
404 elif stat.S_ISDIR(mode): kind = _('directory')
405 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
405 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
406 % (self.pathto(f), kind))
406 % (self.pathto(f), kind))
407 return False
407 return False
408
408
409 def _dirignore(self, f):
409 def _dirignore(self, f):
410 if f == '.':
410 if f == '.':
411 return False
411 return False
412 if self._ignore(f):
412 if self._ignore(f):
413 return True
413 return True
414 for c in strutil.findall(f, '/'):
414 for c in strutil.findall(f, '/'):
415 if self._ignore(f[:c]):
415 if self._ignore(f[:c]):
416 return True
416 return True
417 return False
417 return False
418
418
419 def walk(self, match):
419 def walk(self, match):
420 # filter out the src and stat
420 # filter out the src and stat
421 for src, f, st in self.statwalk(match.files(), match):
421 for src, f, st in self.statwalk(match):
422 yield f
422 yield f
423
423
424 def statwalk(self, files, match, unknown=True, ignored=False):
424 def statwalk(self, match, unknown=True, ignored=False):
425 '''
425 '''
426 walk recursively through the directory tree, finding all files
426 walk recursively through the directory tree, finding all files
427 matched by the match function
427 matched by the match function
428
428
429 results are yielded in a tuple (src, filename, st), where src
429 results are yielded in a tuple (src, filename, st), where src
430 is one of:
430 is one of:
431 'f' the file was found in the directory tree
431 'f' the file was found in the directory tree
432 'm' the file was only in the dirstate and not in the tree
432 'm' the file was only in the dirstate and not in the tree
433
433
434 and st is the stat result if the file was found in the directory.
434 and st is the stat result if the file was found in the directory.
435 '''
435 '''
436
436
437 def fwarn(f, msg):
437 def fwarn(f, msg):
438 self._ui.warn('%s: %s\n' % (self.pathto(ff), msg))
438 self._ui.warn('%s: %s\n' % (self.pathto(ff), msg))
439 return False
439 return False
440 badfn = fwarn
440 badfn = fwarn
441 if hasattr(match, 'bad'):
441 if hasattr(match, 'bad'):
442 badfn = match.bad
442 badfn = match.bad
443
443
444 # walk all files by default
444 # walk all files by default
445 files = match.files()
445 if not files:
446 if not files:
446 files = ['.']
447 files = ['.']
447 dc = self._map.copy()
448 dc = self._map.copy()
448 else:
449 else:
449 files = util.unique(files)
450 files = util.unique(files)
450 dc = self._filter(files)
451 dc = self._filter(files)
451
452
452 def imatch(file_):
453 def imatch(file_):
453 if file_ not in dc and self._ignore(file_):
454 if file_ not in dc and self._ignore(file_):
454 return False
455 return False
455 return match(file_)
456 return match(file_)
456
457
457 # TODO: don't walk unknown directories if unknown and ignored are False
458 # TODO: don't walk unknown directories if unknown and ignored are False
458 ignore = self._ignore
459 ignore = self._ignore
459 dirignore = self._dirignore
460 dirignore = self._dirignore
460 if ignored:
461 if ignored:
461 imatch = match
462 imatch = match
462 ignore = util.never
463 ignore = util.never
463 dirignore = util.never
464 dirignore = util.never
464
465
465 # self._root may end with a path separator when self._root == '/'
466 # self._root may end with a path separator when self._root == '/'
466 common_prefix_len = len(self._root)
467 common_prefix_len = len(self._root)
467 if not util.endswithsep(self._root):
468 if not util.endswithsep(self._root):
468 common_prefix_len += 1
469 common_prefix_len += 1
469
470
470 normpath = util.normpath
471 normpath = util.normpath
471 listdir = osutil.listdir
472 listdir = osutil.listdir
472 lstat = os.lstat
473 lstat = os.lstat
473 bisect_left = bisect.bisect_left
474 bisect_left = bisect.bisect_left
474 isdir = os.path.isdir
475 isdir = os.path.isdir
475 pconvert = util.pconvert
476 pconvert = util.pconvert
476 join = os.path.join
477 join = os.path.join
477 s_isdir = stat.S_ISDIR
478 s_isdir = stat.S_ISDIR
478 supported = self._supported
479 supported = self._supported
479 _join = self._join
480 _join = self._join
480 known = {'.hg': 1}
481 known = {'.hg': 1}
481
482
482 # recursion free walker, faster than os.walk.
483 # recursion free walker, faster than os.walk.
483 def findfiles(s):
484 def findfiles(s):
484 work = [s]
485 work = [s]
485 wadd = work.append
486 wadd = work.append
486 found = []
487 found = []
487 add = found.append
488 add = found.append
488 if hasattr(match, 'dir'):
489 if hasattr(match, 'dir'):
489 match.dir(normpath(s[common_prefix_len:]))
490 match.dir(normpath(s[common_prefix_len:]))
490 while work:
491 while work:
491 top = work.pop()
492 top = work.pop()
492 entries = listdir(top, stat=True)
493 entries = listdir(top, stat=True)
493 # nd is the top of the repository dir tree
494 # nd is the top of the repository dir tree
494 nd = normpath(top[common_prefix_len:])
495 nd = normpath(top[common_prefix_len:])
495 if nd == '.':
496 if nd == '.':
496 nd = ''
497 nd = ''
497 else:
498 else:
498 # do not recurse into a repo contained in this
499 # do not recurse into a repo contained in this
499 # one. use bisect to find .hg directory so speed
500 # one. use bisect to find .hg directory so speed
500 # is good on big directory.
501 # is good on big directory.
501 names = [e[0] for e in entries]
502 names = [e[0] for e in entries]
502 hg = bisect_left(names, '.hg')
503 hg = bisect_left(names, '.hg')
503 if hg < len(names) and names[hg] == '.hg':
504 if hg < len(names) and names[hg] == '.hg':
504 if isdir(join(top, '.hg')):
505 if isdir(join(top, '.hg')):
505 continue
506 continue
506 for f, kind, st in entries:
507 for f, kind, st in entries:
507 np = pconvert(join(nd, f))
508 np = pconvert(join(nd, f))
508 if np in known:
509 if np in known:
509 continue
510 continue
510 known[np] = 1
511 known[np] = 1
511 p = join(top, f)
512 p = join(top, f)
512 # don't trip over symlinks
513 # don't trip over symlinks
513 if kind == stat.S_IFDIR:
514 if kind == stat.S_IFDIR:
514 if not ignore(np):
515 if not ignore(np):
515 wadd(p)
516 wadd(p)
516 if hasattr(match, 'dir'):
517 if hasattr(match, 'dir'):
517 match.dir(np)
518 match.dir(np)
518 if np in dc and match(np):
519 if np in dc and match(np):
519 add((np, 'm', st))
520 add((np, 'm', st))
520 elif imatch(np):
521 elif imatch(np):
521 if supported(np, st.st_mode):
522 if supported(np, st.st_mode):
522 add((np, 'f', st))
523 add((np, 'f', st))
523 elif np in dc:
524 elif np in dc:
524 add((np, 'm', st))
525 add((np, 'm', st))
525 found.sort()
526 found.sort()
526 return found
527 return found
527
528
528 # step one, find all files that match our criteria
529 # step one, find all files that match our criteria
529 files.sort()
530 files.sort()
530 for ff in files:
531 for ff in files:
531 nf = normpath(ff)
532 nf = normpath(ff)
532 f = _join(ff)
533 f = _join(ff)
533 try:
534 try:
534 st = lstat(f)
535 st = lstat(f)
535 except OSError, inst:
536 except OSError, inst:
536 found = False
537 found = False
537 for fn in dc:
538 for fn in dc:
538 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
539 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
539 found = True
540 found = True
540 break
541 break
541 if not found:
542 if not found:
542 if inst.errno != errno.ENOENT:
543 if inst.errno != errno.ENOENT:
543 fwarn(ff, inst.strerror)
544 fwarn(ff, inst.strerror)
544 elif badfn(ff, inst.strerror) and imatch(nf):
545 elif badfn(ff, inst.strerror) and imatch(nf):
545 yield 'f', ff, None
546 yield 'f', ff, None
546 continue
547 continue
547 if s_isdir(st.st_mode):
548 if s_isdir(st.st_mode):
548 if not dirignore(nf):
549 if not dirignore(nf):
549 for f, src, st in findfiles(f):
550 for f, src, st in findfiles(f):
550 yield src, f, st
551 yield src, f, st
551 else:
552 else:
552 if nf in known:
553 if nf in known:
553 continue
554 continue
554 known[nf] = 1
555 known[nf] = 1
555 if match(nf):
556 if match(nf):
556 if supported(ff, st.st_mode, verbose=True):
557 if supported(ff, st.st_mode, verbose=True):
557 yield 'f', nf, st
558 yield 'f', nf, st
558 elif ff in dc:
559 elif ff in dc:
559 yield 'm', nf, st
560 yield 'm', nf, st
560
561
561 # step two run through anything left in the dc hash and yield
562 # step two run through anything left in the dc hash and yield
562 # if we haven't already seen it
563 # if we haven't already seen it
563 ks = dc.keys()
564 ks = dc.keys()
564 ks.sort()
565 ks.sort()
565 for k in ks:
566 for k in ks:
566 if k in known:
567 if k in known:
567 continue
568 continue
568 known[k] = 1
569 known[k] = 1
569 if imatch(k):
570 if imatch(k):
570 yield 'm', k, None
571 yield 'm', k, None
571
572
572 def status(self, files, match, list_ignored, list_clean, list_unknown):
573 def status(self, match, list_ignored, list_clean, list_unknown):
573 lookup, modified, added, unknown, ignored = [], [], [], [], []
574 lookup, modified, added, unknown, ignored = [], [], [], [], []
574 removed, deleted, clean = [], [], []
575 removed, deleted, clean = [], [], []
575
576
576 files = files or []
577 _join = self._join
577 _join = self._join
578 lstat = os.lstat
578 lstat = os.lstat
579 cmap = self._copymap
579 cmap = self._copymap
580 dmap = self._map
580 dmap = self._map
581 ladd = lookup.append
581 ladd = lookup.append
582 madd = modified.append
582 madd = modified.append
583 aadd = added.append
583 aadd = added.append
584 uadd = unknown.append
584 uadd = unknown.append
585 iadd = ignored.append
585 iadd = ignored.append
586 radd = removed.append
586 radd = removed.append
587 dadd = deleted.append
587 dadd = deleted.append
588 cadd = clean.append
588 cadd = clean.append
589
589
590 for src, fn, st in self.statwalk(files, match, unknown=list_unknown,
590 for src, fn, st in self.statwalk(match, unknown=list_unknown,
591 ignored=list_ignored):
591 ignored=list_ignored):
592 if fn not in dmap:
592 if fn not in dmap:
593 if (list_ignored or fn in files) and self._dirignore(fn):
593 if (list_ignored or match.exact(fn)) and self._dirignore(fn):
594 if list_ignored:
594 if list_ignored:
595 iadd(fn)
595 iadd(fn)
596 elif list_unknown:
596 elif list_unknown:
597 uadd(fn)
597 uadd(fn)
598 continue
598 continue
599
599
600 state, mode, size, time, foo = dmap[fn]
600 state, mode, size, time, foo = dmap[fn]
601
601
602 if src == 'm':
602 if src == 'm':
603 nonexistent = True
603 nonexistent = True
604 if not st:
604 if not st:
605 try:
605 try:
606 st = lstat(_join(fn))
606 st = lstat(_join(fn))
607 except OSError, inst:
607 except OSError, inst:
608 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
608 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
609 raise
609 raise
610 st = None
610 st = None
611 # We need to re-check that it is a valid file
611 # We need to re-check that it is a valid file
612 if st and self._supported(fn, st.st_mode):
612 if st and self._supported(fn, st.st_mode):
613 nonexistent = False
613 nonexistent = False
614 if nonexistent and state in "nma":
614 if nonexistent and state in "nma":
615 dadd(fn)
615 dadd(fn)
616 continue
616 continue
617 # check the common case first
617 # check the common case first
618 if state == 'n':
618 if state == 'n':
619 if not st:
619 if not st:
620 st = lstat(_join(fn))
620 st = lstat(_join(fn))
621 if (size >= 0 and
621 if (size >= 0 and
622 (size != st.st_size
622 (size != st.st_size
623 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
623 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
624 or size == -2
624 or size == -2
625 or fn in self._copymap):
625 or fn in self._copymap):
626 madd(fn)
626 madd(fn)
627 elif time != int(st.st_mtime):
627 elif time != int(st.st_mtime):
628 ladd(fn)
628 ladd(fn)
629 elif list_clean:
629 elif list_clean:
630 cadd(fn)
630 cadd(fn)
631 elif state == 'm':
631 elif state == 'm':
632 madd(fn)
632 madd(fn)
633 elif state == 'a':
633 elif state == 'a':
634 aadd(fn)
634 aadd(fn)
635 elif state == 'r':
635 elif state == 'r':
636 radd(fn)
636 radd(fn)
637
637
638 return (lookup, modified, added, removed, deleted, unknown, ignored,
638 return (lookup, modified, added, removed, deleted, unknown, ignored,
639 clean)
639 clean)
@@ -1,2132 +1,2134 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class 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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import bin, hex, nullid, nullrev, short
8 from node import bin, hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import repo, changegroup
10 import repo, changegroup
11 import changelog, dirstate, filelog, manifest, context, weakref
11 import changelog, dirstate, filelog, manifest, context, weakref
12 import lock, transaction, stat, errno, ui
12 import lock, transaction, stat, errno, ui
13 import os, revlog, time, util, extensions, hook, inspect
13 import os, revlog, time, util, extensions, hook, inspect
14 import match as match_
14
15
15 class localrepository(repo.repository):
16 class localrepository(repo.repository):
16 capabilities = util.set(('lookup', 'changegroupsubset'))
17 capabilities = util.set(('lookup', 'changegroupsubset'))
17 supported = ('revlogv1', 'store')
18 supported = ('revlogv1', 'store')
18
19
19 def __init__(self, parentui, path=None, create=0):
20 def __init__(self, parentui, path=None, create=0):
20 repo.repository.__init__(self)
21 repo.repository.__init__(self)
21 self.root = os.path.realpath(path)
22 self.root = os.path.realpath(path)
22 self.path = os.path.join(self.root, ".hg")
23 self.path = os.path.join(self.root, ".hg")
23 self.origroot = path
24 self.origroot = path
24 self.opener = util.opener(self.path)
25 self.opener = util.opener(self.path)
25 self.wopener = util.opener(self.root)
26 self.wopener = util.opener(self.root)
26
27
27 if not os.path.isdir(self.path):
28 if not os.path.isdir(self.path):
28 if create:
29 if create:
29 if not os.path.exists(path):
30 if not os.path.exists(path):
30 os.mkdir(path)
31 os.mkdir(path)
31 os.mkdir(self.path)
32 os.mkdir(self.path)
32 requirements = ["revlogv1"]
33 requirements = ["revlogv1"]
33 if parentui.configbool('format', 'usestore', True):
34 if parentui.configbool('format', 'usestore', True):
34 os.mkdir(os.path.join(self.path, "store"))
35 os.mkdir(os.path.join(self.path, "store"))
35 requirements.append("store")
36 requirements.append("store")
36 # create an invalid changelog
37 # create an invalid changelog
37 self.opener("00changelog.i", "a").write(
38 self.opener("00changelog.i", "a").write(
38 '\0\0\0\2' # represents revlogv2
39 '\0\0\0\2' # represents revlogv2
39 ' dummy changelog to prevent using the old repo layout'
40 ' dummy changelog to prevent using the old repo layout'
40 )
41 )
41 reqfile = self.opener("requires", "w")
42 reqfile = self.opener("requires", "w")
42 for r in requirements:
43 for r in requirements:
43 reqfile.write("%s\n" % r)
44 reqfile.write("%s\n" % r)
44 reqfile.close()
45 reqfile.close()
45 else:
46 else:
46 raise repo.RepoError(_("repository %s not found") % path)
47 raise repo.RepoError(_("repository %s not found") % path)
47 elif create:
48 elif create:
48 raise repo.RepoError(_("repository %s already exists") % path)
49 raise repo.RepoError(_("repository %s already exists") % path)
49 else:
50 else:
50 # find requirements
51 # find requirements
51 try:
52 try:
52 requirements = self.opener("requires").read().splitlines()
53 requirements = self.opener("requires").read().splitlines()
53 except IOError, inst:
54 except IOError, inst:
54 if inst.errno != errno.ENOENT:
55 if inst.errno != errno.ENOENT:
55 raise
56 raise
56 requirements = []
57 requirements = []
57 # check them
58 # check them
58 for r in requirements:
59 for r in requirements:
59 if r not in self.supported:
60 if r not in self.supported:
60 raise repo.RepoError(_("requirement '%s' not supported") % r)
61 raise repo.RepoError(_("requirement '%s' not supported") % r)
61
62
62 # setup store
63 # setup store
63 if "store" in requirements:
64 if "store" in requirements:
64 self.encodefn = util.encodefilename
65 self.encodefn = util.encodefilename
65 self.decodefn = util.decodefilename
66 self.decodefn = util.decodefilename
66 self.spath = os.path.join(self.path, "store")
67 self.spath = os.path.join(self.path, "store")
67 else:
68 else:
68 self.encodefn = lambda x: x
69 self.encodefn = lambda x: x
69 self.decodefn = lambda x: x
70 self.decodefn = lambda x: x
70 self.spath = self.path
71 self.spath = self.path
71
72
72 try:
73 try:
73 # files in .hg/ will be created using this mode
74 # files in .hg/ will be created using this mode
74 mode = os.stat(self.spath).st_mode
75 mode = os.stat(self.spath).st_mode
75 # avoid some useless chmods
76 # avoid some useless chmods
76 if (0777 & ~util._umask) == (0777 & mode):
77 if (0777 & ~util._umask) == (0777 & mode):
77 mode = None
78 mode = None
78 except OSError:
79 except OSError:
79 mode = None
80 mode = None
80
81
81 self._createmode = mode
82 self._createmode = mode
82 self.opener.createmode = mode
83 self.opener.createmode = mode
83 sopener = util.opener(self.spath)
84 sopener = util.opener(self.spath)
84 sopener.createmode = mode
85 sopener.createmode = mode
85 self.sopener = util.encodedopener(sopener, self.encodefn)
86 self.sopener = util.encodedopener(sopener, self.encodefn)
86
87
87 self.ui = ui.ui(parentui=parentui)
88 self.ui = ui.ui(parentui=parentui)
88 try:
89 try:
89 self.ui.readconfig(self.join("hgrc"), self.root)
90 self.ui.readconfig(self.join("hgrc"), self.root)
90 extensions.loadall(self.ui)
91 extensions.loadall(self.ui)
91 except IOError:
92 except IOError:
92 pass
93 pass
93
94
94 self.tagscache = None
95 self.tagscache = None
95 self._tagstypecache = None
96 self._tagstypecache = None
96 self.branchcache = None
97 self.branchcache = None
97 self._ubranchcache = None # UTF-8 version of branchcache
98 self._ubranchcache = None # UTF-8 version of branchcache
98 self._branchcachetip = None
99 self._branchcachetip = None
99 self.nodetagscache = None
100 self.nodetagscache = None
100 self.filterpats = {}
101 self.filterpats = {}
101 self._datafilters = {}
102 self._datafilters = {}
102 self._transref = self._lockref = self._wlockref = None
103 self._transref = self._lockref = self._wlockref = None
103
104
104 def __getattr__(self, name):
105 def __getattr__(self, name):
105 if name == 'changelog':
106 if name == 'changelog':
106 self.changelog = changelog.changelog(self.sopener)
107 self.changelog = changelog.changelog(self.sopener)
107 self.sopener.defversion = self.changelog.version
108 self.sopener.defversion = self.changelog.version
108 return self.changelog
109 return self.changelog
109 if name == 'manifest':
110 if name == 'manifest':
110 self.changelog
111 self.changelog
111 self.manifest = manifest.manifest(self.sopener)
112 self.manifest = manifest.manifest(self.sopener)
112 return self.manifest
113 return self.manifest
113 if name == 'dirstate':
114 if name == 'dirstate':
114 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
115 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
115 return self.dirstate
116 return self.dirstate
116 else:
117 else:
117 raise AttributeError, name
118 raise AttributeError, name
118
119
119 def url(self):
120 def url(self):
120 return 'file:' + self.root
121 return 'file:' + self.root
121
122
122 def hook(self, name, throw=False, **args):
123 def hook(self, name, throw=False, **args):
123 return hook.hook(self.ui, self, name, throw, **args)
124 return hook.hook(self.ui, self, name, throw, **args)
124
125
125 tag_disallowed = ':\r\n'
126 tag_disallowed = ':\r\n'
126
127
127 def _tag(self, names, node, message, local, user, date, parent=None,
128 def _tag(self, names, node, message, local, user, date, parent=None,
128 extra={}):
129 extra={}):
129 use_dirstate = parent is None
130 use_dirstate = parent is None
130
131
131 if isinstance(names, str):
132 if isinstance(names, str):
132 allchars = names
133 allchars = names
133 names = (names,)
134 names = (names,)
134 else:
135 else:
135 allchars = ''.join(names)
136 allchars = ''.join(names)
136 for c in self.tag_disallowed:
137 for c in self.tag_disallowed:
137 if c in allchars:
138 if c in allchars:
138 raise util.Abort(_('%r cannot be used in a tag name') % c)
139 raise util.Abort(_('%r cannot be used in a tag name') % c)
139
140
140 for name in names:
141 for name in names:
141 self.hook('pretag', throw=True, node=hex(node), tag=name,
142 self.hook('pretag', throw=True, node=hex(node), tag=name,
142 local=local)
143 local=local)
143
144
144 def writetags(fp, names, munge, prevtags):
145 def writetags(fp, names, munge, prevtags):
145 fp.seek(0, 2)
146 fp.seek(0, 2)
146 if prevtags and prevtags[-1] != '\n':
147 if prevtags and prevtags[-1] != '\n':
147 fp.write('\n')
148 fp.write('\n')
148 for name in names:
149 for name in names:
149 fp.write('%s %s\n' % (hex(node), munge and munge(name) or name))
150 fp.write('%s %s\n' % (hex(node), munge and munge(name) or name))
150 fp.close()
151 fp.close()
151
152
152 prevtags = ''
153 prevtags = ''
153 if local:
154 if local:
154 try:
155 try:
155 fp = self.opener('localtags', 'r+')
156 fp = self.opener('localtags', 'r+')
156 except IOError, err:
157 except IOError, err:
157 fp = self.opener('localtags', 'a')
158 fp = self.opener('localtags', 'a')
158 else:
159 else:
159 prevtags = fp.read()
160 prevtags = fp.read()
160
161
161 # local tags are stored in the current charset
162 # local tags are stored in the current charset
162 writetags(fp, names, None, prevtags)
163 writetags(fp, names, None, prevtags)
163 for name in names:
164 for name in names:
164 self.hook('tag', node=hex(node), tag=name, local=local)
165 self.hook('tag', node=hex(node), tag=name, local=local)
165 return
166 return
166
167
167 if use_dirstate:
168 if use_dirstate:
168 try:
169 try:
169 fp = self.wfile('.hgtags', 'rb+')
170 fp = self.wfile('.hgtags', 'rb+')
170 except IOError, err:
171 except IOError, err:
171 fp = self.wfile('.hgtags', 'ab')
172 fp = self.wfile('.hgtags', 'ab')
172 else:
173 else:
173 prevtags = fp.read()
174 prevtags = fp.read()
174 else:
175 else:
175 try:
176 try:
176 prevtags = self.filectx('.hgtags', parent).data()
177 prevtags = self.filectx('.hgtags', parent).data()
177 except revlog.LookupError:
178 except revlog.LookupError:
178 pass
179 pass
179 fp = self.wfile('.hgtags', 'wb')
180 fp = self.wfile('.hgtags', 'wb')
180 if prevtags:
181 if prevtags:
181 fp.write(prevtags)
182 fp.write(prevtags)
182
183
183 # committed tags are stored in UTF-8
184 # committed tags are stored in UTF-8
184 writetags(fp, names, util.fromlocal, prevtags)
185 writetags(fp, names, util.fromlocal, prevtags)
185
186
186 if use_dirstate and '.hgtags' not in self.dirstate:
187 if use_dirstate and '.hgtags' not in self.dirstate:
187 self.add(['.hgtags'])
188 self.add(['.hgtags'])
188
189
189 tagnode = self.commit(['.hgtags'], message, user, date, p1=parent,
190 tagnode = self.commit(['.hgtags'], message, user, date, p1=parent,
190 extra=extra)
191 extra=extra)
191
192
192 for name in names:
193 for name in names:
193 self.hook('tag', node=hex(node), tag=name, local=local)
194 self.hook('tag', node=hex(node), tag=name, local=local)
194
195
195 return tagnode
196 return tagnode
196
197
197 def tag(self, names, node, message, local, user, date):
198 def tag(self, names, node, message, local, user, date):
198 '''tag a revision with one or more symbolic names.
199 '''tag a revision with one or more symbolic names.
199
200
200 names is a list of strings or, when adding a single tag, names may be a
201 names is a list of strings or, when adding a single tag, names may be a
201 string.
202 string.
202
203
203 if local is True, the tags are stored in a per-repository file.
204 if local is True, the tags are stored in a per-repository file.
204 otherwise, they are stored in the .hgtags file, and a new
205 otherwise, they are stored in the .hgtags file, and a new
205 changeset is committed with the change.
206 changeset is committed with the change.
206
207
207 keyword arguments:
208 keyword arguments:
208
209
209 local: whether to store tags in non-version-controlled file
210 local: whether to store tags in non-version-controlled file
210 (default False)
211 (default False)
211
212
212 message: commit message to use if committing
213 message: commit message to use if committing
213
214
214 user: name of user to use if committing
215 user: name of user to use if committing
215
216
216 date: date tuple to use if committing'''
217 date: date tuple to use if committing'''
217
218
218 for x in self.status()[:5]:
219 for x in self.status()[:5]:
219 if '.hgtags' in x:
220 if '.hgtags' in x:
220 raise util.Abort(_('working copy of .hgtags is changed '
221 raise util.Abort(_('working copy of .hgtags is changed '
221 '(please commit .hgtags manually)'))
222 '(please commit .hgtags manually)'))
222
223
223 self._tag(names, node, message, local, user, date)
224 self._tag(names, node, message, local, user, date)
224
225
225 def tags(self):
226 def tags(self):
226 '''return a mapping of tag to node'''
227 '''return a mapping of tag to node'''
227 if self.tagscache:
228 if self.tagscache:
228 return self.tagscache
229 return self.tagscache
229
230
230 globaltags = {}
231 globaltags = {}
231 tagtypes = {}
232 tagtypes = {}
232
233
233 def readtags(lines, fn, tagtype):
234 def readtags(lines, fn, tagtype):
234 filetags = {}
235 filetags = {}
235 count = 0
236 count = 0
236
237
237 def warn(msg):
238 def warn(msg):
238 self.ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
239 self.ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
239
240
240 for l in lines:
241 for l in lines:
241 count += 1
242 count += 1
242 if not l:
243 if not l:
243 continue
244 continue
244 s = l.split(" ", 1)
245 s = l.split(" ", 1)
245 if len(s) != 2:
246 if len(s) != 2:
246 warn(_("cannot parse entry"))
247 warn(_("cannot parse entry"))
247 continue
248 continue
248 node, key = s
249 node, key = s
249 key = util.tolocal(key.strip()) # stored in UTF-8
250 key = util.tolocal(key.strip()) # stored in UTF-8
250 try:
251 try:
251 bin_n = bin(node)
252 bin_n = bin(node)
252 except TypeError:
253 except TypeError:
253 warn(_("node '%s' is not well formed") % node)
254 warn(_("node '%s' is not well formed") % node)
254 continue
255 continue
255 if bin_n not in self.changelog.nodemap:
256 if bin_n not in self.changelog.nodemap:
256 warn(_("tag '%s' refers to unknown node") % key)
257 warn(_("tag '%s' refers to unknown node") % key)
257 continue
258 continue
258
259
259 h = []
260 h = []
260 if key in filetags:
261 if key in filetags:
261 n, h = filetags[key]
262 n, h = filetags[key]
262 h.append(n)
263 h.append(n)
263 filetags[key] = (bin_n, h)
264 filetags[key] = (bin_n, h)
264
265
265 for k, nh in filetags.items():
266 for k, nh in filetags.items():
266 if k not in globaltags:
267 if k not in globaltags:
267 globaltags[k] = nh
268 globaltags[k] = nh
268 tagtypes[k] = tagtype
269 tagtypes[k] = tagtype
269 continue
270 continue
270
271
271 # we prefer the global tag if:
272 # we prefer the global tag if:
272 # it supercedes us OR
273 # it supercedes us OR
273 # mutual supercedes and it has a higher rank
274 # mutual supercedes and it has a higher rank
274 # otherwise we win because we're tip-most
275 # otherwise we win because we're tip-most
275 an, ah = nh
276 an, ah = nh
276 bn, bh = globaltags[k]
277 bn, bh = globaltags[k]
277 if (bn != an and an in bh and
278 if (bn != an and an in bh and
278 (bn not in ah or len(bh) > len(ah))):
279 (bn not in ah or len(bh) > len(ah))):
279 an = bn
280 an = bn
280 ah.extend([n for n in bh if n not in ah])
281 ah.extend([n for n in bh if n not in ah])
281 globaltags[k] = an, ah
282 globaltags[k] = an, ah
282 tagtypes[k] = tagtype
283 tagtypes[k] = tagtype
283
284
284 # read the tags file from each head, ending with the tip
285 # read the tags file from each head, ending with the tip
285 f = None
286 f = None
286 for rev, node, fnode in self._hgtagsnodes():
287 for rev, node, fnode in self._hgtagsnodes():
287 f = (f and f.filectx(fnode) or
288 f = (f and f.filectx(fnode) or
288 self.filectx('.hgtags', fileid=fnode))
289 self.filectx('.hgtags', fileid=fnode))
289 readtags(f.data().splitlines(), f, "global")
290 readtags(f.data().splitlines(), f, "global")
290
291
291 try:
292 try:
292 data = util.fromlocal(self.opener("localtags").read())
293 data = util.fromlocal(self.opener("localtags").read())
293 # localtags are stored in the local character set
294 # localtags are stored in the local character set
294 # while the internal tag table is stored in UTF-8
295 # while the internal tag table is stored in UTF-8
295 readtags(data.splitlines(), "localtags", "local")
296 readtags(data.splitlines(), "localtags", "local")
296 except IOError:
297 except IOError:
297 pass
298 pass
298
299
299 self.tagscache = {}
300 self.tagscache = {}
300 self._tagstypecache = {}
301 self._tagstypecache = {}
301 for k,nh in globaltags.items():
302 for k,nh in globaltags.items():
302 n = nh[0]
303 n = nh[0]
303 if n != nullid:
304 if n != nullid:
304 self.tagscache[k] = n
305 self.tagscache[k] = n
305 self._tagstypecache[k] = tagtypes[k]
306 self._tagstypecache[k] = tagtypes[k]
306 self.tagscache['tip'] = self.changelog.tip()
307 self.tagscache['tip'] = self.changelog.tip()
307
308
308 return self.tagscache
309 return self.tagscache
309
310
310 def tagtype(self, tagname):
311 def tagtype(self, tagname):
311 '''
312 '''
312 return the type of the given tag. result can be:
313 return the type of the given tag. result can be:
313
314
314 'local' : a local tag
315 'local' : a local tag
315 'global' : a global tag
316 'global' : a global tag
316 None : tag does not exist
317 None : tag does not exist
317 '''
318 '''
318
319
319 self.tags()
320 self.tags()
320
321
321 return self._tagstypecache.get(tagname)
322 return self._tagstypecache.get(tagname)
322
323
323 def _hgtagsnodes(self):
324 def _hgtagsnodes(self):
324 heads = self.heads()
325 heads = self.heads()
325 heads.reverse()
326 heads.reverse()
326 last = {}
327 last = {}
327 ret = []
328 ret = []
328 for node in heads:
329 for node in heads:
329 c = self.changectx(node)
330 c = self.changectx(node)
330 rev = c.rev()
331 rev = c.rev()
331 try:
332 try:
332 fnode = c.filenode('.hgtags')
333 fnode = c.filenode('.hgtags')
333 except revlog.LookupError:
334 except revlog.LookupError:
334 continue
335 continue
335 ret.append((rev, node, fnode))
336 ret.append((rev, node, fnode))
336 if fnode in last:
337 if fnode in last:
337 ret[last[fnode]] = None
338 ret[last[fnode]] = None
338 last[fnode] = len(ret) - 1
339 last[fnode] = len(ret) - 1
339 return [item for item in ret if item]
340 return [item for item in ret if item]
340
341
341 def tagslist(self):
342 def tagslist(self):
342 '''return a list of tags ordered by revision'''
343 '''return a list of tags ordered by revision'''
343 l = []
344 l = []
344 for t, n in self.tags().items():
345 for t, n in self.tags().items():
345 try:
346 try:
346 r = self.changelog.rev(n)
347 r = self.changelog.rev(n)
347 except:
348 except:
348 r = -2 # sort to the beginning of the list if unknown
349 r = -2 # sort to the beginning of the list if unknown
349 l.append((r, t, n))
350 l.append((r, t, n))
350 l.sort()
351 l.sort()
351 return [(t, n) for r, t, n in l]
352 return [(t, n) for r, t, n in l]
352
353
353 def nodetags(self, node):
354 def nodetags(self, node):
354 '''return the tags associated with a node'''
355 '''return the tags associated with a node'''
355 if not self.nodetagscache:
356 if not self.nodetagscache:
356 self.nodetagscache = {}
357 self.nodetagscache = {}
357 for t, n in self.tags().items():
358 for t, n in self.tags().items():
358 self.nodetagscache.setdefault(n, []).append(t)
359 self.nodetagscache.setdefault(n, []).append(t)
359 return self.nodetagscache.get(node, [])
360 return self.nodetagscache.get(node, [])
360
361
361 def _branchtags(self, partial, lrev):
362 def _branchtags(self, partial, lrev):
362 tiprev = self.changelog.count() - 1
363 tiprev = self.changelog.count() - 1
363 if lrev != tiprev:
364 if lrev != tiprev:
364 self._updatebranchcache(partial, lrev+1, tiprev+1)
365 self._updatebranchcache(partial, lrev+1, tiprev+1)
365 self._writebranchcache(partial, self.changelog.tip(), tiprev)
366 self._writebranchcache(partial, self.changelog.tip(), tiprev)
366
367
367 return partial
368 return partial
368
369
369 def branchtags(self):
370 def branchtags(self):
370 tip = self.changelog.tip()
371 tip = self.changelog.tip()
371 if self.branchcache is not None and self._branchcachetip == tip:
372 if self.branchcache is not None and self._branchcachetip == tip:
372 return self.branchcache
373 return self.branchcache
373
374
374 oldtip = self._branchcachetip
375 oldtip = self._branchcachetip
375 self._branchcachetip = tip
376 self._branchcachetip = tip
376 if self.branchcache is None:
377 if self.branchcache is None:
377 self.branchcache = {} # avoid recursion in changectx
378 self.branchcache = {} # avoid recursion in changectx
378 else:
379 else:
379 self.branchcache.clear() # keep using the same dict
380 self.branchcache.clear() # keep using the same dict
380 if oldtip is None or oldtip not in self.changelog.nodemap:
381 if oldtip is None or oldtip not in self.changelog.nodemap:
381 partial, last, lrev = self._readbranchcache()
382 partial, last, lrev = self._readbranchcache()
382 else:
383 else:
383 lrev = self.changelog.rev(oldtip)
384 lrev = self.changelog.rev(oldtip)
384 partial = self._ubranchcache
385 partial = self._ubranchcache
385
386
386 self._branchtags(partial, lrev)
387 self._branchtags(partial, lrev)
387
388
388 # the branch cache is stored on disk as UTF-8, but in the local
389 # the branch cache is stored on disk as UTF-8, but in the local
389 # charset internally
390 # charset internally
390 for k, v in partial.items():
391 for k, v in partial.items():
391 self.branchcache[util.tolocal(k)] = v
392 self.branchcache[util.tolocal(k)] = v
392 self._ubranchcache = partial
393 self._ubranchcache = partial
393 return self.branchcache
394 return self.branchcache
394
395
395 def _readbranchcache(self):
396 def _readbranchcache(self):
396 partial = {}
397 partial = {}
397 try:
398 try:
398 f = self.opener("branch.cache")
399 f = self.opener("branch.cache")
399 lines = f.read().split('\n')
400 lines = f.read().split('\n')
400 f.close()
401 f.close()
401 except (IOError, OSError):
402 except (IOError, OSError):
402 return {}, nullid, nullrev
403 return {}, nullid, nullrev
403
404
404 try:
405 try:
405 last, lrev = lines.pop(0).split(" ", 1)
406 last, lrev = lines.pop(0).split(" ", 1)
406 last, lrev = bin(last), int(lrev)
407 last, lrev = bin(last), int(lrev)
407 if not (lrev < self.changelog.count() and
408 if not (lrev < self.changelog.count() and
408 self.changelog.node(lrev) == last): # sanity check
409 self.changelog.node(lrev) == last): # sanity check
409 # invalidate the cache
410 # invalidate the cache
410 raise ValueError('invalidating branch cache (tip differs)')
411 raise ValueError('invalidating branch cache (tip differs)')
411 for l in lines:
412 for l in lines:
412 if not l: continue
413 if not l: continue
413 node, label = l.split(" ", 1)
414 node, label = l.split(" ", 1)
414 partial[label.strip()] = bin(node)
415 partial[label.strip()] = bin(node)
415 except (KeyboardInterrupt, util.SignalInterrupt):
416 except (KeyboardInterrupt, util.SignalInterrupt):
416 raise
417 raise
417 except Exception, inst:
418 except Exception, inst:
418 if self.ui.debugflag:
419 if self.ui.debugflag:
419 self.ui.warn(str(inst), '\n')
420 self.ui.warn(str(inst), '\n')
420 partial, last, lrev = {}, nullid, nullrev
421 partial, last, lrev = {}, nullid, nullrev
421 return partial, last, lrev
422 return partial, last, lrev
422
423
423 def _writebranchcache(self, branches, tip, tiprev):
424 def _writebranchcache(self, branches, tip, tiprev):
424 try:
425 try:
425 f = self.opener("branch.cache", "w", atomictemp=True)
426 f = self.opener("branch.cache", "w", atomictemp=True)
426 f.write("%s %s\n" % (hex(tip), tiprev))
427 f.write("%s %s\n" % (hex(tip), tiprev))
427 for label, node in branches.iteritems():
428 for label, node in branches.iteritems():
428 f.write("%s %s\n" % (hex(node), label))
429 f.write("%s %s\n" % (hex(node), label))
429 f.rename()
430 f.rename()
430 except (IOError, OSError):
431 except (IOError, OSError):
431 pass
432 pass
432
433
433 def _updatebranchcache(self, partial, start, end):
434 def _updatebranchcache(self, partial, start, end):
434 for r in xrange(start, end):
435 for r in xrange(start, end):
435 c = self.changectx(r)
436 c = self.changectx(r)
436 b = c.branch()
437 b = c.branch()
437 partial[b] = c.node()
438 partial[b] = c.node()
438
439
439 def lookup(self, key):
440 def lookup(self, key):
440 if key == '.':
441 if key == '.':
441 key, second = self.dirstate.parents()
442 key, second = self.dirstate.parents()
442 if key == nullid:
443 if key == nullid:
443 raise repo.RepoError(_("no revision checked out"))
444 raise repo.RepoError(_("no revision checked out"))
444 if second != nullid:
445 if second != nullid:
445 self.ui.warn(_("warning: working directory has two parents, "
446 self.ui.warn(_("warning: working directory has two parents, "
446 "tag '.' uses the first\n"))
447 "tag '.' uses the first\n"))
447 elif key == 'null':
448 elif key == 'null':
448 return nullid
449 return nullid
449 n = self.changelog._match(key)
450 n = self.changelog._match(key)
450 if n:
451 if n:
451 return n
452 return n
452 if key in self.tags():
453 if key in self.tags():
453 return self.tags()[key]
454 return self.tags()[key]
454 if key in self.branchtags():
455 if key in self.branchtags():
455 return self.branchtags()[key]
456 return self.branchtags()[key]
456 n = self.changelog._partialmatch(key)
457 n = self.changelog._partialmatch(key)
457 if n:
458 if n:
458 return n
459 return n
459 try:
460 try:
460 if len(key) == 20:
461 if len(key) == 20:
461 key = hex(key)
462 key = hex(key)
462 except:
463 except:
463 pass
464 pass
464 raise repo.RepoError(_("unknown revision '%s'") % key)
465 raise repo.RepoError(_("unknown revision '%s'") % key)
465
466
466 def local(self):
467 def local(self):
467 return True
468 return True
468
469
469 def join(self, f):
470 def join(self, f):
470 return os.path.join(self.path, f)
471 return os.path.join(self.path, f)
471
472
472 def sjoin(self, f):
473 def sjoin(self, f):
473 f = self.encodefn(f)
474 f = self.encodefn(f)
474 return os.path.join(self.spath, f)
475 return os.path.join(self.spath, f)
475
476
476 def wjoin(self, f):
477 def wjoin(self, f):
477 return os.path.join(self.root, f)
478 return os.path.join(self.root, f)
478
479
479 def rjoin(self, f):
480 def rjoin(self, f):
480 return os.path.join(self.root, util.pconvert(f))
481 return os.path.join(self.root, util.pconvert(f))
481
482
482 def file(self, f):
483 def file(self, f):
483 if f[0] == '/':
484 if f[0] == '/':
484 f = f[1:]
485 f = f[1:]
485 return filelog.filelog(self.sopener, f)
486 return filelog.filelog(self.sopener, f)
486
487
487 def changectx(self, changeid=None):
488 def changectx(self, changeid=None):
488 return context.changectx(self, changeid)
489 return context.changectx(self, changeid)
489
490
490 def workingctx(self):
491 def workingctx(self):
491 return context.workingctx(self)
492 return context.workingctx(self)
492
493
493 def parents(self, changeid=None):
494 def parents(self, changeid=None):
494 '''
495 '''
495 get list of changectxs for parents of changeid or working directory
496 get list of changectxs for parents of changeid or working directory
496 '''
497 '''
497 if changeid is None:
498 if changeid is None:
498 pl = self.dirstate.parents()
499 pl = self.dirstate.parents()
499 else:
500 else:
500 n = self.changelog.lookup(changeid)
501 n = self.changelog.lookup(changeid)
501 pl = self.changelog.parents(n)
502 pl = self.changelog.parents(n)
502 if pl[1] == nullid:
503 if pl[1] == nullid:
503 return [self.changectx(pl[0])]
504 return [self.changectx(pl[0])]
504 return [self.changectx(pl[0]), self.changectx(pl[1])]
505 return [self.changectx(pl[0]), self.changectx(pl[1])]
505
506
506 def filectx(self, path, changeid=None, fileid=None):
507 def filectx(self, path, changeid=None, fileid=None):
507 """changeid can be a changeset revision, node, or tag.
508 """changeid can be a changeset revision, node, or tag.
508 fileid can be a file revision or node."""
509 fileid can be a file revision or node."""
509 return context.filectx(self, path, changeid, fileid)
510 return context.filectx(self, path, changeid, fileid)
510
511
511 def getcwd(self):
512 def getcwd(self):
512 return self.dirstate.getcwd()
513 return self.dirstate.getcwd()
513
514
514 def pathto(self, f, cwd=None):
515 def pathto(self, f, cwd=None):
515 return self.dirstate.pathto(f, cwd)
516 return self.dirstate.pathto(f, cwd)
516
517
517 def wfile(self, f, mode='r'):
518 def wfile(self, f, mode='r'):
518 return self.wopener(f, mode)
519 return self.wopener(f, mode)
519
520
520 def _link(self, f):
521 def _link(self, f):
521 return os.path.islink(self.wjoin(f))
522 return os.path.islink(self.wjoin(f))
522
523
523 def _filter(self, filter, filename, data):
524 def _filter(self, filter, filename, data):
524 if filter not in self.filterpats:
525 if filter not in self.filterpats:
525 l = []
526 l = []
526 for pat, cmd in self.ui.configitems(filter):
527 for pat, cmd in self.ui.configitems(filter):
527 mf = util.matcher(self.root, "", [pat], [], [])[1]
528 mf = util.matcher(self.root, "", [pat], [], [])[1]
528 fn = None
529 fn = None
529 params = cmd
530 params = cmd
530 for name, filterfn in self._datafilters.iteritems():
531 for name, filterfn in self._datafilters.iteritems():
531 if cmd.startswith(name):
532 if cmd.startswith(name):
532 fn = filterfn
533 fn = filterfn
533 params = cmd[len(name):].lstrip()
534 params = cmd[len(name):].lstrip()
534 break
535 break
535 if not fn:
536 if not fn:
536 fn = lambda s, c, **kwargs: util.filter(s, c)
537 fn = lambda s, c, **kwargs: util.filter(s, c)
537 # Wrap old filters not supporting keyword arguments
538 # Wrap old filters not supporting keyword arguments
538 if not inspect.getargspec(fn)[2]:
539 if not inspect.getargspec(fn)[2]:
539 oldfn = fn
540 oldfn = fn
540 fn = lambda s, c, **kwargs: oldfn(s, c)
541 fn = lambda s, c, **kwargs: oldfn(s, c)
541 l.append((mf, fn, params))
542 l.append((mf, fn, params))
542 self.filterpats[filter] = l
543 self.filterpats[filter] = l
543
544
544 for mf, fn, cmd in self.filterpats[filter]:
545 for mf, fn, cmd in self.filterpats[filter]:
545 if mf(filename):
546 if mf(filename):
546 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
547 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
547 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
548 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
548 break
549 break
549
550
550 return data
551 return data
551
552
552 def adddatafilter(self, name, filter):
553 def adddatafilter(self, name, filter):
553 self._datafilters[name] = filter
554 self._datafilters[name] = filter
554
555
555 def wread(self, filename):
556 def wread(self, filename):
556 if self._link(filename):
557 if self._link(filename):
557 data = os.readlink(self.wjoin(filename))
558 data = os.readlink(self.wjoin(filename))
558 else:
559 else:
559 data = self.wopener(filename, 'r').read()
560 data = self.wopener(filename, 'r').read()
560 return self._filter("encode", filename, data)
561 return self._filter("encode", filename, data)
561
562
562 def wwrite(self, filename, data, flags):
563 def wwrite(self, filename, data, flags):
563 data = self._filter("decode", filename, data)
564 data = self._filter("decode", filename, data)
564 try:
565 try:
565 os.unlink(self.wjoin(filename))
566 os.unlink(self.wjoin(filename))
566 except OSError:
567 except OSError:
567 pass
568 pass
568 self.wopener(filename, 'w').write(data)
569 self.wopener(filename, 'w').write(data)
569 util.set_flags(self.wjoin(filename), flags)
570 util.set_flags(self.wjoin(filename), flags)
570
571
571 def wwritedata(self, filename, data):
572 def wwritedata(self, filename, data):
572 return self._filter("decode", filename, data)
573 return self._filter("decode", filename, data)
573
574
574 def transaction(self):
575 def transaction(self):
575 if self._transref and self._transref():
576 if self._transref and self._transref():
576 return self._transref().nest()
577 return self._transref().nest()
577
578
578 # abort here if the journal already exists
579 # abort here if the journal already exists
579 if os.path.exists(self.sjoin("journal")):
580 if os.path.exists(self.sjoin("journal")):
580 raise repo.RepoError(_("journal already exists - run hg recover"))
581 raise repo.RepoError(_("journal already exists - run hg recover"))
581
582
582 # save dirstate for rollback
583 # save dirstate for rollback
583 try:
584 try:
584 ds = self.opener("dirstate").read()
585 ds = self.opener("dirstate").read()
585 except IOError:
586 except IOError:
586 ds = ""
587 ds = ""
587 self.opener("journal.dirstate", "w").write(ds)
588 self.opener("journal.dirstate", "w").write(ds)
588 self.opener("journal.branch", "w").write(self.dirstate.branch())
589 self.opener("journal.branch", "w").write(self.dirstate.branch())
589
590
590 renames = [(self.sjoin("journal"), self.sjoin("undo")),
591 renames = [(self.sjoin("journal"), self.sjoin("undo")),
591 (self.join("journal.dirstate"), self.join("undo.dirstate")),
592 (self.join("journal.dirstate"), self.join("undo.dirstate")),
592 (self.join("journal.branch"), self.join("undo.branch"))]
593 (self.join("journal.branch"), self.join("undo.branch"))]
593 tr = transaction.transaction(self.ui.warn, self.sopener,
594 tr = transaction.transaction(self.ui.warn, self.sopener,
594 self.sjoin("journal"),
595 self.sjoin("journal"),
595 aftertrans(renames),
596 aftertrans(renames),
596 self._createmode)
597 self._createmode)
597 self._transref = weakref.ref(tr)
598 self._transref = weakref.ref(tr)
598 return tr
599 return tr
599
600
600 def recover(self):
601 def recover(self):
601 l = self.lock()
602 l = self.lock()
602 try:
603 try:
603 if os.path.exists(self.sjoin("journal")):
604 if os.path.exists(self.sjoin("journal")):
604 self.ui.status(_("rolling back interrupted transaction\n"))
605 self.ui.status(_("rolling back interrupted transaction\n"))
605 transaction.rollback(self.sopener, self.sjoin("journal"))
606 transaction.rollback(self.sopener, self.sjoin("journal"))
606 self.invalidate()
607 self.invalidate()
607 return True
608 return True
608 else:
609 else:
609 self.ui.warn(_("no interrupted transaction available\n"))
610 self.ui.warn(_("no interrupted transaction available\n"))
610 return False
611 return False
611 finally:
612 finally:
612 del l
613 del l
613
614
614 def rollback(self):
615 def rollback(self):
615 wlock = lock = None
616 wlock = lock = None
616 try:
617 try:
617 wlock = self.wlock()
618 wlock = self.wlock()
618 lock = self.lock()
619 lock = self.lock()
619 if os.path.exists(self.sjoin("undo")):
620 if os.path.exists(self.sjoin("undo")):
620 self.ui.status(_("rolling back last transaction\n"))
621 self.ui.status(_("rolling back last transaction\n"))
621 transaction.rollback(self.sopener, self.sjoin("undo"))
622 transaction.rollback(self.sopener, self.sjoin("undo"))
622 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
623 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
623 try:
624 try:
624 branch = self.opener("undo.branch").read()
625 branch = self.opener("undo.branch").read()
625 self.dirstate.setbranch(branch)
626 self.dirstate.setbranch(branch)
626 except IOError:
627 except IOError:
627 self.ui.warn(_("Named branch could not be reset, "
628 self.ui.warn(_("Named branch could not be reset, "
628 "current branch still is: %s\n")
629 "current branch still is: %s\n")
629 % util.tolocal(self.dirstate.branch()))
630 % util.tolocal(self.dirstate.branch()))
630 self.invalidate()
631 self.invalidate()
631 self.dirstate.invalidate()
632 self.dirstate.invalidate()
632 else:
633 else:
633 self.ui.warn(_("no rollback information available\n"))
634 self.ui.warn(_("no rollback information available\n"))
634 finally:
635 finally:
635 del lock, wlock
636 del lock, wlock
636
637
637 def invalidate(self):
638 def invalidate(self):
638 for a in "changelog manifest".split():
639 for a in "changelog manifest".split():
639 if a in self.__dict__:
640 if a in self.__dict__:
640 delattr(self, a)
641 delattr(self, a)
641 self.tagscache = None
642 self.tagscache = None
642 self._tagstypecache = None
643 self._tagstypecache = None
643 self.nodetagscache = None
644 self.nodetagscache = None
644 self.branchcache = None
645 self.branchcache = None
645 self._ubranchcache = None
646 self._ubranchcache = None
646 self._branchcachetip = None
647 self._branchcachetip = None
647
648
648 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
649 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
649 try:
650 try:
650 l = lock.lock(lockname, 0, releasefn, desc=desc)
651 l = lock.lock(lockname, 0, releasefn, desc=desc)
651 except lock.LockHeld, inst:
652 except lock.LockHeld, inst:
652 if not wait:
653 if not wait:
653 raise
654 raise
654 self.ui.warn(_("waiting for lock on %s held by %r\n") %
655 self.ui.warn(_("waiting for lock on %s held by %r\n") %
655 (desc, inst.locker))
656 (desc, inst.locker))
656 # default to 600 seconds timeout
657 # default to 600 seconds timeout
657 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
658 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
658 releasefn, desc=desc)
659 releasefn, desc=desc)
659 if acquirefn:
660 if acquirefn:
660 acquirefn()
661 acquirefn()
661 return l
662 return l
662
663
663 def lock(self, wait=True):
664 def lock(self, wait=True):
664 if self._lockref and self._lockref():
665 if self._lockref and self._lockref():
665 return self._lockref()
666 return self._lockref()
666
667
667 l = self._lock(self.sjoin("lock"), wait, None, self.invalidate,
668 l = self._lock(self.sjoin("lock"), wait, None, self.invalidate,
668 _('repository %s') % self.origroot)
669 _('repository %s') % self.origroot)
669 self._lockref = weakref.ref(l)
670 self._lockref = weakref.ref(l)
670 return l
671 return l
671
672
672 def wlock(self, wait=True):
673 def wlock(self, wait=True):
673 if self._wlockref and self._wlockref():
674 if self._wlockref and self._wlockref():
674 return self._wlockref()
675 return self._wlockref()
675
676
676 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
677 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
677 self.dirstate.invalidate, _('working directory of %s') %
678 self.dirstate.invalidate, _('working directory of %s') %
678 self.origroot)
679 self.origroot)
679 self._wlockref = weakref.ref(l)
680 self._wlockref = weakref.ref(l)
680 return l
681 return l
681
682
682 def filecommit(self, fn, manifest1, manifest2, linkrev, tr, changelist):
683 def filecommit(self, fn, manifest1, manifest2, linkrev, tr, changelist):
683 """
684 """
684 commit an individual file as part of a larger transaction
685 commit an individual file as part of a larger transaction
685 """
686 """
686
687
687 t = self.wread(fn)
688 t = self.wread(fn)
688 fl = self.file(fn)
689 fl = self.file(fn)
689 fp1 = manifest1.get(fn, nullid)
690 fp1 = manifest1.get(fn, nullid)
690 fp2 = manifest2.get(fn, nullid)
691 fp2 = manifest2.get(fn, nullid)
691
692
692 meta = {}
693 meta = {}
693 cp = self.dirstate.copied(fn)
694 cp = self.dirstate.copied(fn)
694 if cp:
695 if cp:
695 # Mark the new revision of this file as a copy of another
696 # Mark the new revision of this file as a copy of another
696 # file. This copy data will effectively act as a parent
697 # file. This copy data will effectively act as a parent
697 # of this new revision. If this is a merge, the first
698 # of this new revision. If this is a merge, the first
698 # parent will be the nullid (meaning "look up the copy data")
699 # parent will be the nullid (meaning "look up the copy data")
699 # and the second one will be the other parent. For example:
700 # and the second one will be the other parent. For example:
700 #
701 #
701 # 0 --- 1 --- 3 rev1 changes file foo
702 # 0 --- 1 --- 3 rev1 changes file foo
702 # \ / rev2 renames foo to bar and changes it
703 # \ / rev2 renames foo to bar and changes it
703 # \- 2 -/ rev3 should have bar with all changes and
704 # \- 2 -/ rev3 should have bar with all changes and
704 # should record that bar descends from
705 # should record that bar descends from
705 # bar in rev2 and foo in rev1
706 # bar in rev2 and foo in rev1
706 #
707 #
707 # this allows this merge to succeed:
708 # this allows this merge to succeed:
708 #
709 #
709 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
710 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
710 # \ / merging rev3 and rev4 should use bar@rev2
711 # \ / merging rev3 and rev4 should use bar@rev2
711 # \- 2 --- 4 as the merge base
712 # \- 2 --- 4 as the merge base
712 #
713 #
713 meta["copy"] = cp
714 meta["copy"] = cp
714 if not manifest2: # not a branch merge
715 if not manifest2: # not a branch merge
715 meta["copyrev"] = hex(manifest1[cp])
716 meta["copyrev"] = hex(manifest1[cp])
716 fp2 = nullid
717 fp2 = nullid
717 elif fp2 != nullid: # copied on remote side
718 elif fp2 != nullid: # copied on remote side
718 meta["copyrev"] = hex(manifest1[cp])
719 meta["copyrev"] = hex(manifest1[cp])
719 elif fp1 != nullid: # copied on local side, reversed
720 elif fp1 != nullid: # copied on local side, reversed
720 meta["copyrev"] = hex(manifest2[cp])
721 meta["copyrev"] = hex(manifest2[cp])
721 fp2 = fp1
722 fp2 = fp1
722 elif cp in manifest2: # directory rename on local side
723 elif cp in manifest2: # directory rename on local side
723 meta["copyrev"] = hex(manifest2[cp])
724 meta["copyrev"] = hex(manifest2[cp])
724 else: # directory rename on remote side
725 else: # directory rename on remote side
725 meta["copyrev"] = hex(manifest1[cp])
726 meta["copyrev"] = hex(manifest1[cp])
726 self.ui.debug(_(" %s: copy %s:%s\n") %
727 self.ui.debug(_(" %s: copy %s:%s\n") %
727 (fn, cp, meta["copyrev"]))
728 (fn, cp, meta["copyrev"]))
728 fp1 = nullid
729 fp1 = nullid
729 elif fp2 != nullid:
730 elif fp2 != nullid:
730 # is one parent an ancestor of the other?
731 # is one parent an ancestor of the other?
731 fpa = fl.ancestor(fp1, fp2)
732 fpa = fl.ancestor(fp1, fp2)
732 if fpa == fp1:
733 if fpa == fp1:
733 fp1, fp2 = fp2, nullid
734 fp1, fp2 = fp2, nullid
734 elif fpa == fp2:
735 elif fpa == fp2:
735 fp2 = nullid
736 fp2 = nullid
736
737
737 # is the file unmodified from the parent? report existing entry
738 # is the file unmodified from the parent? report existing entry
738 if fp2 == nullid and not fl.cmp(fp1, t) and not meta:
739 if fp2 == nullid and not fl.cmp(fp1, t) and not meta:
739 return fp1
740 return fp1
740
741
741 changelist.append(fn)
742 changelist.append(fn)
742 return fl.add(t, meta, tr, linkrev, fp1, fp2)
743 return fl.add(t, meta, tr, linkrev, fp1, fp2)
743
744
744 def rawcommit(self, files, text, user, date, p1=None, p2=None, extra={}):
745 def rawcommit(self, files, text, user, date, p1=None, p2=None, extra={}):
745 if p1 is None:
746 if p1 is None:
746 p1, p2 = self.dirstate.parents()
747 p1, p2 = self.dirstate.parents()
747 return self.commit(files=files, text=text, user=user, date=date,
748 return self.commit(files=files, text=text, user=user, date=date,
748 p1=p1, p2=p2, extra=extra, empty_ok=True)
749 p1=p1, p2=p2, extra=extra, empty_ok=True)
749
750
750 def commit(self, files=None, text="", user=None, date=None,
751 def commit(self, files=None, text="", user=None, date=None,
751 match=util.always, force=False, force_editor=False,
752 match=None, force=False, force_editor=False,
752 p1=None, p2=None, extra={}, empty_ok=False):
753 p1=None, p2=None, extra={}, empty_ok=False):
753 wlock = lock = tr = None
754 wlock = lock = tr = None
754 valid = 0 # don't save the dirstate if this isn't set
755 valid = 0 # don't save the dirstate if this isn't set
755 if files:
756 if files:
756 files = util.unique(files)
757 files = util.unique(files)
757 try:
758 try:
758 wlock = self.wlock()
759 wlock = self.wlock()
759 lock = self.lock()
760 lock = self.lock()
760 commit = []
761 commit = []
761 remove = []
762 remove = []
762 changed = []
763 changed = []
763 use_dirstate = (p1 is None) # not rawcommit
764 use_dirstate = (p1 is None) # not rawcommit
764 extra = extra.copy()
765 extra = extra.copy()
765
766
766 if use_dirstate:
767 if use_dirstate:
767 if files:
768 if files:
768 for f in files:
769 for f in files:
769 s = self.dirstate[f]
770 s = self.dirstate[f]
770 if s in 'nma':
771 if s in 'nma':
771 commit.append(f)
772 commit.append(f)
772 elif s == 'r':
773 elif s == 'r':
773 remove.append(f)
774 remove.append(f)
774 else:
775 else:
775 self.ui.warn(_("%s not tracked!\n") % f)
776 self.ui.warn(_("%s not tracked!\n") % f)
776 else:
777 else:
777 changes = self.status(match=match)[:5]
778 changes = self.status(match=match)[:5]
778 modified, added, removed, deleted, unknown = changes
779 modified, added, removed, deleted, unknown = changes
779 commit = modified + added
780 commit = modified + added
780 remove = removed
781 remove = removed
781 else:
782 else:
782 commit = files
783 commit = files
783
784
784 if use_dirstate:
785 if use_dirstate:
785 p1, p2 = self.dirstate.parents()
786 p1, p2 = self.dirstate.parents()
786 update_dirstate = True
787 update_dirstate = True
787
788
788 if (not force and p2 != nullid and
789 if (not force and p2 != nullid and
789 (match.files() or match.anypats())):
790 (match.files() or match.anypats())):
790 raise util.Abort(_('cannot partially commit a merge '
791 raise util.Abort(_('cannot partially commit a merge '
791 '(do not specify files or patterns)'))
792 '(do not specify files or patterns)'))
792 else:
793 else:
793 p1, p2 = p1, p2 or nullid
794 p1, p2 = p1, p2 or nullid
794 update_dirstate = (self.dirstate.parents()[0] == p1)
795 update_dirstate = (self.dirstate.parents()[0] == p1)
795
796
796 c1 = self.changelog.read(p1)
797 c1 = self.changelog.read(p1)
797 c2 = self.changelog.read(p2)
798 c2 = self.changelog.read(p2)
798 m1 = self.manifest.read(c1[0]).copy()
799 m1 = self.manifest.read(c1[0]).copy()
799 m2 = self.manifest.read(c2[0])
800 m2 = self.manifest.read(c2[0])
800
801
801 if use_dirstate:
802 if use_dirstate:
802 branchname = self.workingctx().branch()
803 branchname = self.workingctx().branch()
803 try:
804 try:
804 branchname = branchname.decode('UTF-8').encode('UTF-8')
805 branchname = branchname.decode('UTF-8').encode('UTF-8')
805 except UnicodeDecodeError:
806 except UnicodeDecodeError:
806 raise util.Abort(_('branch name not in UTF-8!'))
807 raise util.Abort(_('branch name not in UTF-8!'))
807 else:
808 else:
808 branchname = ""
809 branchname = ""
809
810
810 if use_dirstate:
811 if use_dirstate:
811 oldname = c1[5].get("branch") # stored in UTF-8
812 oldname = c1[5].get("branch") # stored in UTF-8
812 if (not commit and not remove and not force and p2 == nullid
813 if (not commit and not remove and not force and p2 == nullid
813 and branchname == oldname):
814 and branchname == oldname):
814 self.ui.status(_("nothing changed\n"))
815 self.ui.status(_("nothing changed\n"))
815 return None
816 return None
816
817
817 xp1 = hex(p1)
818 xp1 = hex(p1)
818 if p2 == nullid: xp2 = ''
819 if p2 == nullid: xp2 = ''
819 else: xp2 = hex(p2)
820 else: xp2 = hex(p2)
820
821
821 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
822 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
822
823
823 tr = self.transaction()
824 tr = self.transaction()
824 trp = weakref.proxy(tr)
825 trp = weakref.proxy(tr)
825
826
826 # check in files
827 # check in files
827 new = {}
828 new = {}
828 linkrev = self.changelog.count()
829 linkrev = self.changelog.count()
829 commit.sort()
830 commit.sort()
830 is_exec = util.execfunc(self.root, m1.execf)
831 is_exec = util.execfunc(self.root, m1.execf)
831 is_link = util.linkfunc(self.root, m1.linkf)
832 is_link = util.linkfunc(self.root, m1.linkf)
832 for f in commit:
833 for f in commit:
833 self.ui.note(f + "\n")
834 self.ui.note(f + "\n")
834 try:
835 try:
835 new[f] = self.filecommit(f, m1, m2, linkrev, trp, changed)
836 new[f] = self.filecommit(f, m1, m2, linkrev, trp, changed)
836 new_exec = is_exec(f)
837 new_exec = is_exec(f)
837 new_link = is_link(f)
838 new_link = is_link(f)
838 if ((not changed or changed[-1] != f) and
839 if ((not changed or changed[-1] != f) and
839 m2.get(f) != new[f]):
840 m2.get(f) != new[f]):
840 # mention the file in the changelog if some
841 # mention the file in the changelog if some
841 # flag changed, even if there was no content
842 # flag changed, even if there was no content
842 # change.
843 # change.
843 old_exec = m1.execf(f)
844 old_exec = m1.execf(f)
844 old_link = m1.linkf(f)
845 old_link = m1.linkf(f)
845 if old_exec != new_exec or old_link != new_link:
846 if old_exec != new_exec or old_link != new_link:
846 changed.append(f)
847 changed.append(f)
847 m1.set(f, new_exec, new_link)
848 m1.set(f, new_exec, new_link)
848 if use_dirstate:
849 if use_dirstate:
849 self.dirstate.normal(f)
850 self.dirstate.normal(f)
850
851
851 except (OSError, IOError):
852 except (OSError, IOError):
852 if use_dirstate:
853 if use_dirstate:
853 self.ui.warn(_("trouble committing %s!\n") % f)
854 self.ui.warn(_("trouble committing %s!\n") % f)
854 raise
855 raise
855 else:
856 else:
856 remove.append(f)
857 remove.append(f)
857
858
858 # update manifest
859 # update manifest
859 m1.update(new)
860 m1.update(new)
860 remove.sort()
861 remove.sort()
861 removed = []
862 removed = []
862
863
863 for f in remove:
864 for f in remove:
864 if f in m1:
865 if f in m1:
865 del m1[f]
866 del m1[f]
866 removed.append(f)
867 removed.append(f)
867 elif f in m2:
868 elif f in m2:
868 removed.append(f)
869 removed.append(f)
869 mn = self.manifest.add(m1, trp, linkrev, c1[0], c2[0],
870 mn = self.manifest.add(m1, trp, linkrev, c1[0], c2[0],
870 (new, removed))
871 (new, removed))
871
872
872 # add changeset
873 # add changeset
873 new = new.keys()
874 new = new.keys()
874 new.sort()
875 new.sort()
875
876
876 user = user or self.ui.username()
877 user = user or self.ui.username()
877 if (not empty_ok and not text) or force_editor:
878 if (not empty_ok and not text) or force_editor:
878 edittext = []
879 edittext = []
879 if text:
880 if text:
880 edittext.append(text)
881 edittext.append(text)
881 edittext.append("")
882 edittext.append("")
882 edittext.append(_("HG: Enter commit message."
883 edittext.append(_("HG: Enter commit message."
883 " Lines beginning with 'HG:' are removed."))
884 " Lines beginning with 'HG:' are removed."))
884 edittext.append("HG: --")
885 edittext.append("HG: --")
885 edittext.append("HG: user: %s" % user)
886 edittext.append("HG: user: %s" % user)
886 if p2 != nullid:
887 if p2 != nullid:
887 edittext.append("HG: branch merge")
888 edittext.append("HG: branch merge")
888 if branchname:
889 if branchname:
889 edittext.append("HG: branch '%s'" % util.tolocal(branchname))
890 edittext.append("HG: branch '%s'" % util.tolocal(branchname))
890 edittext.extend(["HG: changed %s" % f for f in changed])
891 edittext.extend(["HG: changed %s" % f for f in changed])
891 edittext.extend(["HG: removed %s" % f for f in removed])
892 edittext.extend(["HG: removed %s" % f for f in removed])
892 if not changed and not remove:
893 if not changed and not remove:
893 edittext.append("HG: no files changed")
894 edittext.append("HG: no files changed")
894 edittext.append("")
895 edittext.append("")
895 # run editor in the repository root
896 # run editor in the repository root
896 olddir = os.getcwd()
897 olddir = os.getcwd()
897 os.chdir(self.root)
898 os.chdir(self.root)
898 text = self.ui.edit("\n".join(edittext), user)
899 text = self.ui.edit("\n".join(edittext), user)
899 os.chdir(olddir)
900 os.chdir(olddir)
900
901
901 if branchname:
902 if branchname:
902 extra["branch"] = branchname
903 extra["branch"] = branchname
903
904
904 lines = [line.rstrip() for line in text.rstrip().splitlines()]
905 lines = [line.rstrip() for line in text.rstrip().splitlines()]
905 while lines and not lines[0]:
906 while lines and not lines[0]:
906 del lines[0]
907 del lines[0]
907 if not lines and use_dirstate:
908 if not lines and use_dirstate:
908 raise util.Abort(_("empty commit message"))
909 raise util.Abort(_("empty commit message"))
909 text = '\n'.join(lines)
910 text = '\n'.join(lines)
910
911
911 n = self.changelog.add(mn, changed + removed, text, trp, p1, p2,
912 n = self.changelog.add(mn, changed + removed, text, trp, p1, p2,
912 user, date, extra)
913 user, date, extra)
913 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
914 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
914 parent2=xp2)
915 parent2=xp2)
915 tr.close()
916 tr.close()
916
917
917 if self.branchcache:
918 if self.branchcache:
918 self.branchtags()
919 self.branchtags()
919
920
920 if use_dirstate or update_dirstate:
921 if use_dirstate or update_dirstate:
921 self.dirstate.setparents(n)
922 self.dirstate.setparents(n)
922 if use_dirstate:
923 if use_dirstate:
923 for f in removed:
924 for f in removed:
924 self.dirstate.forget(f)
925 self.dirstate.forget(f)
925 valid = 1 # our dirstate updates are complete
926 valid = 1 # our dirstate updates are complete
926
927
927 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
928 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
928 return n
929 return n
929 finally:
930 finally:
930 if not valid: # don't save our updated dirstate
931 if not valid: # don't save our updated dirstate
931 self.dirstate.invalidate()
932 self.dirstate.invalidate()
932 del tr, lock, wlock
933 del tr, lock, wlock
933
934
934 def walk(self, match, node=None):
935 def walk(self, match, node=None):
935 '''
936 '''
936 walk recursively through the directory tree or a given
937 walk recursively through the directory tree or a given
937 changeset, finding all files matched by the match
938 changeset, finding all files matched by the match
938 function
939 function
939 '''
940 '''
940
941
941 if node:
942 if node:
942 fdict = dict.fromkeys(match.files())
943 fdict = dict.fromkeys(match.files())
943 # for dirstate.walk, files=['.'] means "walk the whole tree".
944 # for dirstate.walk, files=['.'] means "walk the whole tree".
944 # follow that here, too
945 # follow that here, too
945 fdict.pop('.', None)
946 fdict.pop('.', None)
946 mdict = self.manifest.read(self.changelog.read(node)[0])
947 mdict = self.manifest.read(self.changelog.read(node)[0])
947 mfiles = mdict.keys()
948 mfiles = mdict.keys()
948 mfiles.sort()
949 mfiles.sort()
949 for fn in mfiles:
950 for fn in mfiles:
950 for ffn in fdict:
951 for ffn in fdict:
951 # match if the file is the exact name or a directory
952 # match if the file is the exact name or a directory
952 if ffn == fn or fn.startswith("%s/" % ffn):
953 if ffn == fn or fn.startswith("%s/" % ffn):
953 del fdict[ffn]
954 del fdict[ffn]
954 break
955 break
955 if match(fn):
956 if match(fn):
956 yield fn
957 yield fn
957 ffiles = fdict.keys()
958 ffiles = fdict.keys()
958 ffiles.sort()
959 ffiles.sort()
959 for fn in ffiles:
960 for fn in ffiles:
960 if match.bad(fn, 'No such file in rev ' + short(node)) \
961 if match.bad(fn, 'No such file in rev ' + short(node)) \
961 and match(fn):
962 and match(fn):
962 yield fn
963 yield fn
963 else:
964 else:
964 for fn in self.dirstate.walk(match):
965 for fn in self.dirstate.walk(match):
965 yield fn
966 yield fn
966
967
967 def status(self, node1=None, node2=None, files=[], match=util.always,
968 def status(self, node1=None, node2=None, match=None,
968 list_ignored=False, list_clean=False, list_unknown=True):
969 list_ignored=False, list_clean=False, list_unknown=True):
969 """return status of files between two nodes or node and working directory
970 """return status of files between two nodes or node and working directory
970
971
971 If node1 is None, use the first dirstate parent instead.
972 If node1 is None, use the first dirstate parent instead.
972 If node2 is None, compare node1 with working directory.
973 If node2 is None, compare node1 with working directory.
973 """
974 """
974
975
975 def fcmp(fn, getnode):
976 def fcmp(fn, getnode):
976 t1 = self.wread(fn)
977 t1 = self.wread(fn)
977 return self.file(fn).cmp(getnode(fn), t1)
978 return self.file(fn).cmp(getnode(fn), t1)
978
979
979 def mfmatches(node):
980 def mfmatches(node):
980 change = self.changelog.read(node)
981 change = self.changelog.read(node)
981 mf = self.manifest.read(change[0]).copy()
982 mf = self.manifest.read(change[0]).copy()
982 for fn in mf.keys():
983 for fn in mf.keys():
983 if not match(fn):
984 if not match(fn):
984 del mf[fn]
985 del mf[fn]
985 return mf
986 return mf
986
987
988 if not match:
989 match = match_.always(self.root, self.getcwd())
990
987 modified, added, removed, deleted, unknown = [], [], [], [], []
991 modified, added, removed, deleted, unknown = [], [], [], [], []
988 ignored, clean = [], []
992 ignored, clean = [], []
989
993
990 compareworking = False
994 compareworking = False
991 if not node1 or (not node2 and node1 == self.dirstate.parents()[0]):
995 if not node1 or (not node2 and node1 == self.dirstate.parents()[0]):
992 compareworking = True
996 compareworking = True
993
997
994 if not compareworking:
998 if not compareworking:
995 # read the manifest from node1 before the manifest from node2,
999 # read the manifest from node1 before the manifest from node2,
996 # so that we'll hit the manifest cache if we're going through
1000 # so that we'll hit the manifest cache if we're going through
997 # all the revisions in parent->child order.
1001 # all the revisions in parent->child order.
998 mf1 = mfmatches(node1)
1002 mf1 = mfmatches(node1)
999
1003
1000 # are we comparing the working directory?
1004 # are we comparing the working directory?
1001 if not node2:
1005 if not node2:
1002 (lookup, modified, added, removed, deleted, unknown,
1006 (lookup, modified, added, removed, deleted, unknown,
1003 ignored, clean) = self.dirstate.status(files, match,
1007 ignored, clean) = self.dirstate.status(match, list_ignored,
1004 list_ignored, list_clean,
1008 list_clean, list_unknown)
1005 list_unknown)
1006
1007 # are we comparing working dir against its parent?
1009 # are we comparing working dir against its parent?
1008 if compareworking:
1010 if compareworking:
1009 if lookup:
1011 if lookup:
1010 fixup = []
1012 fixup = []
1011 # do a full compare of any files that might have changed
1013 # do a full compare of any files that might have changed
1012 ctx = self.changectx()
1014 ctx = self.changectx()
1013 mexec = lambda f: 'x' in ctx.fileflags(f)
1015 mexec = lambda f: 'x' in ctx.fileflags(f)
1014 mlink = lambda f: 'l' in ctx.fileflags(f)
1016 mlink = lambda f: 'l' in ctx.fileflags(f)
1015 is_exec = util.execfunc(self.root, mexec)
1017 is_exec = util.execfunc(self.root, mexec)
1016 is_link = util.linkfunc(self.root, mlink)
1018 is_link = util.linkfunc(self.root, mlink)
1017 def flags(f):
1019 def flags(f):
1018 return is_link(f) and 'l' or is_exec(f) and 'x' or ''
1020 return is_link(f) and 'l' or is_exec(f) and 'x' or ''
1019 for f in lookup:
1021 for f in lookup:
1020 if (f not in ctx or flags(f) != ctx.fileflags(f)
1022 if (f not in ctx or flags(f) != ctx.fileflags(f)
1021 or ctx[f].cmp(self.wread(f))):
1023 or ctx[f].cmp(self.wread(f))):
1022 modified.append(f)
1024 modified.append(f)
1023 else:
1025 else:
1024 fixup.append(f)
1026 fixup.append(f)
1025 if list_clean:
1027 if list_clean:
1026 clean.append(f)
1028 clean.append(f)
1027
1029
1028 # update dirstate for files that are actually clean
1030 # update dirstate for files that are actually clean
1029 if fixup:
1031 if fixup:
1030 wlock = None
1032 wlock = None
1031 try:
1033 try:
1032 try:
1034 try:
1033 wlock = self.wlock(False)
1035 wlock = self.wlock(False)
1034 except lock.LockException:
1036 except lock.LockException:
1035 pass
1037 pass
1036 if wlock:
1038 if wlock:
1037 for f in fixup:
1039 for f in fixup:
1038 self.dirstate.normal(f)
1040 self.dirstate.normal(f)
1039 finally:
1041 finally:
1040 del wlock
1042 del wlock
1041 else:
1043 else:
1042 # we are comparing working dir against non-parent
1044 # we are comparing working dir against non-parent
1043 # generate a pseudo-manifest for the working dir
1045 # generate a pseudo-manifest for the working dir
1044 # XXX: create it in dirstate.py ?
1046 # XXX: create it in dirstate.py ?
1045 mf2 = mfmatches(self.dirstate.parents()[0])
1047 mf2 = mfmatches(self.dirstate.parents()[0])
1046 is_exec = util.execfunc(self.root, mf2.execf)
1048 is_exec = util.execfunc(self.root, mf2.execf)
1047 is_link = util.linkfunc(self.root, mf2.linkf)
1049 is_link = util.linkfunc(self.root, mf2.linkf)
1048 for f in lookup + modified + added:
1050 for f in lookup + modified + added:
1049 mf2[f] = ""
1051 mf2[f] = ""
1050 mf2.set(f, is_exec(f), is_link(f))
1052 mf2.set(f, is_exec(f), is_link(f))
1051 for f in removed:
1053 for f in removed:
1052 if f in mf2:
1054 if f in mf2:
1053 del mf2[f]
1055 del mf2[f]
1054
1056
1055 else:
1057 else:
1056 # we are comparing two revisions
1058 # we are comparing two revisions
1057 mf2 = mfmatches(node2)
1059 mf2 = mfmatches(node2)
1058
1060
1059 if not compareworking:
1061 if not compareworking:
1060 # flush lists from dirstate before comparing manifests
1062 # flush lists from dirstate before comparing manifests
1061 modified, added, clean = [], [], []
1063 modified, added, clean = [], [], []
1062
1064
1063 # make sure to sort the files so we talk to the disk in a
1065 # make sure to sort the files so we talk to the disk in a
1064 # reasonable order
1066 # reasonable order
1065 mf2keys = mf2.keys()
1067 mf2keys = mf2.keys()
1066 mf2keys.sort()
1068 mf2keys.sort()
1067 getnode = lambda fn: mf1.get(fn, nullid)
1069 getnode = lambda fn: mf1.get(fn, nullid)
1068 for fn in mf2keys:
1070 for fn in mf2keys:
1069 if fn in mf1:
1071 if fn in mf1:
1070 if (mf1.flags(fn) != mf2.flags(fn) or
1072 if (mf1.flags(fn) != mf2.flags(fn) or
1071 (mf1[fn] != mf2[fn] and
1073 (mf1[fn] != mf2[fn] and
1072 (mf2[fn] != "" or fcmp(fn, getnode)))):
1074 (mf2[fn] != "" or fcmp(fn, getnode)))):
1073 modified.append(fn)
1075 modified.append(fn)
1074 elif list_clean:
1076 elif list_clean:
1075 clean.append(fn)
1077 clean.append(fn)
1076 del mf1[fn]
1078 del mf1[fn]
1077 else:
1079 else:
1078 added.append(fn)
1080 added.append(fn)
1079
1081
1080 removed = mf1.keys()
1082 removed = mf1.keys()
1081
1083
1082 # sort and return results:
1084 # sort and return results:
1083 for l in modified, added, removed, deleted, unknown, ignored, clean:
1085 for l in modified, added, removed, deleted, unknown, ignored, clean:
1084 l.sort()
1086 l.sort()
1085 return (modified, added, removed, deleted, unknown, ignored, clean)
1087 return (modified, added, removed, deleted, unknown, ignored, clean)
1086
1088
1087 def add(self, list):
1089 def add(self, list):
1088 wlock = self.wlock()
1090 wlock = self.wlock()
1089 try:
1091 try:
1090 rejected = []
1092 rejected = []
1091 for f in list:
1093 for f in list:
1092 p = self.wjoin(f)
1094 p = self.wjoin(f)
1093 try:
1095 try:
1094 st = os.lstat(p)
1096 st = os.lstat(p)
1095 except:
1097 except:
1096 self.ui.warn(_("%s does not exist!\n") % f)
1098 self.ui.warn(_("%s does not exist!\n") % f)
1097 rejected.append(f)
1099 rejected.append(f)
1098 continue
1100 continue
1099 if st.st_size > 10000000:
1101 if st.st_size > 10000000:
1100 self.ui.warn(_("%s: files over 10MB may cause memory and"
1102 self.ui.warn(_("%s: files over 10MB may cause memory and"
1101 " performance problems\n"
1103 " performance problems\n"
1102 "(use 'hg revert %s' to unadd the file)\n")
1104 "(use 'hg revert %s' to unadd the file)\n")
1103 % (f, f))
1105 % (f, f))
1104 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1106 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1105 self.ui.warn(_("%s not added: only files and symlinks "
1107 self.ui.warn(_("%s not added: only files and symlinks "
1106 "supported currently\n") % f)
1108 "supported currently\n") % f)
1107 rejected.append(p)
1109 rejected.append(p)
1108 elif self.dirstate[f] in 'amn':
1110 elif self.dirstate[f] in 'amn':
1109 self.ui.warn(_("%s already tracked!\n") % f)
1111 self.ui.warn(_("%s already tracked!\n") % f)
1110 elif self.dirstate[f] == 'r':
1112 elif self.dirstate[f] == 'r':
1111 self.dirstate.normallookup(f)
1113 self.dirstate.normallookup(f)
1112 else:
1114 else:
1113 self.dirstate.add(f)
1115 self.dirstate.add(f)
1114 return rejected
1116 return rejected
1115 finally:
1117 finally:
1116 del wlock
1118 del wlock
1117
1119
1118 def forget(self, list):
1120 def forget(self, list):
1119 wlock = self.wlock()
1121 wlock = self.wlock()
1120 try:
1122 try:
1121 for f in list:
1123 for f in list:
1122 if self.dirstate[f] != 'a':
1124 if self.dirstate[f] != 'a':
1123 self.ui.warn(_("%s not added!\n") % f)
1125 self.ui.warn(_("%s not added!\n") % f)
1124 else:
1126 else:
1125 self.dirstate.forget(f)
1127 self.dirstate.forget(f)
1126 finally:
1128 finally:
1127 del wlock
1129 del wlock
1128
1130
1129 def remove(self, list, unlink=False):
1131 def remove(self, list, unlink=False):
1130 wlock = None
1132 wlock = None
1131 try:
1133 try:
1132 if unlink:
1134 if unlink:
1133 for f in list:
1135 for f in list:
1134 try:
1136 try:
1135 util.unlink(self.wjoin(f))
1137 util.unlink(self.wjoin(f))
1136 except OSError, inst:
1138 except OSError, inst:
1137 if inst.errno != errno.ENOENT:
1139 if inst.errno != errno.ENOENT:
1138 raise
1140 raise
1139 wlock = self.wlock()
1141 wlock = self.wlock()
1140 for f in list:
1142 for f in list:
1141 if unlink and os.path.exists(self.wjoin(f)):
1143 if unlink and os.path.exists(self.wjoin(f)):
1142 self.ui.warn(_("%s still exists!\n") % f)
1144 self.ui.warn(_("%s still exists!\n") % f)
1143 elif self.dirstate[f] == 'a':
1145 elif self.dirstate[f] == 'a':
1144 self.dirstate.forget(f)
1146 self.dirstate.forget(f)
1145 elif f not in self.dirstate:
1147 elif f not in self.dirstate:
1146 self.ui.warn(_("%s not tracked!\n") % f)
1148 self.ui.warn(_("%s not tracked!\n") % f)
1147 else:
1149 else:
1148 self.dirstate.remove(f)
1150 self.dirstate.remove(f)
1149 finally:
1151 finally:
1150 del wlock
1152 del wlock
1151
1153
1152 def undelete(self, list):
1154 def undelete(self, list):
1153 wlock = None
1155 wlock = None
1154 try:
1156 try:
1155 manifests = [self.manifest.read(self.changelog.read(p)[0])
1157 manifests = [self.manifest.read(self.changelog.read(p)[0])
1156 for p in self.dirstate.parents() if p != nullid]
1158 for p in self.dirstate.parents() if p != nullid]
1157 wlock = self.wlock()
1159 wlock = self.wlock()
1158 for f in list:
1160 for f in list:
1159 if self.dirstate[f] != 'r':
1161 if self.dirstate[f] != 'r':
1160 self.ui.warn("%s not removed!\n" % f)
1162 self.ui.warn("%s not removed!\n" % f)
1161 else:
1163 else:
1162 m = f in manifests[0] and manifests[0] or manifests[1]
1164 m = f in manifests[0] and manifests[0] or manifests[1]
1163 t = self.file(f).read(m[f])
1165 t = self.file(f).read(m[f])
1164 self.wwrite(f, t, m.flags(f))
1166 self.wwrite(f, t, m.flags(f))
1165 self.dirstate.normal(f)
1167 self.dirstate.normal(f)
1166 finally:
1168 finally:
1167 del wlock
1169 del wlock
1168
1170
1169 def copy(self, source, dest):
1171 def copy(self, source, dest):
1170 wlock = None
1172 wlock = None
1171 try:
1173 try:
1172 p = self.wjoin(dest)
1174 p = self.wjoin(dest)
1173 if not (os.path.exists(p) or os.path.islink(p)):
1175 if not (os.path.exists(p) or os.path.islink(p)):
1174 self.ui.warn(_("%s does not exist!\n") % dest)
1176 self.ui.warn(_("%s does not exist!\n") % dest)
1175 elif not (os.path.isfile(p) or os.path.islink(p)):
1177 elif not (os.path.isfile(p) or os.path.islink(p)):
1176 self.ui.warn(_("copy failed: %s is not a file or a "
1178 self.ui.warn(_("copy failed: %s is not a file or a "
1177 "symbolic link\n") % dest)
1179 "symbolic link\n") % dest)
1178 else:
1180 else:
1179 wlock = self.wlock()
1181 wlock = self.wlock()
1180 if dest not in self.dirstate:
1182 if dest not in self.dirstate:
1181 self.dirstate.add(dest)
1183 self.dirstate.add(dest)
1182 self.dirstate.copy(source, dest)
1184 self.dirstate.copy(source, dest)
1183 finally:
1185 finally:
1184 del wlock
1186 del wlock
1185
1187
1186 def heads(self, start=None):
1188 def heads(self, start=None):
1187 heads = self.changelog.heads(start)
1189 heads = self.changelog.heads(start)
1188 # sort the output in rev descending order
1190 # sort the output in rev descending order
1189 heads = [(-self.changelog.rev(h), h) for h in heads]
1191 heads = [(-self.changelog.rev(h), h) for h in heads]
1190 heads.sort()
1192 heads.sort()
1191 return [n for (r, n) in heads]
1193 return [n for (r, n) in heads]
1192
1194
1193 def branchheads(self, branch, start=None):
1195 def branchheads(self, branch, start=None):
1194 branches = self.branchtags()
1196 branches = self.branchtags()
1195 if branch not in branches:
1197 if branch not in branches:
1196 return []
1198 return []
1197 # The basic algorithm is this:
1199 # The basic algorithm is this:
1198 #
1200 #
1199 # Start from the branch tip since there are no later revisions that can
1201 # Start from the branch tip since there are no later revisions that can
1200 # possibly be in this branch, and the tip is a guaranteed head.
1202 # possibly be in this branch, and the tip is a guaranteed head.
1201 #
1203 #
1202 # Remember the tip's parents as the first ancestors, since these by
1204 # Remember the tip's parents as the first ancestors, since these by
1203 # definition are not heads.
1205 # definition are not heads.
1204 #
1206 #
1205 # Step backwards from the brach tip through all the revisions. We are
1207 # Step backwards from the brach tip through all the revisions. We are
1206 # guaranteed by the rules of Mercurial that we will now be visiting the
1208 # guaranteed by the rules of Mercurial that we will now be visiting the
1207 # nodes in reverse topological order (children before parents).
1209 # nodes in reverse topological order (children before parents).
1208 #
1210 #
1209 # If a revision is one of the ancestors of a head then we can toss it
1211 # If a revision is one of the ancestors of a head then we can toss it
1210 # out of the ancestors set (we've already found it and won't be
1212 # out of the ancestors set (we've already found it and won't be
1211 # visiting it again) and put its parents in the ancestors set.
1213 # visiting it again) and put its parents in the ancestors set.
1212 #
1214 #
1213 # Otherwise, if a revision is in the branch it's another head, since it
1215 # Otherwise, if a revision is in the branch it's another head, since it
1214 # wasn't in the ancestor list of an existing head. So add it to the
1216 # wasn't in the ancestor list of an existing head. So add it to the
1215 # head list, and add its parents to the ancestor list.
1217 # head list, and add its parents to the ancestor list.
1216 #
1218 #
1217 # If it is not in the branch ignore it.
1219 # If it is not in the branch ignore it.
1218 #
1220 #
1219 # Once we have a list of heads, use nodesbetween to filter out all the
1221 # Once we have a list of heads, use nodesbetween to filter out all the
1220 # heads that cannot be reached from startrev. There may be a more
1222 # heads that cannot be reached from startrev. There may be a more
1221 # efficient way to do this as part of the previous algorithm.
1223 # efficient way to do this as part of the previous algorithm.
1222
1224
1223 set = util.set
1225 set = util.set
1224 heads = [self.changelog.rev(branches[branch])]
1226 heads = [self.changelog.rev(branches[branch])]
1225 # Don't care if ancestors contains nullrev or not.
1227 # Don't care if ancestors contains nullrev or not.
1226 ancestors = set(self.changelog.parentrevs(heads[0]))
1228 ancestors = set(self.changelog.parentrevs(heads[0]))
1227 for rev in xrange(heads[0] - 1, nullrev, -1):
1229 for rev in xrange(heads[0] - 1, nullrev, -1):
1228 if rev in ancestors:
1230 if rev in ancestors:
1229 ancestors.update(self.changelog.parentrevs(rev))
1231 ancestors.update(self.changelog.parentrevs(rev))
1230 ancestors.remove(rev)
1232 ancestors.remove(rev)
1231 elif self.changectx(rev).branch() == branch:
1233 elif self.changectx(rev).branch() == branch:
1232 heads.append(rev)
1234 heads.append(rev)
1233 ancestors.update(self.changelog.parentrevs(rev))
1235 ancestors.update(self.changelog.parentrevs(rev))
1234 heads = [self.changelog.node(rev) for rev in heads]
1236 heads = [self.changelog.node(rev) for rev in heads]
1235 if start is not None:
1237 if start is not None:
1236 heads = self.changelog.nodesbetween([start], heads)[2]
1238 heads = self.changelog.nodesbetween([start], heads)[2]
1237 return heads
1239 return heads
1238
1240
1239 def branches(self, nodes):
1241 def branches(self, nodes):
1240 if not nodes:
1242 if not nodes:
1241 nodes = [self.changelog.tip()]
1243 nodes = [self.changelog.tip()]
1242 b = []
1244 b = []
1243 for n in nodes:
1245 for n in nodes:
1244 t = n
1246 t = n
1245 while 1:
1247 while 1:
1246 p = self.changelog.parents(n)
1248 p = self.changelog.parents(n)
1247 if p[1] != nullid or p[0] == nullid:
1249 if p[1] != nullid or p[0] == nullid:
1248 b.append((t, n, p[0], p[1]))
1250 b.append((t, n, p[0], p[1]))
1249 break
1251 break
1250 n = p[0]
1252 n = p[0]
1251 return b
1253 return b
1252
1254
1253 def between(self, pairs):
1255 def between(self, pairs):
1254 r = []
1256 r = []
1255
1257
1256 for top, bottom in pairs:
1258 for top, bottom in pairs:
1257 n, l, i = top, [], 0
1259 n, l, i = top, [], 0
1258 f = 1
1260 f = 1
1259
1261
1260 while n != bottom:
1262 while n != bottom:
1261 p = self.changelog.parents(n)[0]
1263 p = self.changelog.parents(n)[0]
1262 if i == f:
1264 if i == f:
1263 l.append(n)
1265 l.append(n)
1264 f = f * 2
1266 f = f * 2
1265 n = p
1267 n = p
1266 i += 1
1268 i += 1
1267
1269
1268 r.append(l)
1270 r.append(l)
1269
1271
1270 return r
1272 return r
1271
1273
1272 def findincoming(self, remote, base=None, heads=None, force=False):
1274 def findincoming(self, remote, base=None, heads=None, force=False):
1273 """Return list of roots of the subsets of missing nodes from remote
1275 """Return list of roots of the subsets of missing nodes from remote
1274
1276
1275 If base dict is specified, assume that these nodes and their parents
1277 If base dict is specified, assume that these nodes and their parents
1276 exist on the remote side and that no child of a node of base exists
1278 exist on the remote side and that no child of a node of base exists
1277 in both remote and self.
1279 in both remote and self.
1278 Furthermore base will be updated to include the nodes that exists
1280 Furthermore base will be updated to include the nodes that exists
1279 in self and remote but no children exists in self and remote.
1281 in self and remote but no children exists in self and remote.
1280 If a list of heads is specified, return only nodes which are heads
1282 If a list of heads is specified, return only nodes which are heads
1281 or ancestors of these heads.
1283 or ancestors of these heads.
1282
1284
1283 All the ancestors of base are in self and in remote.
1285 All the ancestors of base are in self and in remote.
1284 All the descendants of the list returned are missing in self.
1286 All the descendants of the list returned are missing in self.
1285 (and so we know that the rest of the nodes are missing in remote, see
1287 (and so we know that the rest of the nodes are missing in remote, see
1286 outgoing)
1288 outgoing)
1287 """
1289 """
1288 m = self.changelog.nodemap
1290 m = self.changelog.nodemap
1289 search = []
1291 search = []
1290 fetch = {}
1292 fetch = {}
1291 seen = {}
1293 seen = {}
1292 seenbranch = {}
1294 seenbranch = {}
1293 if base == None:
1295 if base == None:
1294 base = {}
1296 base = {}
1295
1297
1296 if not heads:
1298 if not heads:
1297 heads = remote.heads()
1299 heads = remote.heads()
1298
1300
1299 if self.changelog.tip() == nullid:
1301 if self.changelog.tip() == nullid:
1300 base[nullid] = 1
1302 base[nullid] = 1
1301 if heads != [nullid]:
1303 if heads != [nullid]:
1302 return [nullid]
1304 return [nullid]
1303 return []
1305 return []
1304
1306
1305 # assume we're closer to the tip than the root
1307 # assume we're closer to the tip than the root
1306 # and start by examining the heads
1308 # and start by examining the heads
1307 self.ui.status(_("searching for changes\n"))
1309 self.ui.status(_("searching for changes\n"))
1308
1310
1309 unknown = []
1311 unknown = []
1310 for h in heads:
1312 for h in heads:
1311 if h not in m:
1313 if h not in m:
1312 unknown.append(h)
1314 unknown.append(h)
1313 else:
1315 else:
1314 base[h] = 1
1316 base[h] = 1
1315
1317
1316 if not unknown:
1318 if not unknown:
1317 return []
1319 return []
1318
1320
1319 req = dict.fromkeys(unknown)
1321 req = dict.fromkeys(unknown)
1320 reqcnt = 0
1322 reqcnt = 0
1321
1323
1322 # search through remote branches
1324 # search through remote branches
1323 # a 'branch' here is a linear segment of history, with four parts:
1325 # a 'branch' here is a linear segment of history, with four parts:
1324 # head, root, first parent, second parent
1326 # head, root, first parent, second parent
1325 # (a branch always has two parents (or none) by definition)
1327 # (a branch always has two parents (or none) by definition)
1326 unknown = remote.branches(unknown)
1328 unknown = remote.branches(unknown)
1327 while unknown:
1329 while unknown:
1328 r = []
1330 r = []
1329 while unknown:
1331 while unknown:
1330 n = unknown.pop(0)
1332 n = unknown.pop(0)
1331 if n[0] in seen:
1333 if n[0] in seen:
1332 continue
1334 continue
1333
1335
1334 self.ui.debug(_("examining %s:%s\n")
1336 self.ui.debug(_("examining %s:%s\n")
1335 % (short(n[0]), short(n[1])))
1337 % (short(n[0]), short(n[1])))
1336 if n[0] == nullid: # found the end of the branch
1338 if n[0] == nullid: # found the end of the branch
1337 pass
1339 pass
1338 elif n in seenbranch:
1340 elif n in seenbranch:
1339 self.ui.debug(_("branch already found\n"))
1341 self.ui.debug(_("branch already found\n"))
1340 continue
1342 continue
1341 elif n[1] and n[1] in m: # do we know the base?
1343 elif n[1] and n[1] in m: # do we know the base?
1342 self.ui.debug(_("found incomplete branch %s:%s\n")
1344 self.ui.debug(_("found incomplete branch %s:%s\n")
1343 % (short(n[0]), short(n[1])))
1345 % (short(n[0]), short(n[1])))
1344 search.append(n) # schedule branch range for scanning
1346 search.append(n) # schedule branch range for scanning
1345 seenbranch[n] = 1
1347 seenbranch[n] = 1
1346 else:
1348 else:
1347 if n[1] not in seen and n[1] not in fetch:
1349 if n[1] not in seen and n[1] not in fetch:
1348 if n[2] in m and n[3] in m:
1350 if n[2] in m and n[3] in m:
1349 self.ui.debug(_("found new changeset %s\n") %
1351 self.ui.debug(_("found new changeset %s\n") %
1350 short(n[1]))
1352 short(n[1]))
1351 fetch[n[1]] = 1 # earliest unknown
1353 fetch[n[1]] = 1 # earliest unknown
1352 for p in n[2:4]:
1354 for p in n[2:4]:
1353 if p in m:
1355 if p in m:
1354 base[p] = 1 # latest known
1356 base[p] = 1 # latest known
1355
1357
1356 for p in n[2:4]:
1358 for p in n[2:4]:
1357 if p not in req and p not in m:
1359 if p not in req and p not in m:
1358 r.append(p)
1360 r.append(p)
1359 req[p] = 1
1361 req[p] = 1
1360 seen[n[0]] = 1
1362 seen[n[0]] = 1
1361
1363
1362 if r:
1364 if r:
1363 reqcnt += 1
1365 reqcnt += 1
1364 self.ui.debug(_("request %d: %s\n") %
1366 self.ui.debug(_("request %d: %s\n") %
1365 (reqcnt, " ".join(map(short, r))))
1367 (reqcnt, " ".join(map(short, r))))
1366 for p in xrange(0, len(r), 10):
1368 for p in xrange(0, len(r), 10):
1367 for b in remote.branches(r[p:p+10]):
1369 for b in remote.branches(r[p:p+10]):
1368 self.ui.debug(_("received %s:%s\n") %
1370 self.ui.debug(_("received %s:%s\n") %
1369 (short(b[0]), short(b[1])))
1371 (short(b[0]), short(b[1])))
1370 unknown.append(b)
1372 unknown.append(b)
1371
1373
1372 # do binary search on the branches we found
1374 # do binary search on the branches we found
1373 while search:
1375 while search:
1374 n = search.pop(0)
1376 n = search.pop(0)
1375 reqcnt += 1
1377 reqcnt += 1
1376 l = remote.between([(n[0], n[1])])[0]
1378 l = remote.between([(n[0], n[1])])[0]
1377 l.append(n[1])
1379 l.append(n[1])
1378 p = n[0]
1380 p = n[0]
1379 f = 1
1381 f = 1
1380 for i in l:
1382 for i in l:
1381 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
1383 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
1382 if i in m:
1384 if i in m:
1383 if f <= 2:
1385 if f <= 2:
1384 self.ui.debug(_("found new branch changeset %s\n") %
1386 self.ui.debug(_("found new branch changeset %s\n") %
1385 short(p))
1387 short(p))
1386 fetch[p] = 1
1388 fetch[p] = 1
1387 base[i] = 1
1389 base[i] = 1
1388 else:
1390 else:
1389 self.ui.debug(_("narrowed branch search to %s:%s\n")
1391 self.ui.debug(_("narrowed branch search to %s:%s\n")
1390 % (short(p), short(i)))
1392 % (short(p), short(i)))
1391 search.append((p, i))
1393 search.append((p, i))
1392 break
1394 break
1393 p, f = i, f * 2
1395 p, f = i, f * 2
1394
1396
1395 # sanity check our fetch list
1397 # sanity check our fetch list
1396 for f in fetch.keys():
1398 for f in fetch.keys():
1397 if f in m:
1399 if f in m:
1398 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
1400 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
1399
1401
1400 if base.keys() == [nullid]:
1402 if base.keys() == [nullid]:
1401 if force:
1403 if force:
1402 self.ui.warn(_("warning: repository is unrelated\n"))
1404 self.ui.warn(_("warning: repository is unrelated\n"))
1403 else:
1405 else:
1404 raise util.Abort(_("repository is unrelated"))
1406 raise util.Abort(_("repository is unrelated"))
1405
1407
1406 self.ui.debug(_("found new changesets starting at ") +
1408 self.ui.debug(_("found new changesets starting at ") +
1407 " ".join([short(f) for f in fetch]) + "\n")
1409 " ".join([short(f) for f in fetch]) + "\n")
1408
1410
1409 self.ui.debug(_("%d total queries\n") % reqcnt)
1411 self.ui.debug(_("%d total queries\n") % reqcnt)
1410
1412
1411 return fetch.keys()
1413 return fetch.keys()
1412
1414
1413 def findoutgoing(self, remote, base=None, heads=None, force=False):
1415 def findoutgoing(self, remote, base=None, heads=None, force=False):
1414 """Return list of nodes that are roots of subsets not in remote
1416 """Return list of nodes that are roots of subsets not in remote
1415
1417
1416 If base dict is specified, assume that these nodes and their parents
1418 If base dict is specified, assume that these nodes and their parents
1417 exist on the remote side.
1419 exist on the remote side.
1418 If a list of heads is specified, return only nodes which are heads
1420 If a list of heads is specified, return only nodes which are heads
1419 or ancestors of these heads, and return a second element which
1421 or ancestors of these heads, and return a second element which
1420 contains all remote heads which get new children.
1422 contains all remote heads which get new children.
1421 """
1423 """
1422 if base == None:
1424 if base == None:
1423 base = {}
1425 base = {}
1424 self.findincoming(remote, base, heads, force=force)
1426 self.findincoming(remote, base, heads, force=force)
1425
1427
1426 self.ui.debug(_("common changesets up to ")
1428 self.ui.debug(_("common changesets up to ")
1427 + " ".join(map(short, base.keys())) + "\n")
1429 + " ".join(map(short, base.keys())) + "\n")
1428
1430
1429 remain = dict.fromkeys(self.changelog.nodemap)
1431 remain = dict.fromkeys(self.changelog.nodemap)
1430
1432
1431 # prune everything remote has from the tree
1433 # prune everything remote has from the tree
1432 del remain[nullid]
1434 del remain[nullid]
1433 remove = base.keys()
1435 remove = base.keys()
1434 while remove:
1436 while remove:
1435 n = remove.pop(0)
1437 n = remove.pop(0)
1436 if n in remain:
1438 if n in remain:
1437 del remain[n]
1439 del remain[n]
1438 for p in self.changelog.parents(n):
1440 for p in self.changelog.parents(n):
1439 remove.append(p)
1441 remove.append(p)
1440
1442
1441 # find every node whose parents have been pruned
1443 # find every node whose parents have been pruned
1442 subset = []
1444 subset = []
1443 # find every remote head that will get new children
1445 # find every remote head that will get new children
1444 updated_heads = {}
1446 updated_heads = {}
1445 for n in remain:
1447 for n in remain:
1446 p1, p2 = self.changelog.parents(n)
1448 p1, p2 = self.changelog.parents(n)
1447 if p1 not in remain and p2 not in remain:
1449 if p1 not in remain and p2 not in remain:
1448 subset.append(n)
1450 subset.append(n)
1449 if heads:
1451 if heads:
1450 if p1 in heads:
1452 if p1 in heads:
1451 updated_heads[p1] = True
1453 updated_heads[p1] = True
1452 if p2 in heads:
1454 if p2 in heads:
1453 updated_heads[p2] = True
1455 updated_heads[p2] = True
1454
1456
1455 # this is the set of all roots we have to push
1457 # this is the set of all roots we have to push
1456 if heads:
1458 if heads:
1457 return subset, updated_heads.keys()
1459 return subset, updated_heads.keys()
1458 else:
1460 else:
1459 return subset
1461 return subset
1460
1462
1461 def pull(self, remote, heads=None, force=False):
1463 def pull(self, remote, heads=None, force=False):
1462 lock = self.lock()
1464 lock = self.lock()
1463 try:
1465 try:
1464 fetch = self.findincoming(remote, heads=heads, force=force)
1466 fetch = self.findincoming(remote, heads=heads, force=force)
1465 if fetch == [nullid]:
1467 if fetch == [nullid]:
1466 self.ui.status(_("requesting all changes\n"))
1468 self.ui.status(_("requesting all changes\n"))
1467
1469
1468 if not fetch:
1470 if not fetch:
1469 self.ui.status(_("no changes found\n"))
1471 self.ui.status(_("no changes found\n"))
1470 return 0
1472 return 0
1471
1473
1472 if heads is None:
1474 if heads is None:
1473 cg = remote.changegroup(fetch, 'pull')
1475 cg = remote.changegroup(fetch, 'pull')
1474 else:
1476 else:
1475 if 'changegroupsubset' not in remote.capabilities:
1477 if 'changegroupsubset' not in remote.capabilities:
1476 raise util.Abort(_("Partial pull cannot be done because other repository doesn't support changegroupsubset."))
1478 raise util.Abort(_("Partial pull cannot be done because other repository doesn't support changegroupsubset."))
1477 cg = remote.changegroupsubset(fetch, heads, 'pull')
1479 cg = remote.changegroupsubset(fetch, heads, 'pull')
1478 return self.addchangegroup(cg, 'pull', remote.url())
1480 return self.addchangegroup(cg, 'pull', remote.url())
1479 finally:
1481 finally:
1480 del lock
1482 del lock
1481
1483
1482 def push(self, remote, force=False, revs=None):
1484 def push(self, remote, force=False, revs=None):
1483 # there are two ways to push to remote repo:
1485 # there are two ways to push to remote repo:
1484 #
1486 #
1485 # addchangegroup assumes local user can lock remote
1487 # addchangegroup assumes local user can lock remote
1486 # repo (local filesystem, old ssh servers).
1488 # repo (local filesystem, old ssh servers).
1487 #
1489 #
1488 # unbundle assumes local user cannot lock remote repo (new ssh
1490 # unbundle assumes local user cannot lock remote repo (new ssh
1489 # servers, http servers).
1491 # servers, http servers).
1490
1492
1491 if remote.capable('unbundle'):
1493 if remote.capable('unbundle'):
1492 return self.push_unbundle(remote, force, revs)
1494 return self.push_unbundle(remote, force, revs)
1493 return self.push_addchangegroup(remote, force, revs)
1495 return self.push_addchangegroup(remote, force, revs)
1494
1496
1495 def prepush(self, remote, force, revs):
1497 def prepush(self, remote, force, revs):
1496 base = {}
1498 base = {}
1497 remote_heads = remote.heads()
1499 remote_heads = remote.heads()
1498 inc = self.findincoming(remote, base, remote_heads, force=force)
1500 inc = self.findincoming(remote, base, remote_heads, force=force)
1499
1501
1500 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1502 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1501 if revs is not None:
1503 if revs is not None:
1502 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1504 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1503 else:
1505 else:
1504 bases, heads = update, self.changelog.heads()
1506 bases, heads = update, self.changelog.heads()
1505
1507
1506 if not bases:
1508 if not bases:
1507 self.ui.status(_("no changes found\n"))
1509 self.ui.status(_("no changes found\n"))
1508 return None, 1
1510 return None, 1
1509 elif not force:
1511 elif not force:
1510 # check if we're creating new remote heads
1512 # check if we're creating new remote heads
1511 # to be a remote head after push, node must be either
1513 # to be a remote head after push, node must be either
1512 # - unknown locally
1514 # - unknown locally
1513 # - a local outgoing head descended from update
1515 # - a local outgoing head descended from update
1514 # - a remote head that's known locally and not
1516 # - a remote head that's known locally and not
1515 # ancestral to an outgoing head
1517 # ancestral to an outgoing head
1516
1518
1517 warn = 0
1519 warn = 0
1518
1520
1519 if remote_heads == [nullid]:
1521 if remote_heads == [nullid]:
1520 warn = 0
1522 warn = 0
1521 elif not revs and len(heads) > len(remote_heads):
1523 elif not revs and len(heads) > len(remote_heads):
1522 warn = 1
1524 warn = 1
1523 else:
1525 else:
1524 newheads = list(heads)
1526 newheads = list(heads)
1525 for r in remote_heads:
1527 for r in remote_heads:
1526 if r in self.changelog.nodemap:
1528 if r in self.changelog.nodemap:
1527 desc = self.changelog.heads(r, heads)
1529 desc = self.changelog.heads(r, heads)
1528 l = [h for h in heads if h in desc]
1530 l = [h for h in heads if h in desc]
1529 if not l:
1531 if not l:
1530 newheads.append(r)
1532 newheads.append(r)
1531 else:
1533 else:
1532 newheads.append(r)
1534 newheads.append(r)
1533 if len(newheads) > len(remote_heads):
1535 if len(newheads) > len(remote_heads):
1534 warn = 1
1536 warn = 1
1535
1537
1536 if warn:
1538 if warn:
1537 self.ui.warn(_("abort: push creates new remote heads!\n"))
1539 self.ui.warn(_("abort: push creates new remote heads!\n"))
1538 self.ui.status(_("(did you forget to merge?"
1540 self.ui.status(_("(did you forget to merge?"
1539 " use push -f to force)\n"))
1541 " use push -f to force)\n"))
1540 return None, 0
1542 return None, 0
1541 elif inc:
1543 elif inc:
1542 self.ui.warn(_("note: unsynced remote changes!\n"))
1544 self.ui.warn(_("note: unsynced remote changes!\n"))
1543
1545
1544
1546
1545 if revs is None:
1547 if revs is None:
1546 cg = self.changegroup(update, 'push')
1548 cg = self.changegroup(update, 'push')
1547 else:
1549 else:
1548 cg = self.changegroupsubset(update, revs, 'push')
1550 cg = self.changegroupsubset(update, revs, 'push')
1549 return cg, remote_heads
1551 return cg, remote_heads
1550
1552
1551 def push_addchangegroup(self, remote, force, revs):
1553 def push_addchangegroup(self, remote, force, revs):
1552 lock = remote.lock()
1554 lock = remote.lock()
1553 try:
1555 try:
1554 ret = self.prepush(remote, force, revs)
1556 ret = self.prepush(remote, force, revs)
1555 if ret[0] is not None:
1557 if ret[0] is not None:
1556 cg, remote_heads = ret
1558 cg, remote_heads = ret
1557 return remote.addchangegroup(cg, 'push', self.url())
1559 return remote.addchangegroup(cg, 'push', self.url())
1558 return ret[1]
1560 return ret[1]
1559 finally:
1561 finally:
1560 del lock
1562 del lock
1561
1563
1562 def push_unbundle(self, remote, force, revs):
1564 def push_unbundle(self, remote, force, revs):
1563 # local repo finds heads on server, finds out what revs it
1565 # local repo finds heads on server, finds out what revs it
1564 # must push. once revs transferred, if server finds it has
1566 # must push. once revs transferred, if server finds it has
1565 # different heads (someone else won commit/push race), server
1567 # different heads (someone else won commit/push race), server
1566 # aborts.
1568 # aborts.
1567
1569
1568 ret = self.prepush(remote, force, revs)
1570 ret = self.prepush(remote, force, revs)
1569 if ret[0] is not None:
1571 if ret[0] is not None:
1570 cg, remote_heads = ret
1572 cg, remote_heads = ret
1571 if force: remote_heads = ['force']
1573 if force: remote_heads = ['force']
1572 return remote.unbundle(cg, remote_heads, 'push')
1574 return remote.unbundle(cg, remote_heads, 'push')
1573 return ret[1]
1575 return ret[1]
1574
1576
1575 def changegroupinfo(self, nodes, source):
1577 def changegroupinfo(self, nodes, source):
1576 if self.ui.verbose or source == 'bundle':
1578 if self.ui.verbose or source == 'bundle':
1577 self.ui.status(_("%d changesets found\n") % len(nodes))
1579 self.ui.status(_("%d changesets found\n") % len(nodes))
1578 if self.ui.debugflag:
1580 if self.ui.debugflag:
1579 self.ui.debug(_("List of changesets:\n"))
1581 self.ui.debug(_("List of changesets:\n"))
1580 for node in nodes:
1582 for node in nodes:
1581 self.ui.debug("%s\n" % hex(node))
1583 self.ui.debug("%s\n" % hex(node))
1582
1584
1583 def changegroupsubset(self, bases, heads, source, extranodes=None):
1585 def changegroupsubset(self, bases, heads, source, extranodes=None):
1584 """This function generates a changegroup consisting of all the nodes
1586 """This function generates a changegroup consisting of all the nodes
1585 that are descendents of any of the bases, and ancestors of any of
1587 that are descendents of any of the bases, and ancestors of any of
1586 the heads.
1588 the heads.
1587
1589
1588 It is fairly complex as determining which filenodes and which
1590 It is fairly complex as determining which filenodes and which
1589 manifest nodes need to be included for the changeset to be complete
1591 manifest nodes need to be included for the changeset to be complete
1590 is non-trivial.
1592 is non-trivial.
1591
1593
1592 Another wrinkle is doing the reverse, figuring out which changeset in
1594 Another wrinkle is doing the reverse, figuring out which changeset in
1593 the changegroup a particular filenode or manifestnode belongs to.
1595 the changegroup a particular filenode or manifestnode belongs to.
1594
1596
1595 The caller can specify some nodes that must be included in the
1597 The caller can specify some nodes that must be included in the
1596 changegroup using the extranodes argument. It should be a dict
1598 changegroup using the extranodes argument. It should be a dict
1597 where the keys are the filenames (or 1 for the manifest), and the
1599 where the keys are the filenames (or 1 for the manifest), and the
1598 values are lists of (node, linknode) tuples, where node is a wanted
1600 values are lists of (node, linknode) tuples, where node is a wanted
1599 node and linknode is the changelog node that should be transmitted as
1601 node and linknode is the changelog node that should be transmitted as
1600 the linkrev.
1602 the linkrev.
1601 """
1603 """
1602
1604
1603 self.hook('preoutgoing', throw=True, source=source)
1605 self.hook('preoutgoing', throw=True, source=source)
1604
1606
1605 # Set up some initial variables
1607 # Set up some initial variables
1606 # Make it easy to refer to self.changelog
1608 # Make it easy to refer to self.changelog
1607 cl = self.changelog
1609 cl = self.changelog
1608 # msng is short for missing - compute the list of changesets in this
1610 # msng is short for missing - compute the list of changesets in this
1609 # changegroup.
1611 # changegroup.
1610 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1612 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1611 self.changegroupinfo(msng_cl_lst, source)
1613 self.changegroupinfo(msng_cl_lst, source)
1612 # Some bases may turn out to be superfluous, and some heads may be
1614 # Some bases may turn out to be superfluous, and some heads may be
1613 # too. nodesbetween will return the minimal set of bases and heads
1615 # too. nodesbetween will return the minimal set of bases and heads
1614 # necessary to re-create the changegroup.
1616 # necessary to re-create the changegroup.
1615
1617
1616 # Known heads are the list of heads that it is assumed the recipient
1618 # Known heads are the list of heads that it is assumed the recipient
1617 # of this changegroup will know about.
1619 # of this changegroup will know about.
1618 knownheads = {}
1620 knownheads = {}
1619 # We assume that all parents of bases are known heads.
1621 # We assume that all parents of bases are known heads.
1620 for n in bases:
1622 for n in bases:
1621 for p in cl.parents(n):
1623 for p in cl.parents(n):
1622 if p != nullid:
1624 if p != nullid:
1623 knownheads[p] = 1
1625 knownheads[p] = 1
1624 knownheads = knownheads.keys()
1626 knownheads = knownheads.keys()
1625 if knownheads:
1627 if knownheads:
1626 # Now that we know what heads are known, we can compute which
1628 # Now that we know what heads are known, we can compute which
1627 # changesets are known. The recipient must know about all
1629 # changesets are known. The recipient must know about all
1628 # changesets required to reach the known heads from the null
1630 # changesets required to reach the known heads from the null
1629 # changeset.
1631 # changeset.
1630 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1632 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1631 junk = None
1633 junk = None
1632 # Transform the list into an ersatz set.
1634 # Transform the list into an ersatz set.
1633 has_cl_set = dict.fromkeys(has_cl_set)
1635 has_cl_set = dict.fromkeys(has_cl_set)
1634 else:
1636 else:
1635 # If there were no known heads, the recipient cannot be assumed to
1637 # If there were no known heads, the recipient cannot be assumed to
1636 # know about any changesets.
1638 # know about any changesets.
1637 has_cl_set = {}
1639 has_cl_set = {}
1638
1640
1639 # Make it easy to refer to self.manifest
1641 # Make it easy to refer to self.manifest
1640 mnfst = self.manifest
1642 mnfst = self.manifest
1641 # We don't know which manifests are missing yet
1643 # We don't know which manifests are missing yet
1642 msng_mnfst_set = {}
1644 msng_mnfst_set = {}
1643 # Nor do we know which filenodes are missing.
1645 # Nor do we know which filenodes are missing.
1644 msng_filenode_set = {}
1646 msng_filenode_set = {}
1645
1647
1646 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1648 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1647 junk = None
1649 junk = None
1648
1650
1649 # A changeset always belongs to itself, so the changenode lookup
1651 # A changeset always belongs to itself, so the changenode lookup
1650 # function for a changenode is identity.
1652 # function for a changenode is identity.
1651 def identity(x):
1653 def identity(x):
1652 return x
1654 return x
1653
1655
1654 # A function generating function. Sets up an environment for the
1656 # A function generating function. Sets up an environment for the
1655 # inner function.
1657 # inner function.
1656 def cmp_by_rev_func(revlog):
1658 def cmp_by_rev_func(revlog):
1657 # Compare two nodes by their revision number in the environment's
1659 # Compare two nodes by their revision number in the environment's
1658 # revision history. Since the revision number both represents the
1660 # revision history. Since the revision number both represents the
1659 # most efficient order to read the nodes in, and represents a
1661 # most efficient order to read the nodes in, and represents a
1660 # topological sorting of the nodes, this function is often useful.
1662 # topological sorting of the nodes, this function is often useful.
1661 def cmp_by_rev(a, b):
1663 def cmp_by_rev(a, b):
1662 return cmp(revlog.rev(a), revlog.rev(b))
1664 return cmp(revlog.rev(a), revlog.rev(b))
1663 return cmp_by_rev
1665 return cmp_by_rev
1664
1666
1665 # If we determine that a particular file or manifest node must be a
1667 # If we determine that a particular file or manifest node must be a
1666 # node that the recipient of the changegroup will already have, we can
1668 # node that the recipient of the changegroup will already have, we can
1667 # also assume the recipient will have all the parents. This function
1669 # also assume the recipient will have all the parents. This function
1668 # prunes them from the set of missing nodes.
1670 # prunes them from the set of missing nodes.
1669 def prune_parents(revlog, hasset, msngset):
1671 def prune_parents(revlog, hasset, msngset):
1670 haslst = hasset.keys()
1672 haslst = hasset.keys()
1671 haslst.sort(cmp_by_rev_func(revlog))
1673 haslst.sort(cmp_by_rev_func(revlog))
1672 for node in haslst:
1674 for node in haslst:
1673 parentlst = [p for p in revlog.parents(node) if p != nullid]
1675 parentlst = [p for p in revlog.parents(node) if p != nullid]
1674 while parentlst:
1676 while parentlst:
1675 n = parentlst.pop()
1677 n = parentlst.pop()
1676 if n not in hasset:
1678 if n not in hasset:
1677 hasset[n] = 1
1679 hasset[n] = 1
1678 p = [p for p in revlog.parents(n) if p != nullid]
1680 p = [p for p in revlog.parents(n) if p != nullid]
1679 parentlst.extend(p)
1681 parentlst.extend(p)
1680 for n in hasset:
1682 for n in hasset:
1681 msngset.pop(n, None)
1683 msngset.pop(n, None)
1682
1684
1683 # This is a function generating function used to set up an environment
1685 # This is a function generating function used to set up an environment
1684 # for the inner function to execute in.
1686 # for the inner function to execute in.
1685 def manifest_and_file_collector(changedfileset):
1687 def manifest_and_file_collector(changedfileset):
1686 # This is an information gathering function that gathers
1688 # This is an information gathering function that gathers
1687 # information from each changeset node that goes out as part of
1689 # information from each changeset node that goes out as part of
1688 # the changegroup. The information gathered is a list of which
1690 # the changegroup. The information gathered is a list of which
1689 # manifest nodes are potentially required (the recipient may
1691 # manifest nodes are potentially required (the recipient may
1690 # already have them) and total list of all files which were
1692 # already have them) and total list of all files which were
1691 # changed in any changeset in the changegroup.
1693 # changed in any changeset in the changegroup.
1692 #
1694 #
1693 # We also remember the first changenode we saw any manifest
1695 # We also remember the first changenode we saw any manifest
1694 # referenced by so we can later determine which changenode 'owns'
1696 # referenced by so we can later determine which changenode 'owns'
1695 # the manifest.
1697 # the manifest.
1696 def collect_manifests_and_files(clnode):
1698 def collect_manifests_and_files(clnode):
1697 c = cl.read(clnode)
1699 c = cl.read(clnode)
1698 for f in c[3]:
1700 for f in c[3]:
1699 # This is to make sure we only have one instance of each
1701 # This is to make sure we only have one instance of each
1700 # filename string for each filename.
1702 # filename string for each filename.
1701 changedfileset.setdefault(f, f)
1703 changedfileset.setdefault(f, f)
1702 msng_mnfst_set.setdefault(c[0], clnode)
1704 msng_mnfst_set.setdefault(c[0], clnode)
1703 return collect_manifests_and_files
1705 return collect_manifests_and_files
1704
1706
1705 # Figure out which manifest nodes (of the ones we think might be part
1707 # Figure out which manifest nodes (of the ones we think might be part
1706 # of the changegroup) the recipient must know about and remove them
1708 # of the changegroup) the recipient must know about and remove them
1707 # from the changegroup.
1709 # from the changegroup.
1708 def prune_manifests():
1710 def prune_manifests():
1709 has_mnfst_set = {}
1711 has_mnfst_set = {}
1710 for n in msng_mnfst_set:
1712 for n in msng_mnfst_set:
1711 # If a 'missing' manifest thinks it belongs to a changenode
1713 # If a 'missing' manifest thinks it belongs to a changenode
1712 # the recipient is assumed to have, obviously the recipient
1714 # the recipient is assumed to have, obviously the recipient
1713 # must have that manifest.
1715 # must have that manifest.
1714 linknode = cl.node(mnfst.linkrev(n))
1716 linknode = cl.node(mnfst.linkrev(n))
1715 if linknode in has_cl_set:
1717 if linknode in has_cl_set:
1716 has_mnfst_set[n] = 1
1718 has_mnfst_set[n] = 1
1717 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1719 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1718
1720
1719 # Use the information collected in collect_manifests_and_files to say
1721 # Use the information collected in collect_manifests_and_files to say
1720 # which changenode any manifestnode belongs to.
1722 # which changenode any manifestnode belongs to.
1721 def lookup_manifest_link(mnfstnode):
1723 def lookup_manifest_link(mnfstnode):
1722 return msng_mnfst_set[mnfstnode]
1724 return msng_mnfst_set[mnfstnode]
1723
1725
1724 # A function generating function that sets up the initial environment
1726 # A function generating function that sets up the initial environment
1725 # the inner function.
1727 # the inner function.
1726 def filenode_collector(changedfiles):
1728 def filenode_collector(changedfiles):
1727 next_rev = [0]
1729 next_rev = [0]
1728 # This gathers information from each manifestnode included in the
1730 # This gathers information from each manifestnode included in the
1729 # changegroup about which filenodes the manifest node references
1731 # changegroup about which filenodes the manifest node references
1730 # so we can include those in the changegroup too.
1732 # so we can include those in the changegroup too.
1731 #
1733 #
1732 # It also remembers which changenode each filenode belongs to. It
1734 # It also remembers which changenode each filenode belongs to. It
1733 # does this by assuming the a filenode belongs to the changenode
1735 # does this by assuming the a filenode belongs to the changenode
1734 # the first manifest that references it belongs to.
1736 # the first manifest that references it belongs to.
1735 def collect_msng_filenodes(mnfstnode):
1737 def collect_msng_filenodes(mnfstnode):
1736 r = mnfst.rev(mnfstnode)
1738 r = mnfst.rev(mnfstnode)
1737 if r == next_rev[0]:
1739 if r == next_rev[0]:
1738 # If the last rev we looked at was the one just previous,
1740 # If the last rev we looked at was the one just previous,
1739 # we only need to see a diff.
1741 # we only need to see a diff.
1740 deltamf = mnfst.readdelta(mnfstnode)
1742 deltamf = mnfst.readdelta(mnfstnode)
1741 # For each line in the delta
1743 # For each line in the delta
1742 for f, fnode in deltamf.items():
1744 for f, fnode in deltamf.items():
1743 f = changedfiles.get(f, None)
1745 f = changedfiles.get(f, None)
1744 # And if the file is in the list of files we care
1746 # And if the file is in the list of files we care
1745 # about.
1747 # about.
1746 if f is not None:
1748 if f is not None:
1747 # Get the changenode this manifest belongs to
1749 # Get the changenode this manifest belongs to
1748 clnode = msng_mnfst_set[mnfstnode]
1750 clnode = msng_mnfst_set[mnfstnode]
1749 # Create the set of filenodes for the file if
1751 # Create the set of filenodes for the file if
1750 # there isn't one already.
1752 # there isn't one already.
1751 ndset = msng_filenode_set.setdefault(f, {})
1753 ndset = msng_filenode_set.setdefault(f, {})
1752 # And set the filenode's changelog node to the
1754 # And set the filenode's changelog node to the
1753 # manifest's if it hasn't been set already.
1755 # manifest's if it hasn't been set already.
1754 ndset.setdefault(fnode, clnode)
1756 ndset.setdefault(fnode, clnode)
1755 else:
1757 else:
1756 # Otherwise we need a full manifest.
1758 # Otherwise we need a full manifest.
1757 m = mnfst.read(mnfstnode)
1759 m = mnfst.read(mnfstnode)
1758 # For every file in we care about.
1760 # For every file in we care about.
1759 for f in changedfiles:
1761 for f in changedfiles:
1760 fnode = m.get(f, None)
1762 fnode = m.get(f, None)
1761 # If it's in the manifest
1763 # If it's in the manifest
1762 if fnode is not None:
1764 if fnode is not None:
1763 # See comments above.
1765 # See comments above.
1764 clnode = msng_mnfst_set[mnfstnode]
1766 clnode = msng_mnfst_set[mnfstnode]
1765 ndset = msng_filenode_set.setdefault(f, {})
1767 ndset = msng_filenode_set.setdefault(f, {})
1766 ndset.setdefault(fnode, clnode)
1768 ndset.setdefault(fnode, clnode)
1767 # Remember the revision we hope to see next.
1769 # Remember the revision we hope to see next.
1768 next_rev[0] = r + 1
1770 next_rev[0] = r + 1
1769 return collect_msng_filenodes
1771 return collect_msng_filenodes
1770
1772
1771 # We have a list of filenodes we think we need for a file, lets remove
1773 # We have a list of filenodes we think we need for a file, lets remove
1772 # all those we now the recipient must have.
1774 # all those we now the recipient must have.
1773 def prune_filenodes(f, filerevlog):
1775 def prune_filenodes(f, filerevlog):
1774 msngset = msng_filenode_set[f]
1776 msngset = msng_filenode_set[f]
1775 hasset = {}
1777 hasset = {}
1776 # If a 'missing' filenode thinks it belongs to a changenode we
1778 # If a 'missing' filenode thinks it belongs to a changenode we
1777 # assume the recipient must have, then the recipient must have
1779 # assume the recipient must have, then the recipient must have
1778 # that filenode.
1780 # that filenode.
1779 for n in msngset:
1781 for n in msngset:
1780 clnode = cl.node(filerevlog.linkrev(n))
1782 clnode = cl.node(filerevlog.linkrev(n))
1781 if clnode in has_cl_set:
1783 if clnode in has_cl_set:
1782 hasset[n] = 1
1784 hasset[n] = 1
1783 prune_parents(filerevlog, hasset, msngset)
1785 prune_parents(filerevlog, hasset, msngset)
1784
1786
1785 # A function generator function that sets up the a context for the
1787 # A function generator function that sets up the a context for the
1786 # inner function.
1788 # inner function.
1787 def lookup_filenode_link_func(fname):
1789 def lookup_filenode_link_func(fname):
1788 msngset = msng_filenode_set[fname]
1790 msngset = msng_filenode_set[fname]
1789 # Lookup the changenode the filenode belongs to.
1791 # Lookup the changenode the filenode belongs to.
1790 def lookup_filenode_link(fnode):
1792 def lookup_filenode_link(fnode):
1791 return msngset[fnode]
1793 return msngset[fnode]
1792 return lookup_filenode_link
1794 return lookup_filenode_link
1793
1795
1794 # Add the nodes that were explicitly requested.
1796 # Add the nodes that were explicitly requested.
1795 def add_extra_nodes(name, nodes):
1797 def add_extra_nodes(name, nodes):
1796 if not extranodes or name not in extranodes:
1798 if not extranodes or name not in extranodes:
1797 return
1799 return
1798
1800
1799 for node, linknode in extranodes[name]:
1801 for node, linknode in extranodes[name]:
1800 if node not in nodes:
1802 if node not in nodes:
1801 nodes[node] = linknode
1803 nodes[node] = linknode
1802
1804
1803 # Now that we have all theses utility functions to help out and
1805 # Now that we have all theses utility functions to help out and
1804 # logically divide up the task, generate the group.
1806 # logically divide up the task, generate the group.
1805 def gengroup():
1807 def gengroup():
1806 # The set of changed files starts empty.
1808 # The set of changed files starts empty.
1807 changedfiles = {}
1809 changedfiles = {}
1808 # Create a changenode group generator that will call our functions
1810 # Create a changenode group generator that will call our functions
1809 # back to lookup the owning changenode and collect information.
1811 # back to lookup the owning changenode and collect information.
1810 group = cl.group(msng_cl_lst, identity,
1812 group = cl.group(msng_cl_lst, identity,
1811 manifest_and_file_collector(changedfiles))
1813 manifest_and_file_collector(changedfiles))
1812 for chnk in group:
1814 for chnk in group:
1813 yield chnk
1815 yield chnk
1814
1816
1815 # The list of manifests has been collected by the generator
1817 # The list of manifests has been collected by the generator
1816 # calling our functions back.
1818 # calling our functions back.
1817 prune_manifests()
1819 prune_manifests()
1818 add_extra_nodes(1, msng_mnfst_set)
1820 add_extra_nodes(1, msng_mnfst_set)
1819 msng_mnfst_lst = msng_mnfst_set.keys()
1821 msng_mnfst_lst = msng_mnfst_set.keys()
1820 # Sort the manifestnodes by revision number.
1822 # Sort the manifestnodes by revision number.
1821 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1823 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1822 # Create a generator for the manifestnodes that calls our lookup
1824 # Create a generator for the manifestnodes that calls our lookup
1823 # and data collection functions back.
1825 # and data collection functions back.
1824 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1826 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1825 filenode_collector(changedfiles))
1827 filenode_collector(changedfiles))
1826 for chnk in group:
1828 for chnk in group:
1827 yield chnk
1829 yield chnk
1828
1830
1829 # These are no longer needed, dereference and toss the memory for
1831 # These are no longer needed, dereference and toss the memory for
1830 # them.
1832 # them.
1831 msng_mnfst_lst = None
1833 msng_mnfst_lst = None
1832 msng_mnfst_set.clear()
1834 msng_mnfst_set.clear()
1833
1835
1834 if extranodes:
1836 if extranodes:
1835 for fname in extranodes:
1837 for fname in extranodes:
1836 if isinstance(fname, int):
1838 if isinstance(fname, int):
1837 continue
1839 continue
1838 add_extra_nodes(fname,
1840 add_extra_nodes(fname,
1839 msng_filenode_set.setdefault(fname, {}))
1841 msng_filenode_set.setdefault(fname, {}))
1840 changedfiles[fname] = 1
1842 changedfiles[fname] = 1
1841 changedfiles = changedfiles.keys()
1843 changedfiles = changedfiles.keys()
1842 changedfiles.sort()
1844 changedfiles.sort()
1843 # Go through all our files in order sorted by name.
1845 # Go through all our files in order sorted by name.
1844 for fname in changedfiles:
1846 for fname in changedfiles:
1845 filerevlog = self.file(fname)
1847 filerevlog = self.file(fname)
1846 if filerevlog.count() == 0:
1848 if filerevlog.count() == 0:
1847 raise util.Abort(_("empty or missing revlog for %s") % fname)
1849 raise util.Abort(_("empty or missing revlog for %s") % fname)
1848 # Toss out the filenodes that the recipient isn't really
1850 # Toss out the filenodes that the recipient isn't really
1849 # missing.
1851 # missing.
1850 if fname in msng_filenode_set:
1852 if fname in msng_filenode_set:
1851 prune_filenodes(fname, filerevlog)
1853 prune_filenodes(fname, filerevlog)
1852 msng_filenode_lst = msng_filenode_set[fname].keys()
1854 msng_filenode_lst = msng_filenode_set[fname].keys()
1853 else:
1855 else:
1854 msng_filenode_lst = []
1856 msng_filenode_lst = []
1855 # If any filenodes are left, generate the group for them,
1857 # If any filenodes are left, generate the group for them,
1856 # otherwise don't bother.
1858 # otherwise don't bother.
1857 if len(msng_filenode_lst) > 0:
1859 if len(msng_filenode_lst) > 0:
1858 yield changegroup.chunkheader(len(fname))
1860 yield changegroup.chunkheader(len(fname))
1859 yield fname
1861 yield fname
1860 # Sort the filenodes by their revision #
1862 # Sort the filenodes by their revision #
1861 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1863 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1862 # Create a group generator and only pass in a changenode
1864 # Create a group generator and only pass in a changenode
1863 # lookup function as we need to collect no information
1865 # lookup function as we need to collect no information
1864 # from filenodes.
1866 # from filenodes.
1865 group = filerevlog.group(msng_filenode_lst,
1867 group = filerevlog.group(msng_filenode_lst,
1866 lookup_filenode_link_func(fname))
1868 lookup_filenode_link_func(fname))
1867 for chnk in group:
1869 for chnk in group:
1868 yield chnk
1870 yield chnk
1869 if fname in msng_filenode_set:
1871 if fname in msng_filenode_set:
1870 # Don't need this anymore, toss it to free memory.
1872 # Don't need this anymore, toss it to free memory.
1871 del msng_filenode_set[fname]
1873 del msng_filenode_set[fname]
1872 # Signal that no more groups are left.
1874 # Signal that no more groups are left.
1873 yield changegroup.closechunk()
1875 yield changegroup.closechunk()
1874
1876
1875 if msng_cl_lst:
1877 if msng_cl_lst:
1876 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1878 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1877
1879
1878 return util.chunkbuffer(gengroup())
1880 return util.chunkbuffer(gengroup())
1879
1881
1880 def changegroup(self, basenodes, source):
1882 def changegroup(self, basenodes, source):
1881 """Generate a changegroup of all nodes that we have that a recipient
1883 """Generate a changegroup of all nodes that we have that a recipient
1882 doesn't.
1884 doesn't.
1883
1885
1884 This is much easier than the previous function as we can assume that
1886 This is much easier than the previous function as we can assume that
1885 the recipient has any changenode we aren't sending them."""
1887 the recipient has any changenode we aren't sending them."""
1886
1888
1887 self.hook('preoutgoing', throw=True, source=source)
1889 self.hook('preoutgoing', throw=True, source=source)
1888
1890
1889 cl = self.changelog
1891 cl = self.changelog
1890 nodes = cl.nodesbetween(basenodes, None)[0]
1892 nodes = cl.nodesbetween(basenodes, None)[0]
1891 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1893 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1892 self.changegroupinfo(nodes, source)
1894 self.changegroupinfo(nodes, source)
1893
1895
1894 def identity(x):
1896 def identity(x):
1895 return x
1897 return x
1896
1898
1897 def gennodelst(revlog):
1899 def gennodelst(revlog):
1898 for r in xrange(0, revlog.count()):
1900 for r in xrange(0, revlog.count()):
1899 n = revlog.node(r)
1901 n = revlog.node(r)
1900 if revlog.linkrev(n) in revset:
1902 if revlog.linkrev(n) in revset:
1901 yield n
1903 yield n
1902
1904
1903 def changed_file_collector(changedfileset):
1905 def changed_file_collector(changedfileset):
1904 def collect_changed_files(clnode):
1906 def collect_changed_files(clnode):
1905 c = cl.read(clnode)
1907 c = cl.read(clnode)
1906 for fname in c[3]:
1908 for fname in c[3]:
1907 changedfileset[fname] = 1
1909 changedfileset[fname] = 1
1908 return collect_changed_files
1910 return collect_changed_files
1909
1911
1910 def lookuprevlink_func(revlog):
1912 def lookuprevlink_func(revlog):
1911 def lookuprevlink(n):
1913 def lookuprevlink(n):
1912 return cl.node(revlog.linkrev(n))
1914 return cl.node(revlog.linkrev(n))
1913 return lookuprevlink
1915 return lookuprevlink
1914
1916
1915 def gengroup():
1917 def gengroup():
1916 # construct a list of all changed files
1918 # construct a list of all changed files
1917 changedfiles = {}
1919 changedfiles = {}
1918
1920
1919 for chnk in cl.group(nodes, identity,
1921 for chnk in cl.group(nodes, identity,
1920 changed_file_collector(changedfiles)):
1922 changed_file_collector(changedfiles)):
1921 yield chnk
1923 yield chnk
1922 changedfiles = changedfiles.keys()
1924 changedfiles = changedfiles.keys()
1923 changedfiles.sort()
1925 changedfiles.sort()
1924
1926
1925 mnfst = self.manifest
1927 mnfst = self.manifest
1926 nodeiter = gennodelst(mnfst)
1928 nodeiter = gennodelst(mnfst)
1927 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1929 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1928 yield chnk
1930 yield chnk
1929
1931
1930 for fname in changedfiles:
1932 for fname in changedfiles:
1931 filerevlog = self.file(fname)
1933 filerevlog = self.file(fname)
1932 if filerevlog.count() == 0:
1934 if filerevlog.count() == 0:
1933 raise util.Abort(_("empty or missing revlog for %s") % fname)
1935 raise util.Abort(_("empty or missing revlog for %s") % fname)
1934 nodeiter = gennodelst(filerevlog)
1936 nodeiter = gennodelst(filerevlog)
1935 nodeiter = list(nodeiter)
1937 nodeiter = list(nodeiter)
1936 if nodeiter:
1938 if nodeiter:
1937 yield changegroup.chunkheader(len(fname))
1939 yield changegroup.chunkheader(len(fname))
1938 yield fname
1940 yield fname
1939 lookup = lookuprevlink_func(filerevlog)
1941 lookup = lookuprevlink_func(filerevlog)
1940 for chnk in filerevlog.group(nodeiter, lookup):
1942 for chnk in filerevlog.group(nodeiter, lookup):
1941 yield chnk
1943 yield chnk
1942
1944
1943 yield changegroup.closechunk()
1945 yield changegroup.closechunk()
1944
1946
1945 if nodes:
1947 if nodes:
1946 self.hook('outgoing', node=hex(nodes[0]), source=source)
1948 self.hook('outgoing', node=hex(nodes[0]), source=source)
1947
1949
1948 return util.chunkbuffer(gengroup())
1950 return util.chunkbuffer(gengroup())
1949
1951
1950 def addchangegroup(self, source, srctype, url, emptyok=False):
1952 def addchangegroup(self, source, srctype, url, emptyok=False):
1951 """add changegroup to repo.
1953 """add changegroup to repo.
1952
1954
1953 return values:
1955 return values:
1954 - nothing changed or no source: 0
1956 - nothing changed or no source: 0
1955 - more heads than before: 1+added heads (2..n)
1957 - more heads than before: 1+added heads (2..n)
1956 - less heads than before: -1-removed heads (-2..-n)
1958 - less heads than before: -1-removed heads (-2..-n)
1957 - number of heads stays the same: 1
1959 - number of heads stays the same: 1
1958 """
1960 """
1959 def csmap(x):
1961 def csmap(x):
1960 self.ui.debug(_("add changeset %s\n") % short(x))
1962 self.ui.debug(_("add changeset %s\n") % short(x))
1961 return cl.count()
1963 return cl.count()
1962
1964
1963 def revmap(x):
1965 def revmap(x):
1964 return cl.rev(x)
1966 return cl.rev(x)
1965
1967
1966 if not source:
1968 if not source:
1967 return 0
1969 return 0
1968
1970
1969 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1971 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1970
1972
1971 changesets = files = revisions = 0
1973 changesets = files = revisions = 0
1972
1974
1973 # write changelog data to temp files so concurrent readers will not see
1975 # write changelog data to temp files so concurrent readers will not see
1974 # inconsistent view
1976 # inconsistent view
1975 cl = self.changelog
1977 cl = self.changelog
1976 cl.delayupdate()
1978 cl.delayupdate()
1977 oldheads = len(cl.heads())
1979 oldheads = len(cl.heads())
1978
1980
1979 tr = self.transaction()
1981 tr = self.transaction()
1980 try:
1982 try:
1981 trp = weakref.proxy(tr)
1983 trp = weakref.proxy(tr)
1982 # pull off the changeset group
1984 # pull off the changeset group
1983 self.ui.status(_("adding changesets\n"))
1985 self.ui.status(_("adding changesets\n"))
1984 cor = cl.count() - 1
1986 cor = cl.count() - 1
1985 chunkiter = changegroup.chunkiter(source)
1987 chunkiter = changegroup.chunkiter(source)
1986 if cl.addgroup(chunkiter, csmap, trp, 1) is None and not emptyok:
1988 if cl.addgroup(chunkiter, csmap, trp, 1) is None and not emptyok:
1987 raise util.Abort(_("received changelog group is empty"))
1989 raise util.Abort(_("received changelog group is empty"))
1988 cnr = cl.count() - 1
1990 cnr = cl.count() - 1
1989 changesets = cnr - cor
1991 changesets = cnr - cor
1990
1992
1991 # pull off the manifest group
1993 # pull off the manifest group
1992 self.ui.status(_("adding manifests\n"))
1994 self.ui.status(_("adding manifests\n"))
1993 chunkiter = changegroup.chunkiter(source)
1995 chunkiter = changegroup.chunkiter(source)
1994 # no need to check for empty manifest group here:
1996 # no need to check for empty manifest group here:
1995 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1997 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1996 # no new manifest will be created and the manifest group will
1998 # no new manifest will be created and the manifest group will
1997 # be empty during the pull
1999 # be empty during the pull
1998 self.manifest.addgroup(chunkiter, revmap, trp)
2000 self.manifest.addgroup(chunkiter, revmap, trp)
1999
2001
2000 # process the files
2002 # process the files
2001 self.ui.status(_("adding file changes\n"))
2003 self.ui.status(_("adding file changes\n"))
2002 while 1:
2004 while 1:
2003 f = changegroup.getchunk(source)
2005 f = changegroup.getchunk(source)
2004 if not f:
2006 if not f:
2005 break
2007 break
2006 self.ui.debug(_("adding %s revisions\n") % f)
2008 self.ui.debug(_("adding %s revisions\n") % f)
2007 fl = self.file(f)
2009 fl = self.file(f)
2008 o = fl.count()
2010 o = fl.count()
2009 chunkiter = changegroup.chunkiter(source)
2011 chunkiter = changegroup.chunkiter(source)
2010 if fl.addgroup(chunkiter, revmap, trp) is None:
2012 if fl.addgroup(chunkiter, revmap, trp) is None:
2011 raise util.Abort(_("received file revlog group is empty"))
2013 raise util.Abort(_("received file revlog group is empty"))
2012 revisions += fl.count() - o
2014 revisions += fl.count() - o
2013 files += 1
2015 files += 1
2014
2016
2015 # make changelog see real files again
2017 # make changelog see real files again
2016 cl.finalize(trp)
2018 cl.finalize(trp)
2017
2019
2018 newheads = len(self.changelog.heads())
2020 newheads = len(self.changelog.heads())
2019 heads = ""
2021 heads = ""
2020 if oldheads and newheads != oldheads:
2022 if oldheads and newheads != oldheads:
2021 heads = _(" (%+d heads)") % (newheads - oldheads)
2023 heads = _(" (%+d heads)") % (newheads - oldheads)
2022
2024
2023 self.ui.status(_("added %d changesets"
2025 self.ui.status(_("added %d changesets"
2024 " with %d changes to %d files%s\n")
2026 " with %d changes to %d files%s\n")
2025 % (changesets, revisions, files, heads))
2027 % (changesets, revisions, files, heads))
2026
2028
2027 if changesets > 0:
2029 if changesets > 0:
2028 self.hook('pretxnchangegroup', throw=True,
2030 self.hook('pretxnchangegroup', throw=True,
2029 node=hex(self.changelog.node(cor+1)), source=srctype,
2031 node=hex(self.changelog.node(cor+1)), source=srctype,
2030 url=url)
2032 url=url)
2031
2033
2032 tr.close()
2034 tr.close()
2033 finally:
2035 finally:
2034 del tr
2036 del tr
2035
2037
2036 if changesets > 0:
2038 if changesets > 0:
2037 # forcefully update the on-disk branch cache
2039 # forcefully update the on-disk branch cache
2038 self.ui.debug(_("updating the branch cache\n"))
2040 self.ui.debug(_("updating the branch cache\n"))
2039 self.branchtags()
2041 self.branchtags()
2040 self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
2042 self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
2041 source=srctype, url=url)
2043 source=srctype, url=url)
2042
2044
2043 for i in xrange(cor + 1, cnr + 1):
2045 for i in xrange(cor + 1, cnr + 1):
2044 self.hook("incoming", node=hex(self.changelog.node(i)),
2046 self.hook("incoming", node=hex(self.changelog.node(i)),
2045 source=srctype, url=url)
2047 source=srctype, url=url)
2046
2048
2047 # never return 0 here:
2049 # never return 0 here:
2048 if newheads < oldheads:
2050 if newheads < oldheads:
2049 return newheads - oldheads - 1
2051 return newheads - oldheads - 1
2050 else:
2052 else:
2051 return newheads - oldheads + 1
2053 return newheads - oldheads + 1
2052
2054
2053
2055
2054 def stream_in(self, remote):
2056 def stream_in(self, remote):
2055 fp = remote.stream_out()
2057 fp = remote.stream_out()
2056 l = fp.readline()
2058 l = fp.readline()
2057 try:
2059 try:
2058 resp = int(l)
2060 resp = int(l)
2059 except ValueError:
2061 except ValueError:
2060 raise util.UnexpectedOutput(
2062 raise util.UnexpectedOutput(
2061 _('Unexpected response from remote server:'), l)
2063 _('Unexpected response from remote server:'), l)
2062 if resp == 1:
2064 if resp == 1:
2063 raise util.Abort(_('operation forbidden by server'))
2065 raise util.Abort(_('operation forbidden by server'))
2064 elif resp == 2:
2066 elif resp == 2:
2065 raise util.Abort(_('locking the remote repository failed'))
2067 raise util.Abort(_('locking the remote repository failed'))
2066 elif resp != 0:
2068 elif resp != 0:
2067 raise util.Abort(_('the server sent an unknown error code'))
2069 raise util.Abort(_('the server sent an unknown error code'))
2068 self.ui.status(_('streaming all changes\n'))
2070 self.ui.status(_('streaming all changes\n'))
2069 l = fp.readline()
2071 l = fp.readline()
2070 try:
2072 try:
2071 total_files, total_bytes = map(int, l.split(' ', 1))
2073 total_files, total_bytes = map(int, l.split(' ', 1))
2072 except (ValueError, TypeError):
2074 except (ValueError, TypeError):
2073 raise util.UnexpectedOutput(
2075 raise util.UnexpectedOutput(
2074 _('Unexpected response from remote server:'), l)
2076 _('Unexpected response from remote server:'), l)
2075 self.ui.status(_('%d files to transfer, %s of data\n') %
2077 self.ui.status(_('%d files to transfer, %s of data\n') %
2076 (total_files, util.bytecount(total_bytes)))
2078 (total_files, util.bytecount(total_bytes)))
2077 start = time.time()
2079 start = time.time()
2078 for i in xrange(total_files):
2080 for i in xrange(total_files):
2079 # XXX doesn't support '\n' or '\r' in filenames
2081 # XXX doesn't support '\n' or '\r' in filenames
2080 l = fp.readline()
2082 l = fp.readline()
2081 try:
2083 try:
2082 name, size = l.split('\0', 1)
2084 name, size = l.split('\0', 1)
2083 size = int(size)
2085 size = int(size)
2084 except ValueError, TypeError:
2086 except ValueError, TypeError:
2085 raise util.UnexpectedOutput(
2087 raise util.UnexpectedOutput(
2086 _('Unexpected response from remote server:'), l)
2088 _('Unexpected response from remote server:'), l)
2087 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
2089 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
2088 ofp = self.sopener(name, 'w')
2090 ofp = self.sopener(name, 'w')
2089 for chunk in util.filechunkiter(fp, limit=size):
2091 for chunk in util.filechunkiter(fp, limit=size):
2090 ofp.write(chunk)
2092 ofp.write(chunk)
2091 ofp.close()
2093 ofp.close()
2092 elapsed = time.time() - start
2094 elapsed = time.time() - start
2093 if elapsed <= 0:
2095 if elapsed <= 0:
2094 elapsed = 0.001
2096 elapsed = 0.001
2095 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2097 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2096 (util.bytecount(total_bytes), elapsed,
2098 (util.bytecount(total_bytes), elapsed,
2097 util.bytecount(total_bytes / elapsed)))
2099 util.bytecount(total_bytes / elapsed)))
2098 self.invalidate()
2100 self.invalidate()
2099 return len(self.heads()) + 1
2101 return len(self.heads()) + 1
2100
2102
2101 def clone(self, remote, heads=[], stream=False):
2103 def clone(self, remote, heads=[], stream=False):
2102 '''clone remote repository.
2104 '''clone remote repository.
2103
2105
2104 keyword arguments:
2106 keyword arguments:
2105 heads: list of revs to clone (forces use of pull)
2107 heads: list of revs to clone (forces use of pull)
2106 stream: use streaming clone if possible'''
2108 stream: use streaming clone if possible'''
2107
2109
2108 # now, all clients that can request uncompressed clones can
2110 # now, all clients that can request uncompressed clones can
2109 # read repo formats supported by all servers that can serve
2111 # read repo formats supported by all servers that can serve
2110 # them.
2112 # them.
2111
2113
2112 # if revlog format changes, client will have to check version
2114 # if revlog format changes, client will have to check version
2113 # and format flags on "stream" capability, and use
2115 # and format flags on "stream" capability, and use
2114 # uncompressed only if compatible.
2116 # uncompressed only if compatible.
2115
2117
2116 if stream and not heads and remote.capable('stream'):
2118 if stream and not heads and remote.capable('stream'):
2117 return self.stream_in(remote)
2119 return self.stream_in(remote)
2118 return self.pull(remote, heads)
2120 return self.pull(remote, heads)
2119
2121
2120 # used to avoid circular references so destructors work
2122 # used to avoid circular references so destructors work
2121 def aftertrans(files):
2123 def aftertrans(files):
2122 renamefiles = [tuple(t) for t in files]
2124 renamefiles = [tuple(t) for t in files]
2123 def a():
2125 def a():
2124 for src, dest in renamefiles:
2126 for src, dest in renamefiles:
2125 util.rename(src, dest)
2127 util.rename(src, dest)
2126 return a
2128 return a
2127
2129
2128 def instance(ui, path, create):
2130 def instance(ui, path, create):
2129 return localrepository(ui, util.drop_scheme('file', path), create)
2131 return localrepository(ui, util.drop_scheme('file', path), create)
2130
2132
2131 def islocal(path):
2133 def islocal(path):
2132 return True
2134 return True
@@ -1,1348 +1,1348 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 from i18n import _
9 from i18n import _
10 from node import hex, nullid, short
10 from node import hex, nullid, short
11 import base85, cmdutil, mdiff, util, context, revlog, diffhelpers, copies
11 import base85, cmdutil, mdiff, util, context, revlog, diffhelpers, copies
12 import cStringIO, email.Parser, os, popen2, re, errno
12 import cStringIO, email.Parser, os, popen2, re, errno
13 import sys, tempfile, zlib
13 import sys, tempfile, zlib
14
14
15 class PatchError(Exception):
15 class PatchError(Exception):
16 pass
16 pass
17
17
18 class NoHunks(PatchError):
18 class NoHunks(PatchError):
19 pass
19 pass
20
20
21 # helper functions
21 # helper functions
22
22
23 def copyfile(src, dst, basedir=None):
23 def copyfile(src, dst, basedir=None):
24 if not basedir:
24 if not basedir:
25 basedir = os.getcwd()
25 basedir = os.getcwd()
26
26
27 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
27 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
28 if os.path.exists(absdst):
28 if os.path.exists(absdst):
29 raise util.Abort(_("cannot create %s: destination already exists") %
29 raise util.Abort(_("cannot create %s: destination already exists") %
30 dst)
30 dst)
31
31
32 targetdir = os.path.dirname(absdst)
32 targetdir = os.path.dirname(absdst)
33 if not os.path.isdir(targetdir):
33 if not os.path.isdir(targetdir):
34 os.makedirs(targetdir)
34 os.makedirs(targetdir)
35
35
36 util.copyfile(abssrc, absdst)
36 util.copyfile(abssrc, absdst)
37
37
38 # public functions
38 # public functions
39
39
40 def extract(ui, fileobj):
40 def extract(ui, fileobj):
41 '''extract patch from data read from fileobj.
41 '''extract patch from data read from fileobj.
42
42
43 patch can be a normal patch or contained in an email message.
43 patch can be a normal patch or contained in an email message.
44
44
45 return tuple (filename, message, user, date, node, p1, p2).
45 return tuple (filename, message, user, date, node, p1, p2).
46 Any item in the returned tuple can be None. If filename is None,
46 Any item in the returned tuple can be None. If filename is None,
47 fileobj did not contain a patch. Caller must unlink filename when done.'''
47 fileobj did not contain a patch. Caller must unlink filename when done.'''
48
48
49 # attempt to detect the start of a patch
49 # attempt to detect the start of a patch
50 # (this heuristic is borrowed from quilt)
50 # (this heuristic is borrowed from quilt)
51 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
51 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
52 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
52 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
53 '(---|\*\*\*)[ \t])', re.MULTILINE)
53 '(---|\*\*\*)[ \t])', re.MULTILINE)
54
54
55 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
55 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
56 tmpfp = os.fdopen(fd, 'w')
56 tmpfp = os.fdopen(fd, 'w')
57 try:
57 try:
58 msg = email.Parser.Parser().parse(fileobj)
58 msg = email.Parser.Parser().parse(fileobj)
59
59
60 subject = msg['Subject']
60 subject = msg['Subject']
61 user = msg['From']
61 user = msg['From']
62 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
62 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
63 # should try to parse msg['Date']
63 # should try to parse msg['Date']
64 date = None
64 date = None
65 nodeid = None
65 nodeid = None
66 branch = None
66 branch = None
67 parents = []
67 parents = []
68
68
69 if subject:
69 if subject:
70 if subject.startswith('[PATCH'):
70 if subject.startswith('[PATCH'):
71 pend = subject.find(']')
71 pend = subject.find(']')
72 if pend >= 0:
72 if pend >= 0:
73 subject = subject[pend+1:].lstrip()
73 subject = subject[pend+1:].lstrip()
74 subject = subject.replace('\n\t', ' ')
74 subject = subject.replace('\n\t', ' ')
75 ui.debug('Subject: %s\n' % subject)
75 ui.debug('Subject: %s\n' % subject)
76 if user:
76 if user:
77 ui.debug('From: %s\n' % user)
77 ui.debug('From: %s\n' % user)
78 diffs_seen = 0
78 diffs_seen = 0
79 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
79 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
80 message = ''
80 message = ''
81 for part in msg.walk():
81 for part in msg.walk():
82 content_type = part.get_content_type()
82 content_type = part.get_content_type()
83 ui.debug('Content-Type: %s\n' % content_type)
83 ui.debug('Content-Type: %s\n' % content_type)
84 if content_type not in ok_types:
84 if content_type not in ok_types:
85 continue
85 continue
86 payload = part.get_payload(decode=True)
86 payload = part.get_payload(decode=True)
87 m = diffre.search(payload)
87 m = diffre.search(payload)
88 if m:
88 if m:
89 hgpatch = False
89 hgpatch = False
90 ignoretext = False
90 ignoretext = False
91
91
92 ui.debug(_('found patch at byte %d\n') % m.start(0))
92 ui.debug(_('found patch at byte %d\n') % m.start(0))
93 diffs_seen += 1
93 diffs_seen += 1
94 cfp = cStringIO.StringIO()
94 cfp = cStringIO.StringIO()
95 for line in payload[:m.start(0)].splitlines():
95 for line in payload[:m.start(0)].splitlines():
96 if line.startswith('# HG changeset patch'):
96 if line.startswith('# HG changeset patch'):
97 ui.debug(_('patch generated by hg export\n'))
97 ui.debug(_('patch generated by hg export\n'))
98 hgpatch = True
98 hgpatch = True
99 # drop earlier commit message content
99 # drop earlier commit message content
100 cfp.seek(0)
100 cfp.seek(0)
101 cfp.truncate()
101 cfp.truncate()
102 subject = None
102 subject = None
103 elif hgpatch:
103 elif hgpatch:
104 if line.startswith('# User '):
104 if line.startswith('# User '):
105 user = line[7:]
105 user = line[7:]
106 ui.debug('From: %s\n' % user)
106 ui.debug('From: %s\n' % user)
107 elif line.startswith("# Date "):
107 elif line.startswith("# Date "):
108 date = line[7:]
108 date = line[7:]
109 elif line.startswith("# Branch "):
109 elif line.startswith("# Branch "):
110 branch = line[9:]
110 branch = line[9:]
111 elif line.startswith("# Node ID "):
111 elif line.startswith("# Node ID "):
112 nodeid = line[10:]
112 nodeid = line[10:]
113 elif line.startswith("# Parent "):
113 elif line.startswith("# Parent "):
114 parents.append(line[10:])
114 parents.append(line[10:])
115 elif line == '---' and gitsendmail:
115 elif line == '---' and gitsendmail:
116 ignoretext = True
116 ignoretext = True
117 if not line.startswith('# ') and not ignoretext:
117 if not line.startswith('# ') and not ignoretext:
118 cfp.write(line)
118 cfp.write(line)
119 cfp.write('\n')
119 cfp.write('\n')
120 message = cfp.getvalue()
120 message = cfp.getvalue()
121 if tmpfp:
121 if tmpfp:
122 tmpfp.write(payload)
122 tmpfp.write(payload)
123 if not payload.endswith('\n'):
123 if not payload.endswith('\n'):
124 tmpfp.write('\n')
124 tmpfp.write('\n')
125 elif not diffs_seen and message and content_type == 'text/plain':
125 elif not diffs_seen and message and content_type == 'text/plain':
126 message += '\n' + payload
126 message += '\n' + payload
127 except:
127 except:
128 tmpfp.close()
128 tmpfp.close()
129 os.unlink(tmpname)
129 os.unlink(tmpname)
130 raise
130 raise
131
131
132 if subject and not message.startswith(subject):
132 if subject and not message.startswith(subject):
133 message = '%s\n%s' % (subject, message)
133 message = '%s\n%s' % (subject, message)
134 tmpfp.close()
134 tmpfp.close()
135 if not diffs_seen:
135 if not diffs_seen:
136 os.unlink(tmpname)
136 os.unlink(tmpname)
137 return None, message, user, date, branch, None, None, None
137 return None, message, user, date, branch, None, None, None
138 p1 = parents and parents.pop(0) or None
138 p1 = parents and parents.pop(0) or None
139 p2 = parents and parents.pop(0) or None
139 p2 = parents and parents.pop(0) or None
140 return tmpname, message, user, date, branch, nodeid, p1, p2
140 return tmpname, message, user, date, branch, nodeid, p1, p2
141
141
142 GP_PATCH = 1 << 0 # we have to run patch
142 GP_PATCH = 1 << 0 # we have to run patch
143 GP_FILTER = 1 << 1 # there's some copy/rename operation
143 GP_FILTER = 1 << 1 # there's some copy/rename operation
144 GP_BINARY = 1 << 2 # there's a binary patch
144 GP_BINARY = 1 << 2 # there's a binary patch
145
145
146 def readgitpatch(fp, firstline=None):
146 def readgitpatch(fp, firstline=None):
147 """extract git-style metadata about patches from <patchname>"""
147 """extract git-style metadata about patches from <patchname>"""
148 class gitpatch:
148 class gitpatch:
149 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
149 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
150 def __init__(self, path):
150 def __init__(self, path):
151 self.path = path
151 self.path = path
152 self.oldpath = None
152 self.oldpath = None
153 self.mode = None
153 self.mode = None
154 self.op = 'MODIFY'
154 self.op = 'MODIFY'
155 self.lineno = 0
155 self.lineno = 0
156 self.binary = False
156 self.binary = False
157
157
158 def reader(fp, firstline):
158 def reader(fp, firstline):
159 if firstline is not None:
159 if firstline is not None:
160 yield firstline
160 yield firstline
161 for line in fp:
161 for line in fp:
162 yield line
162 yield line
163
163
164 # Filter patch for git information
164 # Filter patch for git information
165 gitre = re.compile('diff --git a/(.*) b/(.*)')
165 gitre = re.compile('diff --git a/(.*) b/(.*)')
166 gp = None
166 gp = None
167 gitpatches = []
167 gitpatches = []
168 # Can have a git patch with only metadata, causing patch to complain
168 # Can have a git patch with only metadata, causing patch to complain
169 dopatch = 0
169 dopatch = 0
170
170
171 lineno = 0
171 lineno = 0
172 for line in reader(fp, firstline):
172 for line in reader(fp, firstline):
173 lineno += 1
173 lineno += 1
174 if line.startswith('diff --git'):
174 if line.startswith('diff --git'):
175 m = gitre.match(line)
175 m = gitre.match(line)
176 if m:
176 if m:
177 if gp:
177 if gp:
178 gitpatches.append(gp)
178 gitpatches.append(gp)
179 src, dst = m.group(1, 2)
179 src, dst = m.group(1, 2)
180 gp = gitpatch(dst)
180 gp = gitpatch(dst)
181 gp.lineno = lineno
181 gp.lineno = lineno
182 elif gp:
182 elif gp:
183 if line.startswith('--- '):
183 if line.startswith('--- '):
184 if gp.op in ('COPY', 'RENAME'):
184 if gp.op in ('COPY', 'RENAME'):
185 dopatch |= GP_FILTER
185 dopatch |= GP_FILTER
186 gitpatches.append(gp)
186 gitpatches.append(gp)
187 gp = None
187 gp = None
188 dopatch |= GP_PATCH
188 dopatch |= GP_PATCH
189 continue
189 continue
190 if line.startswith('rename from '):
190 if line.startswith('rename from '):
191 gp.op = 'RENAME'
191 gp.op = 'RENAME'
192 gp.oldpath = line[12:].rstrip()
192 gp.oldpath = line[12:].rstrip()
193 elif line.startswith('rename to '):
193 elif line.startswith('rename to '):
194 gp.path = line[10:].rstrip()
194 gp.path = line[10:].rstrip()
195 elif line.startswith('copy from '):
195 elif line.startswith('copy from '):
196 gp.op = 'COPY'
196 gp.op = 'COPY'
197 gp.oldpath = line[10:].rstrip()
197 gp.oldpath = line[10:].rstrip()
198 elif line.startswith('copy to '):
198 elif line.startswith('copy to '):
199 gp.path = line[8:].rstrip()
199 gp.path = line[8:].rstrip()
200 elif line.startswith('deleted file'):
200 elif line.startswith('deleted file'):
201 gp.op = 'DELETE'
201 gp.op = 'DELETE'
202 elif line.startswith('new file mode '):
202 elif line.startswith('new file mode '):
203 gp.op = 'ADD'
203 gp.op = 'ADD'
204 gp.mode = int(line.rstrip()[-6:], 8)
204 gp.mode = int(line.rstrip()[-6:], 8)
205 elif line.startswith('new mode '):
205 elif line.startswith('new mode '):
206 gp.mode = int(line.rstrip()[-6:], 8)
206 gp.mode = int(line.rstrip()[-6:], 8)
207 elif line.startswith('GIT binary patch'):
207 elif line.startswith('GIT binary patch'):
208 dopatch |= GP_BINARY
208 dopatch |= GP_BINARY
209 gp.binary = True
209 gp.binary = True
210 if gp:
210 if gp:
211 gitpatches.append(gp)
211 gitpatches.append(gp)
212
212
213 if not gitpatches:
213 if not gitpatches:
214 dopatch = GP_PATCH
214 dopatch = GP_PATCH
215
215
216 return (dopatch, gitpatches)
216 return (dopatch, gitpatches)
217
217
218 def patch(patchname, ui, strip=1, cwd=None, files={}):
218 def patch(patchname, ui, strip=1, cwd=None, files={}):
219 """apply <patchname> to the working directory.
219 """apply <patchname> to the working directory.
220 returns whether patch was applied with fuzz factor."""
220 returns whether patch was applied with fuzz factor."""
221 patcher = ui.config('ui', 'patch')
221 patcher = ui.config('ui', 'patch')
222 args = []
222 args = []
223 try:
223 try:
224 if patcher:
224 if patcher:
225 return externalpatch(patcher, args, patchname, ui, strip, cwd,
225 return externalpatch(patcher, args, patchname, ui, strip, cwd,
226 files)
226 files)
227 else:
227 else:
228 try:
228 try:
229 return internalpatch(patchname, ui, strip, cwd, files)
229 return internalpatch(patchname, ui, strip, cwd, files)
230 except NoHunks:
230 except NoHunks:
231 patcher = util.find_exe('gpatch') or util.find_exe('patch')
231 patcher = util.find_exe('gpatch') or util.find_exe('patch')
232 ui.debug('no valid hunks found; trying with %r instead\n' %
232 ui.debug('no valid hunks found; trying with %r instead\n' %
233 patcher)
233 patcher)
234 if util.needbinarypatch():
234 if util.needbinarypatch():
235 args.append('--binary')
235 args.append('--binary')
236 return externalpatch(patcher, args, patchname, ui, strip, cwd,
236 return externalpatch(patcher, args, patchname, ui, strip, cwd,
237 files)
237 files)
238 except PatchError, err:
238 except PatchError, err:
239 s = str(err)
239 s = str(err)
240 if s:
240 if s:
241 raise util.Abort(s)
241 raise util.Abort(s)
242 else:
242 else:
243 raise util.Abort(_('patch failed to apply'))
243 raise util.Abort(_('patch failed to apply'))
244
244
245 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
245 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
246 """use <patcher> to apply <patchname> to the working directory.
246 """use <patcher> to apply <patchname> to the working directory.
247 returns whether patch was applied with fuzz factor."""
247 returns whether patch was applied with fuzz factor."""
248
248
249 fuzz = False
249 fuzz = False
250 if cwd:
250 if cwd:
251 args.append('-d %s' % util.shellquote(cwd))
251 args.append('-d %s' % util.shellquote(cwd))
252 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
252 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
253 util.shellquote(patchname)))
253 util.shellquote(patchname)))
254
254
255 for line in fp:
255 for line in fp:
256 line = line.rstrip()
256 line = line.rstrip()
257 ui.note(line + '\n')
257 ui.note(line + '\n')
258 if line.startswith('patching file '):
258 if line.startswith('patching file '):
259 pf = util.parse_patch_output(line)
259 pf = util.parse_patch_output(line)
260 printed_file = False
260 printed_file = False
261 files.setdefault(pf, (None, None))
261 files.setdefault(pf, (None, None))
262 elif line.find('with fuzz') >= 0:
262 elif line.find('with fuzz') >= 0:
263 fuzz = True
263 fuzz = True
264 if not printed_file:
264 if not printed_file:
265 ui.warn(pf + '\n')
265 ui.warn(pf + '\n')
266 printed_file = True
266 printed_file = True
267 ui.warn(line + '\n')
267 ui.warn(line + '\n')
268 elif line.find('saving rejects to file') >= 0:
268 elif line.find('saving rejects to file') >= 0:
269 ui.warn(line + '\n')
269 ui.warn(line + '\n')
270 elif line.find('FAILED') >= 0:
270 elif line.find('FAILED') >= 0:
271 if not printed_file:
271 if not printed_file:
272 ui.warn(pf + '\n')
272 ui.warn(pf + '\n')
273 printed_file = True
273 printed_file = True
274 ui.warn(line + '\n')
274 ui.warn(line + '\n')
275 code = fp.close()
275 code = fp.close()
276 if code:
276 if code:
277 raise PatchError(_("patch command failed: %s") %
277 raise PatchError(_("patch command failed: %s") %
278 util.explain_exit(code)[0])
278 util.explain_exit(code)[0])
279 return fuzz
279 return fuzz
280
280
281 def internalpatch(patchobj, ui, strip, cwd, files={}):
281 def internalpatch(patchobj, ui, strip, cwd, files={}):
282 """use builtin patch to apply <patchobj> to the working directory.
282 """use builtin patch to apply <patchobj> to the working directory.
283 returns whether patch was applied with fuzz factor."""
283 returns whether patch was applied with fuzz factor."""
284 try:
284 try:
285 fp = file(patchobj, 'rb')
285 fp = file(patchobj, 'rb')
286 except TypeError:
286 except TypeError:
287 fp = patchobj
287 fp = patchobj
288 if cwd:
288 if cwd:
289 curdir = os.getcwd()
289 curdir = os.getcwd()
290 os.chdir(cwd)
290 os.chdir(cwd)
291 try:
291 try:
292 ret = applydiff(ui, fp, files, strip=strip)
292 ret = applydiff(ui, fp, files, strip=strip)
293 finally:
293 finally:
294 if cwd:
294 if cwd:
295 os.chdir(curdir)
295 os.chdir(curdir)
296 if ret < 0:
296 if ret < 0:
297 raise PatchError
297 raise PatchError
298 return ret > 0
298 return ret > 0
299
299
300 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
300 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
301 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
301 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
302 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
302 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
303
303
304 class patchfile:
304 class patchfile:
305 def __init__(self, ui, fname, missing=False):
305 def __init__(self, ui, fname, missing=False):
306 self.fname = fname
306 self.fname = fname
307 self.ui = ui
307 self.ui = ui
308 self.lines = []
308 self.lines = []
309 self.exists = False
309 self.exists = False
310 self.missing = missing
310 self.missing = missing
311 if not missing:
311 if not missing:
312 try:
312 try:
313 fp = file(fname, 'rb')
313 fp = file(fname, 'rb')
314 self.lines = fp.readlines()
314 self.lines = fp.readlines()
315 self.exists = True
315 self.exists = True
316 except IOError:
316 except IOError:
317 pass
317 pass
318 else:
318 else:
319 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
319 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
320
320
321 if not self.exists:
321 if not self.exists:
322 dirname = os.path.dirname(fname)
322 dirname = os.path.dirname(fname)
323 if dirname and not os.path.isdir(dirname):
323 if dirname and not os.path.isdir(dirname):
324 os.makedirs(dirname)
324 os.makedirs(dirname)
325
325
326 self.hash = {}
326 self.hash = {}
327 self.dirty = 0
327 self.dirty = 0
328 self.offset = 0
328 self.offset = 0
329 self.rej = []
329 self.rej = []
330 self.fileprinted = False
330 self.fileprinted = False
331 self.printfile(False)
331 self.printfile(False)
332 self.hunks = 0
332 self.hunks = 0
333
333
334 def printfile(self, warn):
334 def printfile(self, warn):
335 if self.fileprinted:
335 if self.fileprinted:
336 return
336 return
337 if warn or self.ui.verbose:
337 if warn or self.ui.verbose:
338 self.fileprinted = True
338 self.fileprinted = True
339 s = _("patching file %s\n") % self.fname
339 s = _("patching file %s\n") % self.fname
340 if warn:
340 if warn:
341 self.ui.warn(s)
341 self.ui.warn(s)
342 else:
342 else:
343 self.ui.note(s)
343 self.ui.note(s)
344
344
345
345
346 def findlines(self, l, linenum):
346 def findlines(self, l, linenum):
347 # looks through the hash and finds candidate lines. The
347 # looks through the hash and finds candidate lines. The
348 # result is a list of line numbers sorted based on distance
348 # result is a list of line numbers sorted based on distance
349 # from linenum
349 # from linenum
350 def sorter(a, b):
350 def sorter(a, b):
351 vala = abs(a - linenum)
351 vala = abs(a - linenum)
352 valb = abs(b - linenum)
352 valb = abs(b - linenum)
353 return cmp(vala, valb)
353 return cmp(vala, valb)
354
354
355 try:
355 try:
356 cand = self.hash[l]
356 cand = self.hash[l]
357 except:
357 except:
358 return []
358 return []
359
359
360 if len(cand) > 1:
360 if len(cand) > 1:
361 # resort our list of potentials forward then back.
361 # resort our list of potentials forward then back.
362 cand.sort(sorter)
362 cand.sort(sorter)
363 return cand
363 return cand
364
364
365 def hashlines(self):
365 def hashlines(self):
366 self.hash = {}
366 self.hash = {}
367 for x in xrange(len(self.lines)):
367 for x in xrange(len(self.lines)):
368 s = self.lines[x]
368 s = self.lines[x]
369 self.hash.setdefault(s, []).append(x)
369 self.hash.setdefault(s, []).append(x)
370
370
371 def write_rej(self):
371 def write_rej(self):
372 # our rejects are a little different from patch(1). This always
372 # our rejects are a little different from patch(1). This always
373 # creates rejects in the same form as the original patch. A file
373 # creates rejects in the same form as the original patch. A file
374 # header is inserted so that you can run the reject through patch again
374 # header is inserted so that you can run the reject through patch again
375 # without having to type the filename.
375 # without having to type the filename.
376
376
377 if not self.rej:
377 if not self.rej:
378 return
378 return
379 if self.hunks != 1:
379 if self.hunks != 1:
380 hunkstr = "s"
380 hunkstr = "s"
381 else:
381 else:
382 hunkstr = ""
382 hunkstr = ""
383
383
384 fname = self.fname + ".rej"
384 fname = self.fname + ".rej"
385 self.ui.warn(
385 self.ui.warn(
386 _("%d out of %d hunk%s FAILED -- saving rejects to file %s\n") %
386 _("%d out of %d hunk%s FAILED -- saving rejects to file %s\n") %
387 (len(self.rej), self.hunks, hunkstr, fname))
387 (len(self.rej), self.hunks, hunkstr, fname))
388 try: os.unlink(fname)
388 try: os.unlink(fname)
389 except:
389 except:
390 pass
390 pass
391 fp = file(fname, 'wb')
391 fp = file(fname, 'wb')
392 base = os.path.basename(self.fname)
392 base = os.path.basename(self.fname)
393 fp.write("--- %s\n+++ %s\n" % (base, base))
393 fp.write("--- %s\n+++ %s\n" % (base, base))
394 for x in self.rej:
394 for x in self.rej:
395 for l in x.hunk:
395 for l in x.hunk:
396 fp.write(l)
396 fp.write(l)
397 if l[-1] != '\n':
397 if l[-1] != '\n':
398 fp.write("\n\ No newline at end of file\n")
398 fp.write("\n\ No newline at end of file\n")
399
399
400 def write(self, dest=None):
400 def write(self, dest=None):
401 if self.dirty:
401 if self.dirty:
402 if not dest:
402 if not dest:
403 dest = self.fname
403 dest = self.fname
404 st = None
404 st = None
405 try:
405 try:
406 st = os.lstat(dest)
406 st = os.lstat(dest)
407 except OSError, inst:
407 except OSError, inst:
408 if inst.errno != errno.ENOENT:
408 if inst.errno != errno.ENOENT:
409 raise
409 raise
410 if st and st.st_nlink > 1:
410 if st and st.st_nlink > 1:
411 os.unlink(dest)
411 os.unlink(dest)
412 fp = file(dest, 'wb')
412 fp = file(dest, 'wb')
413 if st and st.st_nlink > 1:
413 if st and st.st_nlink > 1:
414 os.chmod(dest, st.st_mode)
414 os.chmod(dest, st.st_mode)
415 fp.writelines(self.lines)
415 fp.writelines(self.lines)
416 fp.close()
416 fp.close()
417
417
418 def close(self):
418 def close(self):
419 self.write()
419 self.write()
420 self.write_rej()
420 self.write_rej()
421
421
422 def apply(self, h, reverse):
422 def apply(self, h, reverse):
423 if not h.complete():
423 if not h.complete():
424 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
424 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
425 (h.number, h.desc, len(h.a), h.lena, len(h.b),
425 (h.number, h.desc, len(h.a), h.lena, len(h.b),
426 h.lenb))
426 h.lenb))
427
427
428 self.hunks += 1
428 self.hunks += 1
429 if reverse:
429 if reverse:
430 h.reverse()
430 h.reverse()
431
431
432 if self.missing:
432 if self.missing:
433 self.rej.append(h)
433 self.rej.append(h)
434 return -1
434 return -1
435
435
436 if self.exists and h.createfile():
436 if self.exists and h.createfile():
437 self.ui.warn(_("file %s already exists\n") % self.fname)
437 self.ui.warn(_("file %s already exists\n") % self.fname)
438 self.rej.append(h)
438 self.rej.append(h)
439 return -1
439 return -1
440
440
441 if isinstance(h, binhunk):
441 if isinstance(h, binhunk):
442 if h.rmfile():
442 if h.rmfile():
443 os.unlink(self.fname)
443 os.unlink(self.fname)
444 else:
444 else:
445 self.lines[:] = h.new()
445 self.lines[:] = h.new()
446 self.offset += len(h.new())
446 self.offset += len(h.new())
447 self.dirty = 1
447 self.dirty = 1
448 return 0
448 return 0
449
449
450 # fast case first, no offsets, no fuzz
450 # fast case first, no offsets, no fuzz
451 old = h.old()
451 old = h.old()
452 # patch starts counting at 1 unless we are adding the file
452 # patch starts counting at 1 unless we are adding the file
453 if h.starta == 0:
453 if h.starta == 0:
454 start = 0
454 start = 0
455 else:
455 else:
456 start = h.starta + self.offset - 1
456 start = h.starta + self.offset - 1
457 orig_start = start
457 orig_start = start
458 if diffhelpers.testhunk(old, self.lines, start) == 0:
458 if diffhelpers.testhunk(old, self.lines, start) == 0:
459 if h.rmfile():
459 if h.rmfile():
460 os.unlink(self.fname)
460 os.unlink(self.fname)
461 else:
461 else:
462 self.lines[start : start + h.lena] = h.new()
462 self.lines[start : start + h.lena] = h.new()
463 self.offset += h.lenb - h.lena
463 self.offset += h.lenb - h.lena
464 self.dirty = 1
464 self.dirty = 1
465 return 0
465 return 0
466
466
467 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
467 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
468 self.hashlines()
468 self.hashlines()
469 if h.hunk[-1][0] != ' ':
469 if h.hunk[-1][0] != ' ':
470 # if the hunk tried to put something at the bottom of the file
470 # if the hunk tried to put something at the bottom of the file
471 # override the start line and use eof here
471 # override the start line and use eof here
472 search_start = len(self.lines)
472 search_start = len(self.lines)
473 else:
473 else:
474 search_start = orig_start
474 search_start = orig_start
475
475
476 for fuzzlen in xrange(3):
476 for fuzzlen in xrange(3):
477 for toponly in [ True, False ]:
477 for toponly in [ True, False ]:
478 old = h.old(fuzzlen, toponly)
478 old = h.old(fuzzlen, toponly)
479
479
480 cand = self.findlines(old[0][1:], search_start)
480 cand = self.findlines(old[0][1:], search_start)
481 for l in cand:
481 for l in cand:
482 if diffhelpers.testhunk(old, self.lines, l) == 0:
482 if diffhelpers.testhunk(old, self.lines, l) == 0:
483 newlines = h.new(fuzzlen, toponly)
483 newlines = h.new(fuzzlen, toponly)
484 self.lines[l : l + len(old)] = newlines
484 self.lines[l : l + len(old)] = newlines
485 self.offset += len(newlines) - len(old)
485 self.offset += len(newlines) - len(old)
486 self.dirty = 1
486 self.dirty = 1
487 if fuzzlen:
487 if fuzzlen:
488 fuzzstr = "with fuzz %d " % fuzzlen
488 fuzzstr = "with fuzz %d " % fuzzlen
489 f = self.ui.warn
489 f = self.ui.warn
490 self.printfile(True)
490 self.printfile(True)
491 else:
491 else:
492 fuzzstr = ""
492 fuzzstr = ""
493 f = self.ui.note
493 f = self.ui.note
494 offset = l - orig_start - fuzzlen
494 offset = l - orig_start - fuzzlen
495 if offset == 1:
495 if offset == 1:
496 linestr = "line"
496 linestr = "line"
497 else:
497 else:
498 linestr = "lines"
498 linestr = "lines"
499 f(_("Hunk #%d succeeded at %d %s(offset %d %s).\n") %
499 f(_("Hunk #%d succeeded at %d %s(offset %d %s).\n") %
500 (h.number, l+1, fuzzstr, offset, linestr))
500 (h.number, l+1, fuzzstr, offset, linestr))
501 return fuzzlen
501 return fuzzlen
502 self.printfile(True)
502 self.printfile(True)
503 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
503 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
504 self.rej.append(h)
504 self.rej.append(h)
505 return -1
505 return -1
506
506
507 class hunk:
507 class hunk:
508 def __init__(self, desc, num, lr, context, create=False, remove=False):
508 def __init__(self, desc, num, lr, context, create=False, remove=False):
509 self.number = num
509 self.number = num
510 self.desc = desc
510 self.desc = desc
511 self.hunk = [ desc ]
511 self.hunk = [ desc ]
512 self.a = []
512 self.a = []
513 self.b = []
513 self.b = []
514 if context:
514 if context:
515 self.read_context_hunk(lr)
515 self.read_context_hunk(lr)
516 else:
516 else:
517 self.read_unified_hunk(lr)
517 self.read_unified_hunk(lr)
518 self.create = create
518 self.create = create
519 self.remove = remove and not create
519 self.remove = remove and not create
520
520
521 def read_unified_hunk(self, lr):
521 def read_unified_hunk(self, lr):
522 m = unidesc.match(self.desc)
522 m = unidesc.match(self.desc)
523 if not m:
523 if not m:
524 raise PatchError(_("bad hunk #%d") % self.number)
524 raise PatchError(_("bad hunk #%d") % self.number)
525 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
525 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
526 if self.lena == None:
526 if self.lena == None:
527 self.lena = 1
527 self.lena = 1
528 else:
528 else:
529 self.lena = int(self.lena)
529 self.lena = int(self.lena)
530 if self.lenb == None:
530 if self.lenb == None:
531 self.lenb = 1
531 self.lenb = 1
532 else:
532 else:
533 self.lenb = int(self.lenb)
533 self.lenb = int(self.lenb)
534 self.starta = int(self.starta)
534 self.starta = int(self.starta)
535 self.startb = int(self.startb)
535 self.startb = int(self.startb)
536 diffhelpers.addlines(lr.fp, self.hunk, self.lena, self.lenb, self.a, self.b)
536 diffhelpers.addlines(lr.fp, self.hunk, self.lena, self.lenb, self.a, self.b)
537 # if we hit eof before finishing out the hunk, the last line will
537 # if we hit eof before finishing out the hunk, the last line will
538 # be zero length. Lets try to fix it up.
538 # be zero length. Lets try to fix it up.
539 while len(self.hunk[-1]) == 0:
539 while len(self.hunk[-1]) == 0:
540 del self.hunk[-1]
540 del self.hunk[-1]
541 del self.a[-1]
541 del self.a[-1]
542 del self.b[-1]
542 del self.b[-1]
543 self.lena -= 1
543 self.lena -= 1
544 self.lenb -= 1
544 self.lenb -= 1
545
545
546 def read_context_hunk(self, lr):
546 def read_context_hunk(self, lr):
547 self.desc = lr.readline()
547 self.desc = lr.readline()
548 m = contextdesc.match(self.desc)
548 m = contextdesc.match(self.desc)
549 if not m:
549 if not m:
550 raise PatchError(_("bad hunk #%d") % self.number)
550 raise PatchError(_("bad hunk #%d") % self.number)
551 foo, self.starta, foo2, aend, foo3 = m.groups()
551 foo, self.starta, foo2, aend, foo3 = m.groups()
552 self.starta = int(self.starta)
552 self.starta = int(self.starta)
553 if aend == None:
553 if aend == None:
554 aend = self.starta
554 aend = self.starta
555 self.lena = int(aend) - self.starta
555 self.lena = int(aend) - self.starta
556 if self.starta:
556 if self.starta:
557 self.lena += 1
557 self.lena += 1
558 for x in xrange(self.lena):
558 for x in xrange(self.lena):
559 l = lr.readline()
559 l = lr.readline()
560 if l.startswith('---'):
560 if l.startswith('---'):
561 lr.push(l)
561 lr.push(l)
562 break
562 break
563 s = l[2:]
563 s = l[2:]
564 if l.startswith('- ') or l.startswith('! '):
564 if l.startswith('- ') or l.startswith('! '):
565 u = '-' + s
565 u = '-' + s
566 elif l.startswith(' '):
566 elif l.startswith(' '):
567 u = ' ' + s
567 u = ' ' + s
568 else:
568 else:
569 raise PatchError(_("bad hunk #%d old text line %d") %
569 raise PatchError(_("bad hunk #%d old text line %d") %
570 (self.number, x))
570 (self.number, x))
571 self.a.append(u)
571 self.a.append(u)
572 self.hunk.append(u)
572 self.hunk.append(u)
573
573
574 l = lr.readline()
574 l = lr.readline()
575 if l.startswith('\ '):
575 if l.startswith('\ '):
576 s = self.a[-1][:-1]
576 s = self.a[-1][:-1]
577 self.a[-1] = s
577 self.a[-1] = s
578 self.hunk[-1] = s
578 self.hunk[-1] = s
579 l = lr.readline()
579 l = lr.readline()
580 m = contextdesc.match(l)
580 m = contextdesc.match(l)
581 if not m:
581 if not m:
582 raise PatchError(_("bad hunk #%d") % self.number)
582 raise PatchError(_("bad hunk #%d") % self.number)
583 foo, self.startb, foo2, bend, foo3 = m.groups()
583 foo, self.startb, foo2, bend, foo3 = m.groups()
584 self.startb = int(self.startb)
584 self.startb = int(self.startb)
585 if bend == None:
585 if bend == None:
586 bend = self.startb
586 bend = self.startb
587 self.lenb = int(bend) - self.startb
587 self.lenb = int(bend) - self.startb
588 if self.startb:
588 if self.startb:
589 self.lenb += 1
589 self.lenb += 1
590 hunki = 1
590 hunki = 1
591 for x in xrange(self.lenb):
591 for x in xrange(self.lenb):
592 l = lr.readline()
592 l = lr.readline()
593 if l.startswith('\ '):
593 if l.startswith('\ '):
594 s = self.b[-1][:-1]
594 s = self.b[-1][:-1]
595 self.b[-1] = s
595 self.b[-1] = s
596 self.hunk[hunki-1] = s
596 self.hunk[hunki-1] = s
597 continue
597 continue
598 if not l:
598 if not l:
599 lr.push(l)
599 lr.push(l)
600 break
600 break
601 s = l[2:]
601 s = l[2:]
602 if l.startswith('+ ') or l.startswith('! '):
602 if l.startswith('+ ') or l.startswith('! '):
603 u = '+' + s
603 u = '+' + s
604 elif l.startswith(' '):
604 elif l.startswith(' '):
605 u = ' ' + s
605 u = ' ' + s
606 elif len(self.b) == 0:
606 elif len(self.b) == 0:
607 # this can happen when the hunk does not add any lines
607 # this can happen when the hunk does not add any lines
608 lr.push(l)
608 lr.push(l)
609 break
609 break
610 else:
610 else:
611 raise PatchError(_("bad hunk #%d old text line %d") %
611 raise PatchError(_("bad hunk #%d old text line %d") %
612 (self.number, x))
612 (self.number, x))
613 self.b.append(s)
613 self.b.append(s)
614 while True:
614 while True:
615 if hunki >= len(self.hunk):
615 if hunki >= len(self.hunk):
616 h = ""
616 h = ""
617 else:
617 else:
618 h = self.hunk[hunki]
618 h = self.hunk[hunki]
619 hunki += 1
619 hunki += 1
620 if h == u:
620 if h == u:
621 break
621 break
622 elif h.startswith('-'):
622 elif h.startswith('-'):
623 continue
623 continue
624 else:
624 else:
625 self.hunk.insert(hunki-1, u)
625 self.hunk.insert(hunki-1, u)
626 break
626 break
627
627
628 if not self.a:
628 if not self.a:
629 # this happens when lines were only added to the hunk
629 # this happens when lines were only added to the hunk
630 for x in self.hunk:
630 for x in self.hunk:
631 if x.startswith('-') or x.startswith(' '):
631 if x.startswith('-') or x.startswith(' '):
632 self.a.append(x)
632 self.a.append(x)
633 if not self.b:
633 if not self.b:
634 # this happens when lines were only deleted from the hunk
634 # this happens when lines were only deleted from the hunk
635 for x in self.hunk:
635 for x in self.hunk:
636 if x.startswith('+') or x.startswith(' '):
636 if x.startswith('+') or x.startswith(' '):
637 self.b.append(x[1:])
637 self.b.append(x[1:])
638 # @@ -start,len +start,len @@
638 # @@ -start,len +start,len @@
639 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
639 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
640 self.startb, self.lenb)
640 self.startb, self.lenb)
641 self.hunk[0] = self.desc
641 self.hunk[0] = self.desc
642
642
643 def reverse(self):
643 def reverse(self):
644 self.create, self.remove = self.remove, self.create
644 self.create, self.remove = self.remove, self.create
645 origlena = self.lena
645 origlena = self.lena
646 origstarta = self.starta
646 origstarta = self.starta
647 self.lena = self.lenb
647 self.lena = self.lenb
648 self.starta = self.startb
648 self.starta = self.startb
649 self.lenb = origlena
649 self.lenb = origlena
650 self.startb = origstarta
650 self.startb = origstarta
651 self.a = []
651 self.a = []
652 self.b = []
652 self.b = []
653 # self.hunk[0] is the @@ description
653 # self.hunk[0] is the @@ description
654 for x in xrange(1, len(self.hunk)):
654 for x in xrange(1, len(self.hunk)):
655 o = self.hunk[x]
655 o = self.hunk[x]
656 if o.startswith('-'):
656 if o.startswith('-'):
657 n = '+' + o[1:]
657 n = '+' + o[1:]
658 self.b.append(o[1:])
658 self.b.append(o[1:])
659 elif o.startswith('+'):
659 elif o.startswith('+'):
660 n = '-' + o[1:]
660 n = '-' + o[1:]
661 self.a.append(n)
661 self.a.append(n)
662 else:
662 else:
663 n = o
663 n = o
664 self.b.append(o[1:])
664 self.b.append(o[1:])
665 self.a.append(o)
665 self.a.append(o)
666 self.hunk[x] = o
666 self.hunk[x] = o
667
667
668 def fix_newline(self):
668 def fix_newline(self):
669 diffhelpers.fix_newline(self.hunk, self.a, self.b)
669 diffhelpers.fix_newline(self.hunk, self.a, self.b)
670
670
671 def complete(self):
671 def complete(self):
672 return len(self.a) == self.lena and len(self.b) == self.lenb
672 return len(self.a) == self.lena and len(self.b) == self.lenb
673
673
674 def createfile(self):
674 def createfile(self):
675 return self.starta == 0 and self.lena == 0 and self.create
675 return self.starta == 0 and self.lena == 0 and self.create
676
676
677 def rmfile(self):
677 def rmfile(self):
678 return self.startb == 0 and self.lenb == 0 and self.remove
678 return self.startb == 0 and self.lenb == 0 and self.remove
679
679
680 def fuzzit(self, l, fuzz, toponly):
680 def fuzzit(self, l, fuzz, toponly):
681 # this removes context lines from the top and bottom of list 'l'. It
681 # this removes context lines from the top and bottom of list 'l'. It
682 # checks the hunk to make sure only context lines are removed, and then
682 # checks the hunk to make sure only context lines are removed, and then
683 # returns a new shortened list of lines.
683 # returns a new shortened list of lines.
684 fuzz = min(fuzz, len(l)-1)
684 fuzz = min(fuzz, len(l)-1)
685 if fuzz:
685 if fuzz:
686 top = 0
686 top = 0
687 bot = 0
687 bot = 0
688 hlen = len(self.hunk)
688 hlen = len(self.hunk)
689 for x in xrange(hlen-1):
689 for x in xrange(hlen-1):
690 # the hunk starts with the @@ line, so use x+1
690 # the hunk starts with the @@ line, so use x+1
691 if self.hunk[x+1][0] == ' ':
691 if self.hunk[x+1][0] == ' ':
692 top += 1
692 top += 1
693 else:
693 else:
694 break
694 break
695 if not toponly:
695 if not toponly:
696 for x in xrange(hlen-1):
696 for x in xrange(hlen-1):
697 if self.hunk[hlen-bot-1][0] == ' ':
697 if self.hunk[hlen-bot-1][0] == ' ':
698 bot += 1
698 bot += 1
699 else:
699 else:
700 break
700 break
701
701
702 # top and bot now count context in the hunk
702 # top and bot now count context in the hunk
703 # adjust them if either one is short
703 # adjust them if either one is short
704 context = max(top, bot, 3)
704 context = max(top, bot, 3)
705 if bot < context:
705 if bot < context:
706 bot = max(0, fuzz - (context - bot))
706 bot = max(0, fuzz - (context - bot))
707 else:
707 else:
708 bot = min(fuzz, bot)
708 bot = min(fuzz, bot)
709 if top < context:
709 if top < context:
710 top = max(0, fuzz - (context - top))
710 top = max(0, fuzz - (context - top))
711 else:
711 else:
712 top = min(fuzz, top)
712 top = min(fuzz, top)
713
713
714 return l[top:len(l)-bot]
714 return l[top:len(l)-bot]
715 return l
715 return l
716
716
717 def old(self, fuzz=0, toponly=False):
717 def old(self, fuzz=0, toponly=False):
718 return self.fuzzit(self.a, fuzz, toponly)
718 return self.fuzzit(self.a, fuzz, toponly)
719
719
720 def newctrl(self):
720 def newctrl(self):
721 res = []
721 res = []
722 for x in self.hunk:
722 for x in self.hunk:
723 c = x[0]
723 c = x[0]
724 if c == ' ' or c == '+':
724 if c == ' ' or c == '+':
725 res.append(x)
725 res.append(x)
726 return res
726 return res
727
727
728 def new(self, fuzz=0, toponly=False):
728 def new(self, fuzz=0, toponly=False):
729 return self.fuzzit(self.b, fuzz, toponly)
729 return self.fuzzit(self.b, fuzz, toponly)
730
730
731 class binhunk:
731 class binhunk:
732 'A binary patch file. Only understands literals so far.'
732 'A binary patch file. Only understands literals so far.'
733 def __init__(self, gitpatch):
733 def __init__(self, gitpatch):
734 self.gitpatch = gitpatch
734 self.gitpatch = gitpatch
735 self.text = None
735 self.text = None
736 self.hunk = ['GIT binary patch\n']
736 self.hunk = ['GIT binary patch\n']
737
737
738 def createfile(self):
738 def createfile(self):
739 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
739 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
740
740
741 def rmfile(self):
741 def rmfile(self):
742 return self.gitpatch.op == 'DELETE'
742 return self.gitpatch.op == 'DELETE'
743
743
744 def complete(self):
744 def complete(self):
745 return self.text is not None
745 return self.text is not None
746
746
747 def new(self):
747 def new(self):
748 return [self.text]
748 return [self.text]
749
749
750 def extract(self, fp):
750 def extract(self, fp):
751 line = fp.readline()
751 line = fp.readline()
752 self.hunk.append(line)
752 self.hunk.append(line)
753 while line and not line.startswith('literal '):
753 while line and not line.startswith('literal '):
754 line = fp.readline()
754 line = fp.readline()
755 self.hunk.append(line)
755 self.hunk.append(line)
756 if not line:
756 if not line:
757 raise PatchError(_('could not extract binary patch'))
757 raise PatchError(_('could not extract binary patch'))
758 size = int(line[8:].rstrip())
758 size = int(line[8:].rstrip())
759 dec = []
759 dec = []
760 line = fp.readline()
760 line = fp.readline()
761 self.hunk.append(line)
761 self.hunk.append(line)
762 while len(line) > 1:
762 while len(line) > 1:
763 l = line[0]
763 l = line[0]
764 if l <= 'Z' and l >= 'A':
764 if l <= 'Z' and l >= 'A':
765 l = ord(l) - ord('A') + 1
765 l = ord(l) - ord('A') + 1
766 else:
766 else:
767 l = ord(l) - ord('a') + 27
767 l = ord(l) - ord('a') + 27
768 dec.append(base85.b85decode(line[1:-1])[:l])
768 dec.append(base85.b85decode(line[1:-1])[:l])
769 line = fp.readline()
769 line = fp.readline()
770 self.hunk.append(line)
770 self.hunk.append(line)
771 text = zlib.decompress(''.join(dec))
771 text = zlib.decompress(''.join(dec))
772 if len(text) != size:
772 if len(text) != size:
773 raise PatchError(_('binary patch is %d bytes, not %d') %
773 raise PatchError(_('binary patch is %d bytes, not %d') %
774 len(text), size)
774 len(text), size)
775 self.text = text
775 self.text = text
776
776
777 def parsefilename(str):
777 def parsefilename(str):
778 # --- filename \t|space stuff
778 # --- filename \t|space stuff
779 s = str[4:].rstrip('\r\n')
779 s = str[4:].rstrip('\r\n')
780 i = s.find('\t')
780 i = s.find('\t')
781 if i < 0:
781 if i < 0:
782 i = s.find(' ')
782 i = s.find(' ')
783 if i < 0:
783 if i < 0:
784 return s
784 return s
785 return s[:i]
785 return s[:i]
786
786
787 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
787 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
788 def pathstrip(path, count=1):
788 def pathstrip(path, count=1):
789 pathlen = len(path)
789 pathlen = len(path)
790 i = 0
790 i = 0
791 if count == 0:
791 if count == 0:
792 return '', path.rstrip()
792 return '', path.rstrip()
793 while count > 0:
793 while count > 0:
794 i = path.find('/', i)
794 i = path.find('/', i)
795 if i == -1:
795 if i == -1:
796 raise PatchError(_("unable to strip away %d dirs from %s") %
796 raise PatchError(_("unable to strip away %d dirs from %s") %
797 (count, path))
797 (count, path))
798 i += 1
798 i += 1
799 # consume '//' in the path
799 # consume '//' in the path
800 while i < pathlen - 1 and path[i] == '/':
800 while i < pathlen - 1 and path[i] == '/':
801 i += 1
801 i += 1
802 count -= 1
802 count -= 1
803 return path[:i].lstrip(), path[i:].rstrip()
803 return path[:i].lstrip(), path[i:].rstrip()
804
804
805 nulla = afile_orig == "/dev/null"
805 nulla = afile_orig == "/dev/null"
806 nullb = bfile_orig == "/dev/null"
806 nullb = bfile_orig == "/dev/null"
807 abase, afile = pathstrip(afile_orig, strip)
807 abase, afile = pathstrip(afile_orig, strip)
808 gooda = not nulla and os.path.exists(afile)
808 gooda = not nulla and os.path.exists(afile)
809 bbase, bfile = pathstrip(bfile_orig, strip)
809 bbase, bfile = pathstrip(bfile_orig, strip)
810 if afile == bfile:
810 if afile == bfile:
811 goodb = gooda
811 goodb = gooda
812 else:
812 else:
813 goodb = not nullb and os.path.exists(bfile)
813 goodb = not nullb and os.path.exists(bfile)
814 createfunc = hunk.createfile
814 createfunc = hunk.createfile
815 if reverse:
815 if reverse:
816 createfunc = hunk.rmfile
816 createfunc = hunk.rmfile
817 missing = not goodb and not gooda and not createfunc()
817 missing = not goodb and not gooda and not createfunc()
818 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
818 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
819 # diff is between a file and its backup. In this case, the original
819 # diff is between a file and its backup. In this case, the original
820 # file should be patched (see original mpatch code).
820 # file should be patched (see original mpatch code).
821 isbackup = (abase == bbase and bfile.startswith(afile))
821 isbackup = (abase == bbase and bfile.startswith(afile))
822 fname = None
822 fname = None
823 if not missing:
823 if not missing:
824 if gooda and goodb:
824 if gooda and goodb:
825 fname = isbackup and afile or bfile
825 fname = isbackup and afile or bfile
826 elif gooda:
826 elif gooda:
827 fname = afile
827 fname = afile
828
828
829 if not fname:
829 if not fname:
830 if not nullb:
830 if not nullb:
831 fname = isbackup and afile or bfile
831 fname = isbackup and afile or bfile
832 elif not nulla:
832 elif not nulla:
833 fname = afile
833 fname = afile
834 else:
834 else:
835 raise PatchError(_("undefined source and destination files"))
835 raise PatchError(_("undefined source and destination files"))
836
836
837 return fname, missing
837 return fname, missing
838
838
839 class linereader:
839 class linereader:
840 # simple class to allow pushing lines back into the input stream
840 # simple class to allow pushing lines back into the input stream
841 def __init__(self, fp):
841 def __init__(self, fp):
842 self.fp = fp
842 self.fp = fp
843 self.buf = []
843 self.buf = []
844
844
845 def push(self, line):
845 def push(self, line):
846 self.buf.append(line)
846 self.buf.append(line)
847
847
848 def readline(self):
848 def readline(self):
849 if self.buf:
849 if self.buf:
850 l = self.buf[0]
850 l = self.buf[0]
851 del self.buf[0]
851 del self.buf[0]
852 return l
852 return l
853 return self.fp.readline()
853 return self.fp.readline()
854
854
855 def iterhunks(ui, fp, sourcefile=None):
855 def iterhunks(ui, fp, sourcefile=None):
856 """Read a patch and yield the following events:
856 """Read a patch and yield the following events:
857 - ("file", afile, bfile, firsthunk): select a new target file.
857 - ("file", afile, bfile, firsthunk): select a new target file.
858 - ("hunk", hunk): a new hunk is ready to be applied, follows a
858 - ("hunk", hunk): a new hunk is ready to be applied, follows a
859 "file" event.
859 "file" event.
860 - ("git", gitchanges): current diff is in git format, gitchanges
860 - ("git", gitchanges): current diff is in git format, gitchanges
861 maps filenames to gitpatch records. Unique event.
861 maps filenames to gitpatch records. Unique event.
862 """
862 """
863
863
864 def scangitpatch(fp, firstline):
864 def scangitpatch(fp, firstline):
865 '''git patches can modify a file, then copy that file to
865 '''git patches can modify a file, then copy that file to
866 a new file, but expect the source to be the unmodified form.
866 a new file, but expect the source to be the unmodified form.
867 So we scan the patch looking for that case so we can do
867 So we scan the patch looking for that case so we can do
868 the copies ahead of time.'''
868 the copies ahead of time.'''
869
869
870 pos = 0
870 pos = 0
871 try:
871 try:
872 pos = fp.tell()
872 pos = fp.tell()
873 except IOError:
873 except IOError:
874 fp = cStringIO.StringIO(fp.read())
874 fp = cStringIO.StringIO(fp.read())
875
875
876 (dopatch, gitpatches) = readgitpatch(fp, firstline)
876 (dopatch, gitpatches) = readgitpatch(fp, firstline)
877 fp.seek(pos)
877 fp.seek(pos)
878
878
879 return fp, dopatch, gitpatches
879 return fp, dopatch, gitpatches
880
880
881 changed = {}
881 changed = {}
882 current_hunk = None
882 current_hunk = None
883 afile = ""
883 afile = ""
884 bfile = ""
884 bfile = ""
885 state = None
885 state = None
886 hunknum = 0
886 hunknum = 0
887 emitfile = False
887 emitfile = False
888
888
889 git = False
889 git = False
890 gitre = re.compile('diff --git (a/.*) (b/.*)')
890 gitre = re.compile('diff --git (a/.*) (b/.*)')
891
891
892 # our states
892 # our states
893 BFILE = 1
893 BFILE = 1
894 context = None
894 context = None
895 lr = linereader(fp)
895 lr = linereader(fp)
896 dopatch = True
896 dopatch = True
897 # gitworkdone is True if a git operation (copy, rename, ...) was
897 # gitworkdone is True if a git operation (copy, rename, ...) was
898 # performed already for the current file. Useful when the file
898 # performed already for the current file. Useful when the file
899 # section may have no hunk.
899 # section may have no hunk.
900 gitworkdone = False
900 gitworkdone = False
901
901
902 while True:
902 while True:
903 newfile = False
903 newfile = False
904 x = lr.readline()
904 x = lr.readline()
905 if not x:
905 if not x:
906 break
906 break
907 if current_hunk:
907 if current_hunk:
908 if x.startswith('\ '):
908 if x.startswith('\ '):
909 current_hunk.fix_newline()
909 current_hunk.fix_newline()
910 yield 'hunk', current_hunk
910 yield 'hunk', current_hunk
911 current_hunk = None
911 current_hunk = None
912 gitworkdone = False
912 gitworkdone = False
913 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
913 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
914 ((context or context == None) and x.startswith('***************')))):
914 ((context or context == None) and x.startswith('***************')))):
915 try:
915 try:
916 if context == None and x.startswith('***************'):
916 if context == None and x.startswith('***************'):
917 context = True
917 context = True
918 gpatch = changed.get(bfile[2:], (None, None))[1]
918 gpatch = changed.get(bfile[2:], (None, None))[1]
919 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
919 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
920 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
920 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
921 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
921 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
922 except PatchError, err:
922 except PatchError, err:
923 ui.debug(err)
923 ui.debug(err)
924 current_hunk = None
924 current_hunk = None
925 continue
925 continue
926 hunknum += 1
926 hunknum += 1
927 if emitfile:
927 if emitfile:
928 emitfile = False
928 emitfile = False
929 yield 'file', (afile, bfile, current_hunk)
929 yield 'file', (afile, bfile, current_hunk)
930 elif state == BFILE and x.startswith('GIT binary patch'):
930 elif state == BFILE and x.startswith('GIT binary patch'):
931 current_hunk = binhunk(changed[bfile[2:]][1])
931 current_hunk = binhunk(changed[bfile[2:]][1])
932 hunknum += 1
932 hunknum += 1
933 if emitfile:
933 if emitfile:
934 emitfile = False
934 emitfile = False
935 yield 'file', (afile, bfile, current_hunk)
935 yield 'file', (afile, bfile, current_hunk)
936 current_hunk.extract(fp)
936 current_hunk.extract(fp)
937 elif x.startswith('diff --git'):
937 elif x.startswith('diff --git'):
938 # check for git diff, scanning the whole patch file if needed
938 # check for git diff, scanning the whole patch file if needed
939 m = gitre.match(x)
939 m = gitre.match(x)
940 if m:
940 if m:
941 afile, bfile = m.group(1, 2)
941 afile, bfile = m.group(1, 2)
942 if not git:
942 if not git:
943 git = True
943 git = True
944 fp, dopatch, gitpatches = scangitpatch(fp, x)
944 fp, dopatch, gitpatches = scangitpatch(fp, x)
945 yield 'git', gitpatches
945 yield 'git', gitpatches
946 for gp in gitpatches:
946 for gp in gitpatches:
947 changed[gp.path] = (gp.op, gp)
947 changed[gp.path] = (gp.op, gp)
948 # else error?
948 # else error?
949 # copy/rename + modify should modify target, not source
949 # copy/rename + modify should modify target, not source
950 gitop = changed.get(bfile[2:], (None, None))[0]
950 gitop = changed.get(bfile[2:], (None, None))[0]
951 if gitop in ('COPY', 'DELETE', 'RENAME'):
951 if gitop in ('COPY', 'DELETE', 'RENAME'):
952 afile = bfile
952 afile = bfile
953 gitworkdone = True
953 gitworkdone = True
954 newfile = True
954 newfile = True
955 elif x.startswith('---'):
955 elif x.startswith('---'):
956 # check for a unified diff
956 # check for a unified diff
957 l2 = lr.readline()
957 l2 = lr.readline()
958 if not l2.startswith('+++'):
958 if not l2.startswith('+++'):
959 lr.push(l2)
959 lr.push(l2)
960 continue
960 continue
961 newfile = True
961 newfile = True
962 context = False
962 context = False
963 afile = parsefilename(x)
963 afile = parsefilename(x)
964 bfile = parsefilename(l2)
964 bfile = parsefilename(l2)
965 elif x.startswith('***'):
965 elif x.startswith('***'):
966 # check for a context diff
966 # check for a context diff
967 l2 = lr.readline()
967 l2 = lr.readline()
968 if not l2.startswith('---'):
968 if not l2.startswith('---'):
969 lr.push(l2)
969 lr.push(l2)
970 continue
970 continue
971 l3 = lr.readline()
971 l3 = lr.readline()
972 lr.push(l3)
972 lr.push(l3)
973 if not l3.startswith("***************"):
973 if not l3.startswith("***************"):
974 lr.push(l2)
974 lr.push(l2)
975 continue
975 continue
976 newfile = True
976 newfile = True
977 context = True
977 context = True
978 afile = parsefilename(x)
978 afile = parsefilename(x)
979 bfile = parsefilename(l2)
979 bfile = parsefilename(l2)
980
980
981 if newfile:
981 if newfile:
982 emitfile = True
982 emitfile = True
983 state = BFILE
983 state = BFILE
984 hunknum = 0
984 hunknum = 0
985 if current_hunk:
985 if current_hunk:
986 if current_hunk.complete():
986 if current_hunk.complete():
987 yield 'hunk', current_hunk
987 yield 'hunk', current_hunk
988 else:
988 else:
989 raise PatchError(_("malformed patch %s %s") % (afile,
989 raise PatchError(_("malformed patch %s %s") % (afile,
990 current_hunk.desc))
990 current_hunk.desc))
991
991
992 if hunknum == 0 and dopatch and not gitworkdone:
992 if hunknum == 0 and dopatch and not gitworkdone:
993 raise NoHunks
993 raise NoHunks
994
994
995 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
995 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
996 rejmerge=None, updatedir=None):
996 rejmerge=None, updatedir=None):
997 """reads a patch from fp and tries to apply it. The dict 'changed' is
997 """reads a patch from fp and tries to apply it. The dict 'changed' is
998 filled in with all of the filenames changed by the patch. Returns 0
998 filled in with all of the filenames changed by the patch. Returns 0
999 for a clean patch, -1 if any rejects were found and 1 if there was
999 for a clean patch, -1 if any rejects were found and 1 if there was
1000 any fuzz."""
1000 any fuzz."""
1001
1001
1002 rejects = 0
1002 rejects = 0
1003 err = 0
1003 err = 0
1004 current_file = None
1004 current_file = None
1005 gitpatches = None
1005 gitpatches = None
1006
1006
1007 def closefile():
1007 def closefile():
1008 if not current_file:
1008 if not current_file:
1009 return 0
1009 return 0
1010 current_file.close()
1010 current_file.close()
1011 if rejmerge:
1011 if rejmerge:
1012 rejmerge(current_file)
1012 rejmerge(current_file)
1013 return len(current_file.rej)
1013 return len(current_file.rej)
1014
1014
1015 for state, values in iterhunks(ui, fp, sourcefile):
1015 for state, values in iterhunks(ui, fp, sourcefile):
1016 if state == 'hunk':
1016 if state == 'hunk':
1017 if not current_file:
1017 if not current_file:
1018 continue
1018 continue
1019 current_hunk = values
1019 current_hunk = values
1020 ret = current_file.apply(current_hunk, reverse)
1020 ret = current_file.apply(current_hunk, reverse)
1021 if ret >= 0:
1021 if ret >= 0:
1022 changed.setdefault(current_file.fname, (None, None))
1022 changed.setdefault(current_file.fname, (None, None))
1023 if ret > 0:
1023 if ret > 0:
1024 err = 1
1024 err = 1
1025 elif state == 'file':
1025 elif state == 'file':
1026 rejects += closefile()
1026 rejects += closefile()
1027 afile, bfile, first_hunk = values
1027 afile, bfile, first_hunk = values
1028 try:
1028 try:
1029 if sourcefile:
1029 if sourcefile:
1030 current_file = patchfile(ui, sourcefile)
1030 current_file = patchfile(ui, sourcefile)
1031 else:
1031 else:
1032 current_file, missing = selectfile(afile, bfile, first_hunk,
1032 current_file, missing = selectfile(afile, bfile, first_hunk,
1033 strip, reverse)
1033 strip, reverse)
1034 current_file = patchfile(ui, current_file, missing)
1034 current_file = patchfile(ui, current_file, missing)
1035 except PatchError, err:
1035 except PatchError, err:
1036 ui.warn(str(err) + '\n')
1036 ui.warn(str(err) + '\n')
1037 current_file, current_hunk = None, None
1037 current_file, current_hunk = None, None
1038 rejects += 1
1038 rejects += 1
1039 continue
1039 continue
1040 elif state == 'git':
1040 elif state == 'git':
1041 gitpatches = values
1041 gitpatches = values
1042 for gp in gitpatches:
1042 for gp in gitpatches:
1043 if gp.op in ('COPY', 'RENAME'):
1043 if gp.op in ('COPY', 'RENAME'):
1044 copyfile(gp.oldpath, gp.path)
1044 copyfile(gp.oldpath, gp.path)
1045 changed[gp.path] = (gp.op, gp)
1045 changed[gp.path] = (gp.op, gp)
1046 else:
1046 else:
1047 raise util.Abort(_('unsupported parser state: %s') % state)
1047 raise util.Abort(_('unsupported parser state: %s') % state)
1048
1048
1049 rejects += closefile()
1049 rejects += closefile()
1050
1050
1051 if updatedir and gitpatches:
1051 if updatedir and gitpatches:
1052 updatedir(gitpatches)
1052 updatedir(gitpatches)
1053 if rejects:
1053 if rejects:
1054 return -1
1054 return -1
1055 return err
1055 return err
1056
1056
1057 def diffopts(ui, opts={}, untrusted=False):
1057 def diffopts(ui, opts={}, untrusted=False):
1058 def get(key, name=None, getter=ui.configbool):
1058 def get(key, name=None, getter=ui.configbool):
1059 return (opts.get(key) or
1059 return (opts.get(key) or
1060 getter('diff', name or key, None, untrusted=untrusted))
1060 getter('diff', name or key, None, untrusted=untrusted))
1061 return mdiff.diffopts(
1061 return mdiff.diffopts(
1062 text=opts.get('text'),
1062 text=opts.get('text'),
1063 git=get('git'),
1063 git=get('git'),
1064 nodates=get('nodates'),
1064 nodates=get('nodates'),
1065 showfunc=get('show_function', 'showfunc'),
1065 showfunc=get('show_function', 'showfunc'),
1066 ignorews=get('ignore_all_space', 'ignorews'),
1066 ignorews=get('ignore_all_space', 'ignorews'),
1067 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1067 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1068 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1068 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1069 context=get('unified', getter=ui.config))
1069 context=get('unified', getter=ui.config))
1070
1070
1071 def updatedir(ui, repo, patches):
1071 def updatedir(ui, repo, patches):
1072 '''Update dirstate after patch application according to metadata'''
1072 '''Update dirstate after patch application according to metadata'''
1073 if not patches:
1073 if not patches:
1074 return
1074 return
1075 copies = []
1075 copies = []
1076 removes = {}
1076 removes = {}
1077 cfiles = patches.keys()
1077 cfiles = patches.keys()
1078 cwd = repo.getcwd()
1078 cwd = repo.getcwd()
1079 if cwd:
1079 if cwd:
1080 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1080 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1081 for f in patches:
1081 for f in patches:
1082 ctype, gp = patches[f]
1082 ctype, gp = patches[f]
1083 if ctype == 'RENAME':
1083 if ctype == 'RENAME':
1084 copies.append((gp.oldpath, gp.path))
1084 copies.append((gp.oldpath, gp.path))
1085 removes[gp.oldpath] = 1
1085 removes[gp.oldpath] = 1
1086 elif ctype == 'COPY':
1086 elif ctype == 'COPY':
1087 copies.append((gp.oldpath, gp.path))
1087 copies.append((gp.oldpath, gp.path))
1088 elif ctype == 'DELETE':
1088 elif ctype == 'DELETE':
1089 removes[gp.path] = 1
1089 removes[gp.path] = 1
1090 for src, dst in copies:
1090 for src, dst in copies:
1091 repo.copy(src, dst)
1091 repo.copy(src, dst)
1092 removes = removes.keys()
1092 removes = removes.keys()
1093 if removes:
1093 if removes:
1094 removes.sort()
1094 removes.sort()
1095 repo.remove(removes, True)
1095 repo.remove(removes, True)
1096 for f in patches:
1096 for f in patches:
1097 ctype, gp = patches[f]
1097 ctype, gp = patches[f]
1098 if gp and gp.mode:
1098 if gp and gp.mode:
1099 flags = ''
1099 flags = ''
1100 if gp.mode & 0100:
1100 if gp.mode & 0100:
1101 flags = 'x'
1101 flags = 'x'
1102 elif gp.mode & 020000:
1102 elif gp.mode & 020000:
1103 flags = 'l'
1103 flags = 'l'
1104 dst = os.path.join(repo.root, gp.path)
1104 dst = os.path.join(repo.root, gp.path)
1105 # patch won't create empty files
1105 # patch won't create empty files
1106 if ctype == 'ADD' and not os.path.exists(dst):
1106 if ctype == 'ADD' and not os.path.exists(dst):
1107 repo.wwrite(gp.path, '', flags)
1107 repo.wwrite(gp.path, '', flags)
1108 else:
1108 else:
1109 util.set_flags(dst, flags)
1109 util.set_flags(dst, flags)
1110 cmdutil.addremove(repo, cfiles)
1110 cmdutil.addremove(repo, cfiles)
1111 files = patches.keys()
1111 files = patches.keys()
1112 files.extend([r for r in removes if r not in files])
1112 files.extend([r for r in removes if r not in files])
1113 files.sort()
1113 files.sort()
1114
1114
1115 return files
1115 return files
1116
1116
1117 def b85diff(to, tn):
1117 def b85diff(to, tn):
1118 '''print base85-encoded binary diff'''
1118 '''print base85-encoded binary diff'''
1119 def gitindex(text):
1119 def gitindex(text):
1120 if not text:
1120 if not text:
1121 return '0' * 40
1121 return '0' * 40
1122 l = len(text)
1122 l = len(text)
1123 s = util.sha1('blob %d\0' % l)
1123 s = util.sha1('blob %d\0' % l)
1124 s.update(text)
1124 s.update(text)
1125 return s.hexdigest()
1125 return s.hexdigest()
1126
1126
1127 def fmtline(line):
1127 def fmtline(line):
1128 l = len(line)
1128 l = len(line)
1129 if l <= 26:
1129 if l <= 26:
1130 l = chr(ord('A') + l - 1)
1130 l = chr(ord('A') + l - 1)
1131 else:
1131 else:
1132 l = chr(l - 26 + ord('a') - 1)
1132 l = chr(l - 26 + ord('a') - 1)
1133 return '%c%s\n' % (l, base85.b85encode(line, True))
1133 return '%c%s\n' % (l, base85.b85encode(line, True))
1134
1134
1135 def chunk(text, csize=52):
1135 def chunk(text, csize=52):
1136 l = len(text)
1136 l = len(text)
1137 i = 0
1137 i = 0
1138 while i < l:
1138 while i < l:
1139 yield text[i:i+csize]
1139 yield text[i:i+csize]
1140 i += csize
1140 i += csize
1141
1141
1142 tohash = gitindex(to)
1142 tohash = gitindex(to)
1143 tnhash = gitindex(tn)
1143 tnhash = gitindex(tn)
1144 if tohash == tnhash:
1144 if tohash == tnhash:
1145 return ""
1145 return ""
1146
1146
1147 # TODO: deltas
1147 # TODO: deltas
1148 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1148 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1149 (tohash, tnhash, len(tn))]
1149 (tohash, tnhash, len(tn))]
1150 for l in chunk(zlib.compress(tn)):
1150 for l in chunk(zlib.compress(tn)):
1151 ret.append(fmtline(l))
1151 ret.append(fmtline(l))
1152 ret.append('\n')
1152 ret.append('\n')
1153 return ''.join(ret)
1153 return ''.join(ret)
1154
1154
1155 def diff(repo, node1=None, node2=None, match=None,
1155 def diff(repo, node1=None, node2=None, match=None,
1156 fp=None, changes=None, opts=None):
1156 fp=None, changes=None, opts=None):
1157 '''print diff of changes to files between two nodes, or node and
1157 '''print diff of changes to files between two nodes, or node and
1158 working directory.
1158 working directory.
1159
1159
1160 if node1 is None, use first dirstate parent instead.
1160 if node1 is None, use first dirstate parent instead.
1161 if node2 is None, compare node1 with working directory.'''
1161 if node2 is None, compare node1 with working directory.'''
1162
1162
1163 if not match:
1163 if not match:
1164 match = cmdutil.matchall(repo)
1164 match = cmdutil.matchall(repo)
1165
1165
1166 if opts is None:
1166 if opts is None:
1167 opts = mdiff.defaultopts
1167 opts = mdiff.defaultopts
1168 if fp is None:
1168 if fp is None:
1169 fp = repo.ui
1169 fp = repo.ui
1170
1170
1171 if not node1:
1171 if not node1:
1172 node1 = repo.dirstate.parents()[0]
1172 node1 = repo.dirstate.parents()[0]
1173
1173
1174 flcache = {}
1174 flcache = {}
1175 def getfilectx(f, ctx):
1175 def getfilectx(f, ctx):
1176 flctx = ctx.filectx(f, filelog=flcache.get(f))
1176 flctx = ctx.filectx(f, filelog=flcache.get(f))
1177 if f not in flcache:
1177 if f not in flcache:
1178 flcache[f] = flctx._filelog
1178 flcache[f] = flctx._filelog
1179 return flctx
1179 return flctx
1180
1180
1181 # reading the data for node1 early allows it to play nicely
1181 # reading the data for node1 early allows it to play nicely
1182 # with repo.status and the revlog cache.
1182 # with repo.status and the revlog cache.
1183 ctx1 = context.changectx(repo, node1)
1183 ctx1 = context.changectx(repo, node1)
1184 # force manifest reading
1184 # force manifest reading
1185 man1 = ctx1.manifest()
1185 man1 = ctx1.manifest()
1186 date1 = util.datestr(ctx1.date())
1186 date1 = util.datestr(ctx1.date())
1187
1187
1188 if not changes:
1188 if not changes:
1189 changes = repo.status(node1, node2, files=match.files(), match=match)[:5]
1189 changes = repo.status(node1, node2, match=match)[:5]
1190 modified, added, removed, deleted, unknown = changes
1190 modified, added, removed, deleted, unknown = changes
1191
1191
1192 if not modified and not added and not removed:
1192 if not modified and not added and not removed:
1193 return
1193 return
1194
1194
1195 if node2:
1195 if node2:
1196 ctx2 = context.changectx(repo, node2)
1196 ctx2 = context.changectx(repo, node2)
1197 execf2 = ctx2.manifest().execf
1197 execf2 = ctx2.manifest().execf
1198 linkf2 = ctx2.manifest().linkf
1198 linkf2 = ctx2.manifest().linkf
1199 else:
1199 else:
1200 ctx2 = context.workingctx(repo)
1200 ctx2 = context.workingctx(repo)
1201 execf2 = util.execfunc(repo.root, None)
1201 execf2 = util.execfunc(repo.root, None)
1202 linkf2 = util.linkfunc(repo.root, None)
1202 linkf2 = util.linkfunc(repo.root, None)
1203 if execf2 is None:
1203 if execf2 is None:
1204 mc = ctx2.parents()[0].manifest().copy()
1204 mc = ctx2.parents()[0].manifest().copy()
1205 execf2 = mc.execf
1205 execf2 = mc.execf
1206 linkf2 = mc.linkf
1206 linkf2 = mc.linkf
1207
1207
1208 if repo.ui.quiet:
1208 if repo.ui.quiet:
1209 r = None
1209 r = None
1210 else:
1210 else:
1211 hexfunc = repo.ui.debugflag and hex or short
1211 hexfunc = repo.ui.debugflag and hex or short
1212 r = [hexfunc(node) for node in [node1, node2] if node]
1212 r = [hexfunc(node) for node in [node1, node2] if node]
1213
1213
1214 if opts.git:
1214 if opts.git:
1215 copy, diverge = copies.copies(repo, ctx1, ctx2, repo.changectx(nullid))
1215 copy, diverge = copies.copies(repo, ctx1, ctx2, repo.changectx(nullid))
1216 for k, v in copy.items():
1216 for k, v in copy.items():
1217 copy[v] = k
1217 copy[v] = k
1218
1218
1219 all = modified + added + removed
1219 all = modified + added + removed
1220 all.sort()
1220 all.sort()
1221 gone = {}
1221 gone = {}
1222
1222
1223 for f in all:
1223 for f in all:
1224 to = None
1224 to = None
1225 tn = None
1225 tn = None
1226 dodiff = True
1226 dodiff = True
1227 header = []
1227 header = []
1228 if f in man1:
1228 if f in man1:
1229 to = getfilectx(f, ctx1).data()
1229 to = getfilectx(f, ctx1).data()
1230 if f not in removed:
1230 if f not in removed:
1231 tn = getfilectx(f, ctx2).data()
1231 tn = getfilectx(f, ctx2).data()
1232 a, b = f, f
1232 a, b = f, f
1233 if opts.git:
1233 if opts.git:
1234 def gitmode(x, l):
1234 def gitmode(x, l):
1235 return l and '120000' or (x and '100755' or '100644')
1235 return l and '120000' or (x and '100755' or '100644')
1236 def addmodehdr(header, omode, nmode):
1236 def addmodehdr(header, omode, nmode):
1237 if omode != nmode:
1237 if omode != nmode:
1238 header.append('old mode %s\n' % omode)
1238 header.append('old mode %s\n' % omode)
1239 header.append('new mode %s\n' % nmode)
1239 header.append('new mode %s\n' % nmode)
1240
1240
1241 if f in added:
1241 if f in added:
1242 mode = gitmode(execf2(f), linkf2(f))
1242 mode = gitmode(execf2(f), linkf2(f))
1243 if f in copy:
1243 if f in copy:
1244 a = copy[f]
1244 a = copy[f]
1245 omode = gitmode(man1.execf(a), man1.linkf(a))
1245 omode = gitmode(man1.execf(a), man1.linkf(a))
1246 addmodehdr(header, omode, mode)
1246 addmodehdr(header, omode, mode)
1247 if a in removed and a not in gone:
1247 if a in removed and a not in gone:
1248 op = 'rename'
1248 op = 'rename'
1249 gone[a] = 1
1249 gone[a] = 1
1250 else:
1250 else:
1251 op = 'copy'
1251 op = 'copy'
1252 header.append('%s from %s\n' % (op, a))
1252 header.append('%s from %s\n' % (op, a))
1253 header.append('%s to %s\n' % (op, f))
1253 header.append('%s to %s\n' % (op, f))
1254 to = getfilectx(a, ctx1).data()
1254 to = getfilectx(a, ctx1).data()
1255 else:
1255 else:
1256 header.append('new file mode %s\n' % mode)
1256 header.append('new file mode %s\n' % mode)
1257 if util.binary(tn):
1257 if util.binary(tn):
1258 dodiff = 'binary'
1258 dodiff = 'binary'
1259 elif f in removed:
1259 elif f in removed:
1260 # have we already reported a copy above?
1260 # have we already reported a copy above?
1261 if f in copy and copy[f] in added and copy[copy[f]] == f:
1261 if f in copy and copy[f] in added and copy[copy[f]] == f:
1262 dodiff = False
1262 dodiff = False
1263 else:
1263 else:
1264 mode = gitmode(man1.execf(f), man1.linkf(f))
1264 mode = gitmode(man1.execf(f), man1.linkf(f))
1265 header.append('deleted file mode %s\n' % mode)
1265 header.append('deleted file mode %s\n' % mode)
1266 else:
1266 else:
1267 omode = gitmode(man1.execf(f), man1.linkf(f))
1267 omode = gitmode(man1.execf(f), man1.linkf(f))
1268 nmode = gitmode(execf2(f), linkf2(f))
1268 nmode = gitmode(execf2(f), linkf2(f))
1269 addmodehdr(header, omode, nmode)
1269 addmodehdr(header, omode, nmode)
1270 if util.binary(to) or util.binary(tn):
1270 if util.binary(to) or util.binary(tn):
1271 dodiff = 'binary'
1271 dodiff = 'binary'
1272 r = None
1272 r = None
1273 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
1273 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
1274 if dodiff:
1274 if dodiff:
1275 if dodiff == 'binary':
1275 if dodiff == 'binary':
1276 text = b85diff(to, tn)
1276 text = b85diff(to, tn)
1277 else:
1277 else:
1278 text = mdiff.unidiff(to, date1,
1278 text = mdiff.unidiff(to, date1,
1279 # ctx2 date may be dynamic
1279 # ctx2 date may be dynamic
1280 tn, util.datestr(ctx2.date()),
1280 tn, util.datestr(ctx2.date()),
1281 a, b, r, opts=opts)
1281 a, b, r, opts=opts)
1282 if text or len(header) > 1:
1282 if text or len(header) > 1:
1283 fp.write(''.join(header))
1283 fp.write(''.join(header))
1284 fp.write(text)
1284 fp.write(text)
1285
1285
1286 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1286 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1287 opts=None):
1287 opts=None):
1288 '''export changesets as hg patches.'''
1288 '''export changesets as hg patches.'''
1289
1289
1290 total = len(revs)
1290 total = len(revs)
1291 revwidth = max([len(str(rev)) for rev in revs])
1291 revwidth = max([len(str(rev)) for rev in revs])
1292
1292
1293 def single(rev, seqno, fp):
1293 def single(rev, seqno, fp):
1294 ctx = repo.changectx(rev)
1294 ctx = repo.changectx(rev)
1295 node = ctx.node()
1295 node = ctx.node()
1296 parents = [p.node() for p in ctx.parents() if p]
1296 parents = [p.node() for p in ctx.parents() if p]
1297 branch = ctx.branch()
1297 branch = ctx.branch()
1298 if switch_parent:
1298 if switch_parent:
1299 parents.reverse()
1299 parents.reverse()
1300 prev = (parents and parents[0]) or nullid
1300 prev = (parents and parents[0]) or nullid
1301
1301
1302 if not fp:
1302 if not fp:
1303 fp = cmdutil.make_file(repo, template, node, total=total,
1303 fp = cmdutil.make_file(repo, template, node, total=total,
1304 seqno=seqno, revwidth=revwidth)
1304 seqno=seqno, revwidth=revwidth)
1305 if fp != sys.stdout and hasattr(fp, 'name'):
1305 if fp != sys.stdout and hasattr(fp, 'name'):
1306 repo.ui.note("%s\n" % fp.name)
1306 repo.ui.note("%s\n" % fp.name)
1307
1307
1308 fp.write("# HG changeset patch\n")
1308 fp.write("# HG changeset patch\n")
1309 fp.write("# User %s\n" % ctx.user())
1309 fp.write("# User %s\n" % ctx.user())
1310 fp.write("# Date %d %d\n" % ctx.date())
1310 fp.write("# Date %d %d\n" % ctx.date())
1311 if branch and (branch != 'default'):
1311 if branch and (branch != 'default'):
1312 fp.write("# Branch %s\n" % branch)
1312 fp.write("# Branch %s\n" % branch)
1313 fp.write("# Node ID %s\n" % hex(node))
1313 fp.write("# Node ID %s\n" % hex(node))
1314 fp.write("# Parent %s\n" % hex(prev))
1314 fp.write("# Parent %s\n" % hex(prev))
1315 if len(parents) > 1:
1315 if len(parents) > 1:
1316 fp.write("# Parent %s\n" % hex(parents[1]))
1316 fp.write("# Parent %s\n" % hex(parents[1]))
1317 fp.write(ctx.description().rstrip())
1317 fp.write(ctx.description().rstrip())
1318 fp.write("\n\n")
1318 fp.write("\n\n")
1319
1319
1320 diff(repo, prev, node, fp=fp, opts=opts)
1320 diff(repo, prev, node, fp=fp, opts=opts)
1321 if fp not in (sys.stdout, repo.ui):
1321 if fp not in (sys.stdout, repo.ui):
1322 fp.close()
1322 fp.close()
1323
1323
1324 for seqno, rev in enumerate(revs):
1324 for seqno, rev in enumerate(revs):
1325 single(rev, seqno+1, fp)
1325 single(rev, seqno+1, fp)
1326
1326
1327 def diffstat(patchlines):
1327 def diffstat(patchlines):
1328 if not util.find_exe('diffstat'):
1328 if not util.find_exe('diffstat'):
1329 return
1329 return
1330 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
1330 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
1331 try:
1331 try:
1332 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
1332 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
1333 try:
1333 try:
1334 for line in patchlines:
1334 for line in patchlines:
1335 p.tochild.write(line + "\n")
1335 p.tochild.write(line + "\n")
1336 p.tochild.close()
1336 p.tochild.close()
1337 if p.wait(): return
1337 if p.wait(): return
1338 fp = os.fdopen(fd, 'r')
1338 fp = os.fdopen(fd, 'r')
1339 stat = []
1339 stat = []
1340 for line in fp: stat.append(line.lstrip())
1340 for line in fp: stat.append(line.lstrip())
1341 last = stat.pop()
1341 last = stat.pop()
1342 stat.insert(0, last)
1342 stat.insert(0, last)
1343 stat = ''.join(stat)
1343 stat = ''.join(stat)
1344 return stat
1344 return stat
1345 except: raise
1345 except: raise
1346 finally:
1346 finally:
1347 try: os.unlink(name)
1347 try: os.unlink(name)
1348 except: pass
1348 except: pass
@@ -1,552 +1,553 b''
1 % help
1 % help
2 mq extension - patch management and development
2 mq extension - patch management and development
3
3
4 This extension lets you work with a stack of patches in a Mercurial
4 This extension lets you work with a stack of patches in a Mercurial
5 repository. It manages two stacks of patches - all known patches, and
5 repository. It manages two stacks of patches - all known patches, and
6 applied patches (subset of known patches).
6 applied patches (subset of known patches).
7
7
8 Known patches are represented as patch files in the .hg/patches
8 Known patches are represented as patch files in the .hg/patches
9 directory. Applied patches are both patch files and changesets.
9 directory. Applied patches are both patch files and changesets.
10
10
11 Common tasks (use "hg help command" for more details):
11 Common tasks (use "hg help command" for more details):
12
12
13 prepare repository to work with patches qinit
13 prepare repository to work with patches qinit
14 create new patch qnew
14 create new patch qnew
15 import existing patch qimport
15 import existing patch qimport
16
16
17 print patch series qseries
17 print patch series qseries
18 print applied patches qapplied
18 print applied patches qapplied
19 print name of top applied patch qtop
19 print name of top applied patch qtop
20
20
21 add known patch to applied stack qpush
21 add known patch to applied stack qpush
22 remove patch from applied stack qpop
22 remove patch from applied stack qpop
23 refresh contents of top applied patch qrefresh
23 refresh contents of top applied patch qrefresh
24
24
25 list of commands:
25 list of commands:
26
26
27 qapplied print the patches already applied
27 qapplied print the patches already applied
28 qclone clone main and patch repository at same time
28 qclone clone main and patch repository at same time
29 qcommit commit changes in the queue repository
29 qcommit commit changes in the queue repository
30 qdelete remove patches from queue
30 qdelete remove patches from queue
31 qdiff diff of the current patch
31 qdiff diff of the current patch
32 qfold fold the named patches into the current patch
32 qfold fold the named patches into the current patch
33 qgoto push or pop patches until named patch is at top of stack
33 qgoto push or pop patches until named patch is at top of stack
34 qguard set or print guards for a patch
34 qguard set or print guards for a patch
35 qheader Print the header of the topmost or specified patch
35 qheader Print the header of the topmost or specified patch
36 qimport import a patch
36 qimport import a patch
37 qinit init a new queue repository
37 qinit init a new queue repository
38 qnew create a new patch
38 qnew create a new patch
39 qnext print the name of the next patch
39 qnext print the name of the next patch
40 qpop pop the current patch off the stack
40 qpop pop the current patch off the stack
41 qprev print the name of the previous patch
41 qprev print the name of the previous patch
42 qpush push the next patch onto the stack
42 qpush push the next patch onto the stack
43 qrefresh update the current patch
43 qrefresh update the current patch
44 qrename rename a patch
44 qrename rename a patch
45 qrestore restore the queue state saved by a rev
45 qrestore restore the queue state saved by a rev
46 qsave save current queue state
46 qsave save current queue state
47 qselect set or print guarded patches to push
47 qselect set or print guarded patches to push
48 qseries print the entire series file
48 qseries print the entire series file
49 qtop print the name of the current patch
49 qtop print the name of the current patch
50 qunapplied print the patches not yet applied
50 qunapplied print the patches not yet applied
51 strip strip a revision and all later revs on the same branch
51 strip strip a revision and all later revs on the same branch
52
52
53 use "hg -v help mq" to show aliases and global options
53 use "hg -v help mq" to show aliases and global options
54 adding a
54 adding a
55 updating working directory
55 updating working directory
56 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
56 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
57 adding b/z
57 adding b/z
58 % qinit
58 % qinit
59 % -R qinit
59 % -R qinit
60 % qinit -c
60 % qinit -c
61 A .hgignore
61 A .hgignore
62 A series
62 A series
63 % qnew should refuse bad patch names
63 % qnew should refuse bad patch names
64 abort: "series" cannot be used as the name of a patch
64 abort: "series" cannot be used as the name of a patch
65 abort: "status" cannot be used as the name of a patch
65 abort: "status" cannot be used as the name of a patch
66 abort: "guards" cannot be used as the name of a patch
66 abort: "guards" cannot be used as the name of a patch
67 abort: ".hgignore" cannot be used as the name of a patch
67 abort: ".hgignore" cannot be used as the name of a patch
68 % qnew implies add
68 % qnew implies add
69 A .hgignore
69 A .hgignore
70 A series
70 A series
71 A test.patch
71 A test.patch
72 % qinit; qinit -c
72 % qinit; qinit -c
73 .hgignore:
73 .hgignore:
74 ^\.hg
74 ^\.hg
75 ^\.mq
75 ^\.mq
76 syntax: glob
76 syntax: glob
77 status
77 status
78 guards
78 guards
79 series:
79 series:
80 abort: repository already exists!
80 abort: repository already exists!
81 % qinit; <stuff>; qinit -c
81 % qinit; <stuff>; qinit -c
82 adding .hg/patches/A
82 adding .hg/patches/A
83 adding .hg/patches/B
83 adding .hg/patches/B
84 A .hgignore
84 A .hgignore
85 A A
85 A A
86 A B
86 A B
87 A series
87 A series
88 .hgignore:
88 .hgignore:
89 status
89 status
90 bleh
90 bleh
91 series:
91 series:
92 A
92 A
93 B
93 B
94 % qnew with uncommitted changes
94 % qnew with uncommitted changes
95 abort: local changes found, refresh first
95 abort: local changes found, refresh first
96 A somefile
96 A somefile
97 % qnew with uncommitted changes and missing file (issue 803)
97 % qnew with uncommitted changes and missing file (issue 803)
98 someotherfile: No such file or directory
98 someotherfile: No such file or directory
99 someotherfile: No such file or directory
99 A somefile
100 A somefile
100 issue803.patch
101 issue803.patch
101 Patch queue now empty
102 Patch queue now empty
102 % qnew -m
103 % qnew -m
103 foo bar
104 foo bar
104 % qrefresh
105 % qrefresh
105 foo bar
106 foo bar
106
107
107 diff -r xa
108 diff -r xa
108 --- a/a
109 --- a/a
109 +++ b/a
110 +++ b/a
110 @@ -1,1 +1,2 @@
111 @@ -1,1 +1,2 @@
111 a
112 a
112 +a
113 +a
113 % empty qrefresh
114 % empty qrefresh
114 revision:
115 revision:
115 patch:
116 patch:
116 foo bar
117 foo bar
117
118
118 working dir diff:
119 working dir diff:
119 --- a/a
120 --- a/a
120 +++ b/a
121 +++ b/a
121 @@ -1,1 +1,2 @@
122 @@ -1,1 +1,2 @@
122 a
123 a
123 +a
124 +a
124 % qpop
125 % qpop
125 Patch queue now empty
126 Patch queue now empty
126 % qpush
127 % qpush
127 applying test.patch
128 applying test.patch
128 Now at: test.patch
129 Now at: test.patch
129 % pop/push outside repo
130 % pop/push outside repo
130 Patch queue now empty
131 Patch queue now empty
131 applying test.patch
132 applying test.patch
132 Now at: test.patch
133 Now at: test.patch
133 % qrefresh in subdir
134 % qrefresh in subdir
134 % pop/push -a in subdir
135 % pop/push -a in subdir
135 Patch queue now empty
136 Patch queue now empty
136 applying test.patch
137 applying test.patch
137 applying test2.patch
138 applying test2.patch
138 Now at: test2.patch
139 Now at: test2.patch
139 % qseries
140 % qseries
140 test.patch
141 test.patch
141 test2.patch
142 test2.patch
142 Now at: test.patch
143 Now at: test.patch
143 0 A test.patch: foo bar
144 0 A test.patch: foo bar
144 1 U test2.patch:
145 1 U test2.patch:
145 applying test2.patch
146 applying test2.patch
146 Now at: test2.patch
147 Now at: test2.patch
147 % qapplied
148 % qapplied
148 test.patch
149 test.patch
149 test2.patch
150 test2.patch
150 % qtop
151 % qtop
151 test2.patch
152 test2.patch
152 % qprev
153 % qprev
153 test.patch
154 test.patch
154 % qnext
155 % qnext
155 All patches applied
156 All patches applied
156 % pop, qnext, qprev, qapplied
157 % pop, qnext, qprev, qapplied
157 Now at: test.patch
158 Now at: test.patch
158 test2.patch
159 test2.patch
159 Only one patch applied
160 Only one patch applied
160 test.patch
161 test.patch
161 % commit should fail
162 % commit should fail
162 abort: cannot commit over an applied mq patch
163 abort: cannot commit over an applied mq patch
163 % push should fail
164 % push should fail
164 pushing to ../../k
165 pushing to ../../k
165 abort: source has mq patches applied
166 abort: source has mq patches applied
166 % qunapplied
167 % qunapplied
167 test2.patch
168 test2.patch
168 % qpush/qpop with index
169 % qpush/qpop with index
169 applying test2.patch
170 applying test2.patch
170 Now at: test2.patch
171 Now at: test2.patch
171 Now at: test.patch
172 Now at: test.patch
172 applying test1b.patch
173 applying test1b.patch
173 Now at: test1b.patch
174 Now at: test1b.patch
174 applying test2.patch
175 applying test2.patch
175 Now at: test2.patch
176 Now at: test2.patch
176 Now at: test1b.patch
177 Now at: test1b.patch
177 Now at: test.patch
178 Now at: test.patch
178 applying test1b.patch
179 applying test1b.patch
179 applying test2.patch
180 applying test2.patch
180 Now at: test2.patch
181 Now at: test2.patch
181 % push should succeed
182 % push should succeed
182 Patch queue now empty
183 Patch queue now empty
183 pushing to ../../k
184 pushing to ../../k
184 searching for changes
185 searching for changes
185 adding changesets
186 adding changesets
186 adding manifests
187 adding manifests
187 adding file changes
188 adding file changes
188 added 1 changesets with 1 changes to 1 files
189 added 1 changesets with 1 changes to 1 files
189 % qpush/qpop error codes
190 % qpush/qpop error codes
190 applying test.patch
191 applying test.patch
191 applying test1b.patch
192 applying test1b.patch
192 applying test2.patch
193 applying test2.patch
193 Now at: test2.patch
194 Now at: test2.patch
194 % pops all patches and succeeds
195 % pops all patches and succeeds
195 Patch queue now empty
196 Patch queue now empty
196 qpop -a succeeds
197 qpop -a succeeds
197 % does nothing and succeeds
198 % does nothing and succeeds
198 no patches applied
199 no patches applied
199 qpop -a succeeds
200 qpop -a succeeds
200 % fails - nothing else to pop
201 % fails - nothing else to pop
201 no patches applied
202 no patches applied
202 qpop fails
203 qpop fails
203 % pushes a patch and succeeds
204 % pushes a patch and succeeds
204 applying test.patch
205 applying test.patch
205 Now at: test.patch
206 Now at: test.patch
206 qpush succeeds
207 qpush succeeds
207 % pops a patch and succeeds
208 % pops a patch and succeeds
208 Patch queue now empty
209 Patch queue now empty
209 qpop succeeds
210 qpop succeeds
210 % pushes up to test1b.patch and succeeds
211 % pushes up to test1b.patch and succeeds
211 applying test.patch
212 applying test.patch
212 applying test1b.patch
213 applying test1b.patch
213 Now at: test1b.patch
214 Now at: test1b.patch
214 qpush test1b.patch succeeds
215 qpush test1b.patch succeeds
215 % does nothing and succeeds
216 % does nothing and succeeds
216 qpush: test1b.patch is already at the top
217 qpush: test1b.patch is already at the top
217 qpush test1b.patch succeeds
218 qpush test1b.patch succeeds
218 % does nothing and succeeds
219 % does nothing and succeeds
219 qpop: test1b.patch is already at the top
220 qpop: test1b.patch is already at the top
220 qpop test1b.patch succeeds
221 qpop test1b.patch succeeds
221 % fails - can't push to this patch
222 % fails - can't push to this patch
222 abort: cannot push to a previous patch: test.patch
223 abort: cannot push to a previous patch: test.patch
223 qpush test.patch fails
224 qpush test.patch fails
224 % fails - can't pop to this patch
225 % fails - can't pop to this patch
225 abort: patch test2.patch is not applied
226 abort: patch test2.patch is not applied
226 qpop test2.patch fails
227 qpop test2.patch fails
227 % pops up to test.patch and succeeds
228 % pops up to test.patch and succeeds
228 Now at: test.patch
229 Now at: test.patch
229 qpop test.patch succeeds
230 qpop test.patch succeeds
230 % pushes all patches and succeeds
231 % pushes all patches and succeeds
231 applying test1b.patch
232 applying test1b.patch
232 applying test2.patch
233 applying test2.patch
233 Now at: test2.patch
234 Now at: test2.patch
234 qpush -a succeeds
235 qpush -a succeeds
235 % does nothing and succeeds
236 % does nothing and succeeds
236 all patches are currently applied
237 all patches are currently applied
237 qpush -a succeeds
238 qpush -a succeeds
238 % fails - nothing else to push
239 % fails - nothing else to push
239 patch series already fully applied
240 patch series already fully applied
240 qpush fails
241 qpush fails
241 % does nothing and succeeds
242 % does nothing and succeeds
242 all patches are currently applied
243 all patches are currently applied
243 qpush test2.patch succeeds
244 qpush test2.patch succeeds
244 % strip
245 % strip
245 adding x
246 adding x
246 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
247 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
247 saving bundle to
248 saving bundle to
248 adding changesets
249 adding changesets
249 adding manifests
250 adding manifests
250 adding file changes
251 adding file changes
251 added 1 changesets with 1 changes to 1 files
252 added 1 changesets with 1 changes to 1 files
252 (run 'hg update' to get a working copy)
253 (run 'hg update' to get a working copy)
253 % strip with local changes, should complain
254 % strip with local changes, should complain
254 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
255 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
255 abort: local changes found
256 abort: local changes found
256 % --force strip with local changes
257 % --force strip with local changes
257 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
258 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
258 saving bundle to
259 saving bundle to
259 % cd b; hg qrefresh
260 % cd b; hg qrefresh
260 adding a
261 adding a
261 foo
262 foo
262
263
263 diff -r cb9a9f314b8b a
264 diff -r cb9a9f314b8b a
264 --- a/a
265 --- a/a
265 +++ b/a
266 +++ b/a
266 @@ -1,1 +1,2 @@
267 @@ -1,1 +1,2 @@
267 a
268 a
268 +a
269 +a
269 diff -r cb9a9f314b8b b/f
270 diff -r cb9a9f314b8b b/f
270 --- /dev/null
271 --- /dev/null
271 +++ b/b/f
272 +++ b/b/f
272 @@ -0,0 +1,1 @@
273 @@ -0,0 +1,1 @@
273 +f
274 +f
274 % hg qrefresh .
275 % hg qrefresh .
275 foo
276 foo
276
277
277 diff -r cb9a9f314b8b b/f
278 diff -r cb9a9f314b8b b/f
278 --- /dev/null
279 --- /dev/null
279 +++ b/b/f
280 +++ b/b/f
280 @@ -0,0 +1,1 @@
281 @@ -0,0 +1,1 @@
281 +f
282 +f
282 M a
283 M a
283 % qpush failure
284 % qpush failure
284 Patch queue now empty
285 Patch queue now empty
285 applying foo
286 applying foo
286 applying bar
287 applying bar
287 file foo already exists
288 file foo already exists
288 1 out of 1 hunk FAILED -- saving rejects to file foo.rej
289 1 out of 1 hunk FAILED -- saving rejects to file foo.rej
289 patch failed, unable to continue (try -v)
290 patch failed, unable to continue (try -v)
290 patch failed, rejects left in working dir
291 patch failed, rejects left in working dir
291 Errors during apply, please fix and refresh bar
292 Errors during apply, please fix and refresh bar
292 ? foo
293 ? foo
293 ? foo.rej
294 ? foo.rej
294 % mq tags
295 % mq tags
295 0 qparent
296 0 qparent
296 1 qbase foo
297 1 qbase foo
297 2 qtip bar tip
298 2 qtip bar tip
298 % bad node in status
299 % bad node in status
299 Now at: foo
300 Now at: foo
300 changeset: 0:cb9a9f314b8b
301 changeset: 0:cb9a9f314b8b
301 mq status file refers to unknown node
302 mq status file refers to unknown node
302 tag: tip
303 tag: tip
303 user: test
304 user: test
304 date: Thu Jan 01 00:00:00 1970 +0000
305 date: Thu Jan 01 00:00:00 1970 +0000
305 summary: a
306 summary: a
306
307
307 mq status file refers to unknown node
308 mq status file refers to unknown node
308 default 0:cb9a9f314b8b
309 default 0:cb9a9f314b8b
309 abort: working directory revision is not qtip
310 abort: working directory revision is not qtip
310 new file
311 new file
311
312
312 diff --git a/new b/new
313 diff --git a/new b/new
313 new file mode 100755
314 new file mode 100755
314 --- /dev/null
315 --- /dev/null
315 +++ b/new
316 +++ b/new
316 @@ -0,0 +1,1 @@
317 @@ -0,0 +1,1 @@
317 +foo
318 +foo
318 copy file
319 copy file
319
320
320 diff --git a/new b/copy
321 diff --git a/new b/copy
321 copy from new
322 copy from new
322 copy to copy
323 copy to copy
323 Now at: new
324 Now at: new
324 applying copy
325 applying copy
325 Now at: copy
326 Now at: copy
326 diff --git a/new b/copy
327 diff --git a/new b/copy
327 copy from new
328 copy from new
328 copy to copy
329 copy to copy
329 diff --git a/new b/copy
330 diff --git a/new b/copy
330 copy from new
331 copy from new
331 copy to copy
332 copy to copy
332 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
333 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
333 created new head
334 created new head
334 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
335 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
335 adding branch
336 adding branch
336 adding changesets
337 adding changesets
337 adding manifests
338 adding manifests
338 adding file changes
339 adding file changes
339 added 1 changesets with 1 changes to 1 files
340 added 1 changesets with 1 changes to 1 files
340 Patch queue now empty
341 Patch queue now empty
341 (working directory not at tip)
342 (working directory not at tip)
342 applying bar
343 applying bar
343 Now at: bar
344 Now at: bar
344 diff --git a/bar b/bar
345 diff --git a/bar b/bar
345 new file mode 100644
346 new file mode 100644
346 --- /dev/null
347 --- /dev/null
347 +++ b/bar
348 +++ b/bar
348 @@ -0,0 +1,1 @@
349 @@ -0,0 +1,1 @@
349 +bar
350 +bar
350 diff --git a/foo b/baz
351 diff --git a/foo b/baz
351 rename from foo
352 rename from foo
352 rename to baz
353 rename to baz
353 2 baz (foo)
354 2 baz (foo)
354 diff --git a/bar b/bar
355 diff --git a/bar b/bar
355 new file mode 100644
356 new file mode 100644
356 --- /dev/null
357 --- /dev/null
357 +++ b/bar
358 +++ b/bar
358 @@ -0,0 +1,1 @@
359 @@ -0,0 +1,1 @@
359 +bar
360 +bar
360 diff --git a/foo b/baz
361 diff --git a/foo b/baz
361 rename from foo
362 rename from foo
362 rename to baz
363 rename to baz
363 2 baz (foo)
364 2 baz (foo)
364 diff --git a/bar b/bar
365 diff --git a/bar b/bar
365 diff --git a/foo b/baz
366 diff --git a/foo b/baz
366
367
367 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
368 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
368 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
369 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
369 adding branch
370 adding branch
370 adding changesets
371 adding changesets
371 adding manifests
372 adding manifests
372 adding file changes
373 adding file changes
373 added 1 changesets with 1 changes to 1 files
374 added 1 changesets with 1 changes to 1 files
374 Patch queue now empty
375 Patch queue now empty
375 (working directory not at tip)
376 (working directory not at tip)
376 applying bar
377 applying bar
377 Now at: bar
378 Now at: bar
378 diff --git a/foo b/bleh
379 diff --git a/foo b/bleh
379 rename from foo
380 rename from foo
380 rename to bleh
381 rename to bleh
381 diff --git a/quux b/quux
382 diff --git a/quux b/quux
382 new file mode 100644
383 new file mode 100644
383 --- /dev/null
384 --- /dev/null
384 +++ b/quux
385 +++ b/quux
385 @@ -0,0 +1,1 @@
386 @@ -0,0 +1,1 @@
386 +bar
387 +bar
387 3 bleh (foo)
388 3 bleh (foo)
388 diff --git a/foo b/barney
389 diff --git a/foo b/barney
389 rename from foo
390 rename from foo
390 rename to barney
391 rename to barney
391 diff --git a/fred b/fred
392 diff --git a/fred b/fred
392 new file mode 100644
393 new file mode 100644
393 --- /dev/null
394 --- /dev/null
394 +++ b/fred
395 +++ b/fred
395 @@ -0,0 +1,1 @@
396 @@ -0,0 +1,1 @@
396 +bar
397 +bar
397 3 barney (foo)
398 3 barney (foo)
398 % refresh omitting an added file
399 % refresh omitting an added file
399 C newfile
400 C newfile
400 A newfile
401 A newfile
401 Now at: bar
402 Now at: bar
402 % create a git patch
403 % create a git patch
403 diff --git a/alexander b/alexander
404 diff --git a/alexander b/alexander
404 % create a git binary patch
405 % create a git binary patch
405 8ba2a2f3e77b55d03051ff9c24ad65e7 bucephalus
406 8ba2a2f3e77b55d03051ff9c24ad65e7 bucephalus
406 diff --git a/bucephalus b/bucephalus
407 diff --git a/bucephalus b/bucephalus
407 % check binary patches can be popped and pushed
408 % check binary patches can be popped and pushed
408 Now at: addalexander
409 Now at: addalexander
409 applying addbucephalus
410 applying addbucephalus
410 Now at: addbucephalus
411 Now at: addbucephalus
411 8ba2a2f3e77b55d03051ff9c24ad65e7 bucephalus
412 8ba2a2f3e77b55d03051ff9c24ad65e7 bucephalus
412 % strip again
413 % strip again
413 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
414 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
414 created new head
415 created new head
415 merging foo
416 merging foo
416 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
417 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
417 (branch merge, don't forget to commit)
418 (branch merge, don't forget to commit)
418 changeset: 3:99615015637b
419 changeset: 3:99615015637b
419 tag: tip
420 tag: tip
420 parent: 2:20cbbe65cff7
421 parent: 2:20cbbe65cff7
421 parent: 1:d2871fc282d4
422 parent: 1:d2871fc282d4
422 user: test
423 user: test
423 date: Thu Jan 01 00:00:00 1970 +0000
424 date: Thu Jan 01 00:00:00 1970 +0000
424 summary: merge
425 summary: merge
425
426
426 changeset: 2:20cbbe65cff7
427 changeset: 2:20cbbe65cff7
427 parent: 0:53245c60e682
428 parent: 0:53245c60e682
428 user: test
429 user: test
429 date: Thu Jan 01 00:00:00 1970 +0000
430 date: Thu Jan 01 00:00:00 1970 +0000
430 summary: change foo 2
431 summary: change foo 2
431
432
432 changeset: 1:d2871fc282d4
433 changeset: 1:d2871fc282d4
433 user: test
434 user: test
434 date: Thu Jan 01 00:00:00 1970 +0000
435 date: Thu Jan 01 00:00:00 1970 +0000
435 summary: change foo 1
436 summary: change foo 1
436
437
437 changeset: 0:53245c60e682
438 changeset: 0:53245c60e682
438 user: test
439 user: test
439 date: Thu Jan 01 00:00:00 1970 +0000
440 date: Thu Jan 01 00:00:00 1970 +0000
440 summary: add foo
441 summary: add foo
441
442
442 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
443 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
443 saving bundle to
444 saving bundle to
444 saving bundle to
445 saving bundle to
445 adding branch
446 adding branch
446 adding changesets
447 adding changesets
447 adding manifests
448 adding manifests
448 adding file changes
449 adding file changes
449 added 1 changesets with 1 changes to 1 files
450 added 1 changesets with 1 changes to 1 files
450 changeset: 1:20cbbe65cff7
451 changeset: 1:20cbbe65cff7
451 tag: tip
452 tag: tip
452 user: test
453 user: test
453 date: Thu Jan 01 00:00:00 1970 +0000
454 date: Thu Jan 01 00:00:00 1970 +0000
454 summary: change foo 2
455 summary: change foo 2
455
456
456 changeset: 0:53245c60e682
457 changeset: 0:53245c60e682
457 user: test
458 user: test
458 date: Thu Jan 01 00:00:00 1970 +0000
459 date: Thu Jan 01 00:00:00 1970 +0000
459 summary: add foo
460 summary: add foo
460
461
461 % qclone
462 % qclone
462 abort: versioned patch repository not found (see qinit -c)
463 abort: versioned patch repository not found (see qinit -c)
463 adding .hg/patches/patch1
464 adding .hg/patches/patch1
464 main repo:
465 main repo:
465 rev 1: change foo
466 rev 1: change foo
466 rev 0: add foo
467 rev 0: add foo
467 patch repo:
468 patch repo:
468 rev 0: checkpoint
469 rev 0: checkpoint
469 updating working directory
470 updating working directory
470 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
471 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
471 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
472 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
472 main repo:
473 main repo:
473 rev 0: add foo
474 rev 0: add foo
474 patch repo:
475 patch repo:
475 rev 0: checkpoint
476 rev 0: checkpoint
476 Patch queue now empty
477 Patch queue now empty
477 main repo:
478 main repo:
478 rev 0: add foo
479 rev 0: add foo
479 patch repo:
480 patch repo:
480 rev 0: checkpoint
481 rev 0: checkpoint
481 updating working directory
482 updating working directory
482 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
483 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
483 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
484 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
484 main repo:
485 main repo:
485 rev 0: add foo
486 rev 0: add foo
486 patch repo:
487 patch repo:
487 rev 0: checkpoint
488 rev 0: checkpoint
488 % test applying on an empty file (issue 1033)
489 % test applying on an empty file (issue 1033)
489 adding a
490 adding a
490 Patch queue now empty
491 Patch queue now empty
491 applying changea
492 applying changea
492 Now at: changea
493 Now at: changea
493 % test qpush with --force, issue1087
494 % test qpush with --force, issue1087
494 adding bye.txt
495 adding bye.txt
495 adding hello.txt
496 adding hello.txt
496 Patch queue now empty
497 Patch queue now empty
497 % qpush should fail, local changes
498 % qpush should fail, local changes
498 abort: local changes found, refresh first
499 abort: local changes found, refresh first
499 % apply force, should not discard changes with empty patch
500 % apply force, should not discard changes with empty patch
500 applying empty
501 applying empty
501 /usr/bin/patch: **** Only garbage was found in the patch input.
502 /usr/bin/patch: **** Only garbage was found in the patch input.
502 patch failed, unable to continue (try -v)
503 patch failed, unable to continue (try -v)
503 patch empty is empty
504 patch empty is empty
504 Now at: empty
505 Now at: empty
505 diff -r bf5fc3f07a0a hello.txt
506 diff -r bf5fc3f07a0a hello.txt
506 --- a/hello.txt
507 --- a/hello.txt
507 +++ b/hello.txt
508 +++ b/hello.txt
508 @@ -1,1 +1,2 @@
509 @@ -1,1 +1,2 @@
509 hello
510 hello
510 +world
511 +world
511 diff -r 9ecee4f634e3 hello.txt
512 diff -r 9ecee4f634e3 hello.txt
512 --- a/hello.txt
513 --- a/hello.txt
513 +++ b/hello.txt
514 +++ b/hello.txt
514 @@ -1,1 +1,2 @@
515 @@ -1,1 +1,2 @@
515 hello
516 hello
516 +world
517 +world
517 changeset: 1:bf5fc3f07a0a
518 changeset: 1:bf5fc3f07a0a
518 tag: qtip
519 tag: qtip
519 tag: tip
520 tag: tip
520 tag: empty
521 tag: empty
521 tag: qbase
522 tag: qbase
522 user: test
523 user: test
523 date: Thu Jan 01 00:00:00 1970 +0000
524 date: Thu Jan 01 00:00:00 1970 +0000
524 summary: imported patch empty
525 summary: imported patch empty
525
526
526
527
527 Patch queue now empty
528 Patch queue now empty
528 % qpush should fail, local changes
529 % qpush should fail, local changes
529 abort: local changes found, refresh first
530 abort: local changes found, refresh first
530 % apply force, should discard changes in hello, but not bye
531 % apply force, should discard changes in hello, but not bye
531 applying empty
532 applying empty
532 Now at: empty
533 Now at: empty
533 M bye.txt
534 M bye.txt
534 diff -r ba252371dbc1 bye.txt
535 diff -r ba252371dbc1 bye.txt
535 --- a/bye.txt
536 --- a/bye.txt
536 +++ b/bye.txt
537 +++ b/bye.txt
537 @@ -1,1 +1,2 @@
538 @@ -1,1 +1,2 @@
538 bye
539 bye
539 +universe
540 +universe
540 diff -r 9ecee4f634e3 bye.txt
541 diff -r 9ecee4f634e3 bye.txt
541 --- a/bye.txt
542 --- a/bye.txt
542 +++ b/bye.txt
543 +++ b/bye.txt
543 @@ -1,1 +1,2 @@
544 @@ -1,1 +1,2 @@
544 bye
545 bye
545 +universe
546 +universe
546 diff -r 9ecee4f634e3 hello.txt
547 diff -r 9ecee4f634e3 hello.txt
547 --- a/hello.txt
548 --- a/hello.txt
548 +++ b/hello.txt
549 +++ b/hello.txt
549 @@ -1,1 +1,3 @@
550 @@ -1,1 +1,3 @@
550 hello
551 hello
551 +world
552 +world
552 +universe
553 +universe
General Comments 0
You need to be logged in to leave comments. Login now