##// END OF EJS Templates
patch: use temporary files to handle intermediate copies...
Patrick Mezard -
r14452:ee574cfd default
parent child Browse files
Show More
@@ -1,691 +1,692 b''
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007-2010 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007-2010 Christian Ebert <blacktrash@gmx.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 #
7 #
8 # $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
14 # files (like LaTeX packages), that are mostly addressed to an
15 # audience not running a version control system.
15 # audience not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
18 # <http://mercurial.selenic.com/wiki/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 # Files to act upon/ignore are specified in the [keyword] section.
24 # Files to act upon/ignore are specified in the [keyword] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
26 #
26 #
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28
28
29 '''expand keywords in tracked files
29 '''expand keywords in tracked files
30
30
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 tracked text files selected by your configuration.
32 tracked text files selected by your configuration.
33
33
34 Keywords are only expanded in local repositories and not stored in the
34 Keywords are only expanded in local repositories and not stored in the
35 change history. The mechanism can be regarded as a convenience for the
35 change history. The mechanism can be regarded as a convenience for the
36 current user or for archive distribution.
36 current user or for archive distribution.
37
37
38 Keywords expand to the changeset data pertaining to the latest change
38 Keywords expand to the changeset data pertaining to the latest change
39 relative to the working directory parent of each file.
39 relative to the working directory parent of each file.
40
40
41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
42 sections of hgrc files.
42 sections of hgrc files.
43
43
44 Example::
44 Example::
45
45
46 [keyword]
46 [keyword]
47 # expand keywords in every python file except those matching "x*"
47 # expand keywords in every python file except those matching "x*"
48 **.py =
48 **.py =
49 x* = ignore
49 x* = ignore
50
50
51 [keywordset]
51 [keywordset]
52 # prefer svn- over cvs-like default keywordmaps
52 # prefer svn- over cvs-like default keywordmaps
53 svn = True
53 svn = True
54
54
55 .. note::
55 .. note::
56 The more specific you are in your filename patterns the less you
56 The more specific you are in your filename patterns the less you
57 lose speed in huge repositories.
57 lose speed in huge repositories.
58
58
59 For [keywordmaps] template mapping and expansion demonstration and
59 For [keywordmaps] template mapping and expansion demonstration and
60 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
60 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
61 available templates and filters.
61 available templates and filters.
62
62
63 Three additional date template filters are provided:
63 Three additional date template filters are provided:
64
64
65 :``utcdate``: "2006/09/18 15:13:13"
65 :``utcdate``: "2006/09/18 15:13:13"
66 :``svnutcdate``: "2006-09-18 15:13:13Z"
66 :``svnutcdate``: "2006-09-18 15:13:13Z"
67 :``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
67 :``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
68
68
69 The default template mappings (view with :hg:`kwdemo -d`) can be
69 The default template mappings (view with :hg:`kwdemo -d`) can be
70 replaced with customized keywords and templates. Again, run
70 replaced with customized keywords and templates. Again, run
71 :hg:`kwdemo` to control the results of your configuration changes.
71 :hg:`kwdemo` to control the results of your configuration changes.
72
72
73 Before changing/disabling active keywords, you must run :hg:`kwshrink`
73 Before changing/disabling active keywords, you must run :hg:`kwshrink`
74 to avoid storing expanded keywords in the change history.
74 to avoid storing expanded keywords in the change history.
75
75
76 To force expansion after enabling it, or a configuration change, run
76 To force expansion after enabling it, or a configuration change, run
77 :hg:`kwexpand`.
77 :hg:`kwexpand`.
78
78
79 Expansions spanning more than one line and incremental expansions,
79 Expansions spanning more than one line and incremental expansions,
80 like CVS' $Log$, are not supported. A keyword template map "Log =
80 like CVS' $Log$, are not supported. A keyword template map "Log =
81 {desc}" expands to the first line of the changeset description.
81 {desc}" expands to the first line of the changeset description.
82 '''
82 '''
83
83
84 from mercurial import commands, context, cmdutil, dispatch, filelog, extensions
84 from mercurial import commands, context, cmdutil, dispatch, filelog, extensions
85 from mercurial import localrepo, match, patch, templatefilters, templater, util
85 from mercurial import localrepo, match, patch, templatefilters, templater, util
86 from mercurial import scmutil
86 from mercurial import scmutil
87 from mercurial.hgweb import webcommands
87 from mercurial.hgweb import webcommands
88 from mercurial.i18n import _
88 from mercurial.i18n import _
89 import os, re, shutil, tempfile
89 import os, re, shutil, tempfile
90
90
91 commands.optionalrepo += ' kwdemo'
91 commands.optionalrepo += ' kwdemo'
92
92
93 cmdtable = {}
93 cmdtable = {}
94 command = cmdutil.command(cmdtable)
94 command = cmdutil.command(cmdtable)
95
95
96 # hg commands that do not act on keywords
96 # hg commands that do not act on keywords
97 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
97 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
98 ' outgoing push tip verify convert email glog')
98 ' outgoing push tip verify convert email glog')
99
99
100 # hg commands that trigger expansion only when writing to working dir,
100 # hg commands that trigger expansion only when writing to working dir,
101 # not when reading filelog, and unexpand when reading from working dir
101 # not when reading filelog, and unexpand when reading from working dir
102 restricted = 'merge kwexpand kwshrink record qrecord resolve transplant'
102 restricted = 'merge kwexpand kwshrink record qrecord resolve transplant'
103
103
104 # names of extensions using dorecord
104 # names of extensions using dorecord
105 recordextensions = 'record'
105 recordextensions = 'record'
106
106
107 colortable = {
107 colortable = {
108 'kwfiles.enabled': 'green bold',
108 'kwfiles.enabled': 'green bold',
109 'kwfiles.deleted': 'cyan bold underline',
109 'kwfiles.deleted': 'cyan bold underline',
110 'kwfiles.enabledunknown': 'green',
110 'kwfiles.enabledunknown': 'green',
111 'kwfiles.ignored': 'bold',
111 'kwfiles.ignored': 'bold',
112 'kwfiles.ignoredunknown': 'none'
112 'kwfiles.ignoredunknown': 'none'
113 }
113 }
114
114
115 # date like in cvs' $Date
115 # date like in cvs' $Date
116 def utcdate(text):
116 def utcdate(text):
117 ''':utcdate: Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
117 ''':utcdate: Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
118 '''
118 '''
119 return util.datestr((text[0], 0), '%Y/%m/%d %H:%M:%S')
119 return util.datestr((text[0], 0), '%Y/%m/%d %H:%M:%S')
120 # date like in svn's $Date
120 # date like in svn's $Date
121 def svnisodate(text):
121 def svnisodate(text):
122 ''':svnisodate: Date. Returns a date in this format: "2009-08-18 13:00:13
122 ''':svnisodate: Date. Returns a date in this format: "2009-08-18 13:00:13
123 +0200 (Tue, 18 Aug 2009)".
123 +0200 (Tue, 18 Aug 2009)".
124 '''
124 '''
125 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
125 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
126 # date like in svn's $Id
126 # date like in svn's $Id
127 def svnutcdate(text):
127 def svnutcdate(text):
128 ''':svnutcdate: Date. Returns a UTC-date in this format: "2009-08-18
128 ''':svnutcdate: Date. Returns a UTC-date in this format: "2009-08-18
129 11:00:13Z".
129 11:00:13Z".
130 '''
130 '''
131 return util.datestr((text[0], 0), '%Y-%m-%d %H:%M:%SZ')
131 return util.datestr((text[0], 0), '%Y-%m-%d %H:%M:%SZ')
132
132
133 templatefilters.filters.update({'utcdate': utcdate,
133 templatefilters.filters.update({'utcdate': utcdate,
134 'svnisodate': svnisodate,
134 'svnisodate': svnisodate,
135 'svnutcdate': svnutcdate})
135 'svnutcdate': svnutcdate})
136
136
137 # make keyword tools accessible
137 # make keyword tools accessible
138 kwtools = {'templater': None, 'hgcmd': ''}
138 kwtools = {'templater': None, 'hgcmd': ''}
139
139
140 def _defaultkwmaps(ui):
140 def _defaultkwmaps(ui):
141 '''Returns default keywordmaps according to keywordset configuration.'''
141 '''Returns default keywordmaps according to keywordset configuration.'''
142 templates = {
142 templates = {
143 'Revision': '{node|short}',
143 'Revision': '{node|short}',
144 'Author': '{author|user}',
144 'Author': '{author|user}',
145 }
145 }
146 kwsets = ({
146 kwsets = ({
147 'Date': '{date|utcdate}',
147 'Date': '{date|utcdate}',
148 'RCSfile': '{file|basename},v',
148 'RCSfile': '{file|basename},v',
149 'RCSFile': '{file|basename},v', # kept for backwards compatibility
149 'RCSFile': '{file|basename},v', # kept for backwards compatibility
150 # with hg-keyword
150 # with hg-keyword
151 'Source': '{root}/{file},v',
151 'Source': '{root}/{file},v',
152 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
152 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
153 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
153 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
154 }, {
154 }, {
155 'Date': '{date|svnisodate}',
155 'Date': '{date|svnisodate}',
156 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
156 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
157 'LastChangedRevision': '{node|short}',
157 'LastChangedRevision': '{node|short}',
158 'LastChangedBy': '{author|user}',
158 'LastChangedBy': '{author|user}',
159 'LastChangedDate': '{date|svnisodate}',
159 'LastChangedDate': '{date|svnisodate}',
160 })
160 })
161 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
161 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
162 return templates
162 return templates
163
163
164 def _shrinktext(text, subfunc):
164 def _shrinktext(text, subfunc):
165 '''Helper for keyword expansion removal in text.
165 '''Helper for keyword expansion removal in text.
166 Depending on subfunc also returns number of substitutions.'''
166 Depending on subfunc also returns number of substitutions.'''
167 return subfunc(r'$\1$', text)
167 return subfunc(r'$\1$', text)
168
168
169 def _preselect(wstatus, changed):
169 def _preselect(wstatus, changed):
170 '''Retrieves modfied and added files from a working directory state
170 '''Retrieves modfied and added files from a working directory state
171 and returns the subset of each contained in given changed files
171 and returns the subset of each contained in given changed files
172 retrieved from a change context.'''
172 retrieved from a change context.'''
173 modified, added = wstatus[:2]
173 modified, added = wstatus[:2]
174 modified = [f for f in modified if f in changed]
174 modified = [f for f in modified if f in changed]
175 added = [f for f in added if f in changed]
175 added = [f for f in added if f in changed]
176 return modified, added
176 return modified, added
177
177
178
178
179 class kwtemplater(object):
179 class kwtemplater(object):
180 '''
180 '''
181 Sets up keyword templates, corresponding keyword regex, and
181 Sets up keyword templates, corresponding keyword regex, and
182 provides keyword substitution functions.
182 provides keyword substitution functions.
183 '''
183 '''
184
184
185 def __init__(self, ui, repo, inc, exc):
185 def __init__(self, ui, repo, inc, exc):
186 self.ui = ui
186 self.ui = ui
187 self.repo = repo
187 self.repo = repo
188 self.match = match.match(repo.root, '', [], inc, exc)
188 self.match = match.match(repo.root, '', [], inc, exc)
189 self.restrict = kwtools['hgcmd'] in restricted.split()
189 self.restrict = kwtools['hgcmd'] in restricted.split()
190 self.record = False
190 self.record = False
191
191
192 kwmaps = self.ui.configitems('keywordmaps')
192 kwmaps = self.ui.configitems('keywordmaps')
193 if kwmaps: # override default templates
193 if kwmaps: # override default templates
194 self.templates = dict((k, templater.parsestring(v, False))
194 self.templates = dict((k, templater.parsestring(v, False))
195 for k, v in kwmaps)
195 for k, v in kwmaps)
196 else:
196 else:
197 self.templates = _defaultkwmaps(self.ui)
197 self.templates = _defaultkwmaps(self.ui)
198
198
199 @util.propertycache
199 @util.propertycache
200 def escape(self):
200 def escape(self):
201 '''Returns bar-separated and escaped keywords.'''
201 '''Returns bar-separated and escaped keywords.'''
202 return '|'.join(map(re.escape, self.templates.keys()))
202 return '|'.join(map(re.escape, self.templates.keys()))
203
203
204 @util.propertycache
204 @util.propertycache
205 def rekw(self):
205 def rekw(self):
206 '''Returns regex for unexpanded keywords.'''
206 '''Returns regex for unexpanded keywords.'''
207 return re.compile(r'\$(%s)\$' % self.escape)
207 return re.compile(r'\$(%s)\$' % self.escape)
208
208
209 @util.propertycache
209 @util.propertycache
210 def rekwexp(self):
210 def rekwexp(self):
211 '''Returns regex for expanded keywords.'''
211 '''Returns regex for expanded keywords.'''
212 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
212 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
213
213
214 def substitute(self, data, path, ctx, subfunc):
214 def substitute(self, data, path, ctx, subfunc):
215 '''Replaces keywords in data with expanded template.'''
215 '''Replaces keywords in data with expanded template.'''
216 def kwsub(mobj):
216 def kwsub(mobj):
217 kw = mobj.group(1)
217 kw = mobj.group(1)
218 ct = cmdutil.changeset_templater(self.ui, self.repo,
218 ct = cmdutil.changeset_templater(self.ui, self.repo,
219 False, None, '', False)
219 False, None, '', False)
220 ct.use_template(self.templates[kw])
220 ct.use_template(self.templates[kw])
221 self.ui.pushbuffer()
221 self.ui.pushbuffer()
222 ct.show(ctx, root=self.repo.root, file=path)
222 ct.show(ctx, root=self.repo.root, file=path)
223 ekw = templatefilters.firstline(self.ui.popbuffer())
223 ekw = templatefilters.firstline(self.ui.popbuffer())
224 return '$%s: %s $' % (kw, ekw)
224 return '$%s: %s $' % (kw, ekw)
225 return subfunc(kwsub, data)
225 return subfunc(kwsub, data)
226
226
227 def linkctx(self, path, fileid):
227 def linkctx(self, path, fileid):
228 '''Similar to filelog.linkrev, but returns a changectx.'''
228 '''Similar to filelog.linkrev, but returns a changectx.'''
229 return self.repo.filectx(path, fileid=fileid).changectx()
229 return self.repo.filectx(path, fileid=fileid).changectx()
230
230
231 def expand(self, path, node, data):
231 def expand(self, path, node, data):
232 '''Returns data with keywords expanded.'''
232 '''Returns data with keywords expanded.'''
233 if not self.restrict and self.match(path) and not util.binary(data):
233 if not self.restrict and self.match(path) and not util.binary(data):
234 ctx = self.linkctx(path, node)
234 ctx = self.linkctx(path, node)
235 return self.substitute(data, path, ctx, self.rekw.sub)
235 return self.substitute(data, path, ctx, self.rekw.sub)
236 return data
236 return data
237
237
238 def iskwfile(self, cand, ctx):
238 def iskwfile(self, cand, ctx):
239 '''Returns subset of candidates which are configured for keyword
239 '''Returns subset of candidates which are configured for keyword
240 expansion are not symbolic links.'''
240 expansion are not symbolic links.'''
241 return [f for f in cand if self.match(f) and not 'l' in ctx.flags(f)]
241 return [f for f in cand if self.match(f) and not 'l' in ctx.flags(f)]
242
242
243 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
243 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
244 '''Overwrites selected files expanding/shrinking keywords.'''
244 '''Overwrites selected files expanding/shrinking keywords.'''
245 if self.restrict or lookup or self.record: # exclude kw_copy
245 if self.restrict or lookup or self.record: # exclude kw_copy
246 candidates = self.iskwfile(candidates, ctx)
246 candidates = self.iskwfile(candidates, ctx)
247 if not candidates:
247 if not candidates:
248 return
248 return
249 kwcmd = self.restrict and lookup # kwexpand/kwshrink
249 kwcmd = self.restrict and lookup # kwexpand/kwshrink
250 if self.restrict or expand and lookup:
250 if self.restrict or expand and lookup:
251 mf = ctx.manifest()
251 mf = ctx.manifest()
252 lctx = ctx
252 lctx = ctx
253 re_kw = (self.restrict or rekw) and self.rekw or self.rekwexp
253 re_kw = (self.restrict or rekw) and self.rekw or self.rekwexp
254 msg = (expand and _('overwriting %s expanding keywords\n')
254 msg = (expand and _('overwriting %s expanding keywords\n')
255 or _('overwriting %s shrinking keywords\n'))
255 or _('overwriting %s shrinking keywords\n'))
256 for f in candidates:
256 for f in candidates:
257 if self.restrict:
257 if self.restrict:
258 data = self.repo.file(f).read(mf[f])
258 data = self.repo.file(f).read(mf[f])
259 else:
259 else:
260 data = self.repo.wread(f)
260 data = self.repo.wread(f)
261 if util.binary(data):
261 if util.binary(data):
262 continue
262 continue
263 if expand:
263 if expand:
264 if lookup:
264 if lookup:
265 lctx = self.linkctx(f, mf[f])
265 lctx = self.linkctx(f, mf[f])
266 data, found = self.substitute(data, f, lctx, re_kw.subn)
266 data, found = self.substitute(data, f, lctx, re_kw.subn)
267 elif self.restrict:
267 elif self.restrict:
268 found = re_kw.search(data)
268 found = re_kw.search(data)
269 else:
269 else:
270 data, found = _shrinktext(data, re_kw.subn)
270 data, found = _shrinktext(data, re_kw.subn)
271 if found:
271 if found:
272 self.ui.note(msg % f)
272 self.ui.note(msg % f)
273 self.repo.wwrite(f, data, ctx.flags(f))
273 self.repo.wwrite(f, data, ctx.flags(f))
274 if kwcmd:
274 if kwcmd:
275 self.repo.dirstate.normal(f)
275 self.repo.dirstate.normal(f)
276 elif self.record:
276 elif self.record:
277 self.repo.dirstate.normallookup(f)
277 self.repo.dirstate.normallookup(f)
278
278
279 def shrink(self, fname, text):
279 def shrink(self, fname, text):
280 '''Returns text with all keyword substitutions removed.'''
280 '''Returns text with all keyword substitutions removed.'''
281 if self.match(fname) and not util.binary(text):
281 if self.match(fname) and not util.binary(text):
282 return _shrinktext(text, self.rekwexp.sub)
282 return _shrinktext(text, self.rekwexp.sub)
283 return text
283 return text
284
284
285 def shrinklines(self, fname, lines):
285 def shrinklines(self, fname, lines):
286 '''Returns lines with keyword substitutions removed.'''
286 '''Returns lines with keyword substitutions removed.'''
287 if self.match(fname):
287 if self.match(fname):
288 text = ''.join(lines)
288 text = ''.join(lines)
289 if not util.binary(text):
289 if not util.binary(text):
290 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
290 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
291 return lines
291 return lines
292
292
293 def wread(self, fname, data):
293 def wread(self, fname, data):
294 '''If in restricted mode returns data read from wdir with
294 '''If in restricted mode returns data read from wdir with
295 keyword substitutions removed.'''
295 keyword substitutions removed.'''
296 return self.restrict and self.shrink(fname, data) or data
296 return self.restrict and self.shrink(fname, data) or data
297
297
298 class kwfilelog(filelog.filelog):
298 class kwfilelog(filelog.filelog):
299 '''
299 '''
300 Subclass of filelog to hook into its read, add, cmp methods.
300 Subclass of filelog to hook into its read, add, cmp methods.
301 Keywords are "stored" unexpanded, and processed on reading.
301 Keywords are "stored" unexpanded, and processed on reading.
302 '''
302 '''
303 def __init__(self, opener, kwt, path):
303 def __init__(self, opener, kwt, path):
304 super(kwfilelog, self).__init__(opener, path)
304 super(kwfilelog, self).__init__(opener, path)
305 self.kwt = kwt
305 self.kwt = kwt
306 self.path = path
306 self.path = path
307
307
308 def read(self, node):
308 def read(self, node):
309 '''Expands keywords when reading filelog.'''
309 '''Expands keywords when reading filelog.'''
310 data = super(kwfilelog, self).read(node)
310 data = super(kwfilelog, self).read(node)
311 if self.renamed(node):
311 if self.renamed(node):
312 return data
312 return data
313 return self.kwt.expand(self.path, node, data)
313 return self.kwt.expand(self.path, node, data)
314
314
315 def add(self, text, meta, tr, link, p1=None, p2=None):
315 def add(self, text, meta, tr, link, p1=None, p2=None):
316 '''Removes keyword substitutions when adding to filelog.'''
316 '''Removes keyword substitutions when adding to filelog.'''
317 text = self.kwt.shrink(self.path, text)
317 text = self.kwt.shrink(self.path, text)
318 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
318 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
319
319
320 def cmp(self, node, text):
320 def cmp(self, node, text):
321 '''Removes keyword substitutions for comparison.'''
321 '''Removes keyword substitutions for comparison.'''
322 text = self.kwt.shrink(self.path, text)
322 text = self.kwt.shrink(self.path, text)
323 return super(kwfilelog, self).cmp(node, text)
323 return super(kwfilelog, self).cmp(node, text)
324
324
325 def _status(ui, repo, kwt, *pats, **opts):
325 def _status(ui, repo, kwt, *pats, **opts):
326 '''Bails out if [keyword] configuration is not active.
326 '''Bails out if [keyword] configuration is not active.
327 Returns status of working directory.'''
327 Returns status of working directory.'''
328 if kwt:
328 if kwt:
329 return repo.status(match=scmutil.match(repo, pats, opts), clean=True,
329 return repo.status(match=scmutil.match(repo, pats, opts), clean=True,
330 unknown=opts.get('unknown') or opts.get('all'))
330 unknown=opts.get('unknown') or opts.get('all'))
331 if ui.configitems('keyword'):
331 if ui.configitems('keyword'):
332 raise util.Abort(_('[keyword] patterns cannot match'))
332 raise util.Abort(_('[keyword] patterns cannot match'))
333 raise util.Abort(_('no [keyword] patterns configured'))
333 raise util.Abort(_('no [keyword] patterns configured'))
334
334
335 def _kwfwrite(ui, repo, expand, *pats, **opts):
335 def _kwfwrite(ui, repo, expand, *pats, **opts):
336 '''Selects files and passes them to kwtemplater.overwrite.'''
336 '''Selects files and passes them to kwtemplater.overwrite.'''
337 wctx = repo[None]
337 wctx = repo[None]
338 if len(wctx.parents()) > 1:
338 if len(wctx.parents()) > 1:
339 raise util.Abort(_('outstanding uncommitted merge'))
339 raise util.Abort(_('outstanding uncommitted merge'))
340 kwt = kwtools['templater']
340 kwt = kwtools['templater']
341 wlock = repo.wlock()
341 wlock = repo.wlock()
342 try:
342 try:
343 status = _status(ui, repo, kwt, *pats, **opts)
343 status = _status(ui, repo, kwt, *pats, **opts)
344 modified, added, removed, deleted, unknown, ignored, clean = status
344 modified, added, removed, deleted, unknown, ignored, clean = status
345 if modified or added or removed or deleted:
345 if modified or added or removed or deleted:
346 raise util.Abort(_('outstanding uncommitted changes'))
346 raise util.Abort(_('outstanding uncommitted changes'))
347 kwt.overwrite(wctx, clean, True, expand)
347 kwt.overwrite(wctx, clean, True, expand)
348 finally:
348 finally:
349 wlock.release()
349 wlock.release()
350
350
351 @command('kwdemo',
351 @command('kwdemo',
352 [('d', 'default', None, _('show default keyword template maps')),
352 [('d', 'default', None, _('show default keyword template maps')),
353 ('f', 'rcfile', '',
353 ('f', 'rcfile', '',
354 _('read maps from rcfile'), _('FILE'))],
354 _('read maps from rcfile'), _('FILE'))],
355 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'))
355 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'))
356 def demo(ui, repo, *args, **opts):
356 def demo(ui, repo, *args, **opts):
357 '''print [keywordmaps] configuration and an expansion example
357 '''print [keywordmaps] configuration and an expansion example
358
358
359 Show current, custom, or default keyword template maps and their
359 Show current, custom, or default keyword template maps and their
360 expansions.
360 expansions.
361
361
362 Extend the current configuration by specifying maps as arguments
362 Extend the current configuration by specifying maps as arguments
363 and using -f/--rcfile to source an external hgrc file.
363 and using -f/--rcfile to source an external hgrc file.
364
364
365 Use -d/--default to disable current configuration.
365 Use -d/--default to disable current configuration.
366
366
367 See :hg:`help templates` for information on templates and filters.
367 See :hg:`help templates` for information on templates and filters.
368 '''
368 '''
369 def demoitems(section, items):
369 def demoitems(section, items):
370 ui.write('[%s]\n' % section)
370 ui.write('[%s]\n' % section)
371 for k, v in sorted(items):
371 for k, v in sorted(items):
372 ui.write('%s = %s\n' % (k, v))
372 ui.write('%s = %s\n' % (k, v))
373
373
374 fn = 'demo.txt'
374 fn = 'demo.txt'
375 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
375 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
376 ui.note(_('creating temporary repository at %s\n') % tmpdir)
376 ui.note(_('creating temporary repository at %s\n') % tmpdir)
377 repo = localrepo.localrepository(ui, tmpdir, True)
377 repo = localrepo.localrepository(ui, tmpdir, True)
378 ui.setconfig('keyword', fn, '')
378 ui.setconfig('keyword', fn, '')
379 svn = ui.configbool('keywordset', 'svn')
379 svn = ui.configbool('keywordset', 'svn')
380 # explicitly set keywordset for demo output
380 # explicitly set keywordset for demo output
381 ui.setconfig('keywordset', 'svn', svn)
381 ui.setconfig('keywordset', 'svn', svn)
382
382
383 uikwmaps = ui.configitems('keywordmaps')
383 uikwmaps = ui.configitems('keywordmaps')
384 if args or opts.get('rcfile'):
384 if args or opts.get('rcfile'):
385 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
385 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
386 if uikwmaps:
386 if uikwmaps:
387 ui.status(_('\textending current template maps\n'))
387 ui.status(_('\textending current template maps\n'))
388 if opts.get('default') or not uikwmaps:
388 if opts.get('default') or not uikwmaps:
389 if svn:
389 if svn:
390 ui.status(_('\toverriding default svn keywordset\n'))
390 ui.status(_('\toverriding default svn keywordset\n'))
391 else:
391 else:
392 ui.status(_('\toverriding default cvs keywordset\n'))
392 ui.status(_('\toverriding default cvs keywordset\n'))
393 if opts.get('rcfile'):
393 if opts.get('rcfile'):
394 ui.readconfig(opts.get('rcfile'))
394 ui.readconfig(opts.get('rcfile'))
395 if args:
395 if args:
396 # simulate hgrc parsing
396 # simulate hgrc parsing
397 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
397 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
398 fp = repo.opener('hgrc', 'w')
398 fp = repo.opener('hgrc', 'w')
399 fp.writelines(rcmaps)
399 fp.writelines(rcmaps)
400 fp.close()
400 fp.close()
401 ui.readconfig(repo.join('hgrc'))
401 ui.readconfig(repo.join('hgrc'))
402 kwmaps = dict(ui.configitems('keywordmaps'))
402 kwmaps = dict(ui.configitems('keywordmaps'))
403 elif opts.get('default'):
403 elif opts.get('default'):
404 if svn:
404 if svn:
405 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
405 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
406 else:
406 else:
407 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
407 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
408 kwmaps = _defaultkwmaps(ui)
408 kwmaps = _defaultkwmaps(ui)
409 if uikwmaps:
409 if uikwmaps:
410 ui.status(_('\tdisabling current template maps\n'))
410 ui.status(_('\tdisabling current template maps\n'))
411 for k, v in kwmaps.iteritems():
411 for k, v in kwmaps.iteritems():
412 ui.setconfig('keywordmaps', k, v)
412 ui.setconfig('keywordmaps', k, v)
413 else:
413 else:
414 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
414 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
415 kwmaps = dict(uikwmaps) or _defaultkwmaps(ui)
415 kwmaps = dict(uikwmaps) or _defaultkwmaps(ui)
416
416
417 uisetup(ui)
417 uisetup(ui)
418 reposetup(ui, repo)
418 reposetup(ui, repo)
419 ui.write('[extensions]\nkeyword =\n')
419 ui.write('[extensions]\nkeyword =\n')
420 demoitems('keyword', ui.configitems('keyword'))
420 demoitems('keyword', ui.configitems('keyword'))
421 demoitems('keywordset', ui.configitems('keywordset'))
421 demoitems('keywordset', ui.configitems('keywordset'))
422 demoitems('keywordmaps', kwmaps.iteritems())
422 demoitems('keywordmaps', kwmaps.iteritems())
423 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
423 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
424 repo.wopener.write(fn, keywords)
424 repo.wopener.write(fn, keywords)
425 repo[None].add([fn])
425 repo[None].add([fn])
426 ui.note(_('\nkeywords written to %s:\n') % fn)
426 ui.note(_('\nkeywords written to %s:\n') % fn)
427 ui.note(keywords)
427 ui.note(keywords)
428 repo.dirstate.setbranch('demobranch')
428 repo.dirstate.setbranch('demobranch')
429 for name, cmd in ui.configitems('hooks'):
429 for name, cmd in ui.configitems('hooks'):
430 if name.split('.', 1)[0].find('commit') > -1:
430 if name.split('.', 1)[0].find('commit') > -1:
431 repo.ui.setconfig('hooks', name, '')
431 repo.ui.setconfig('hooks', name, '')
432 msg = _('hg keyword configuration and expansion example')
432 msg = _('hg keyword configuration and expansion example')
433 ui.note("hg ci -m '%s'\n" % msg)
433 ui.note("hg ci -m '%s'\n" % msg)
434 repo.commit(text=msg)
434 repo.commit(text=msg)
435 ui.status(_('\n\tkeywords expanded\n'))
435 ui.status(_('\n\tkeywords expanded\n'))
436 ui.write(repo.wread(fn))
436 ui.write(repo.wread(fn))
437 shutil.rmtree(tmpdir, ignore_errors=True)
437 shutil.rmtree(tmpdir, ignore_errors=True)
438
438
439 @command('kwexpand', commands.walkopts, _('hg kwexpand [OPTION]... [FILE]...'))
439 @command('kwexpand', commands.walkopts, _('hg kwexpand [OPTION]... [FILE]...'))
440 def expand(ui, repo, *pats, **opts):
440 def expand(ui, repo, *pats, **opts):
441 '''expand keywords in the working directory
441 '''expand keywords in the working directory
442
442
443 Run after (re)enabling keyword expansion.
443 Run after (re)enabling keyword expansion.
444
444
445 kwexpand refuses to run if given files contain local changes.
445 kwexpand refuses to run if given files contain local changes.
446 '''
446 '''
447 # 3rd argument sets expansion to True
447 # 3rd argument sets expansion to True
448 _kwfwrite(ui, repo, True, *pats, **opts)
448 _kwfwrite(ui, repo, True, *pats, **opts)
449
449
450 @command('kwfiles',
450 @command('kwfiles',
451 [('A', 'all', None, _('show keyword status flags of all files')),
451 [('A', 'all', None, _('show keyword status flags of all files')),
452 ('i', 'ignore', None, _('show files excluded from expansion')),
452 ('i', 'ignore', None, _('show files excluded from expansion')),
453 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
453 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
454 ] + commands.walkopts,
454 ] + commands.walkopts,
455 _('hg kwfiles [OPTION]... [FILE]...'))
455 _('hg kwfiles [OPTION]... [FILE]...'))
456 def files(ui, repo, *pats, **opts):
456 def files(ui, repo, *pats, **opts):
457 '''show files configured for keyword expansion
457 '''show files configured for keyword expansion
458
458
459 List which files in the working directory are matched by the
459 List which files in the working directory are matched by the
460 [keyword] configuration patterns.
460 [keyword] configuration patterns.
461
461
462 Useful to prevent inadvertent keyword expansion and to speed up
462 Useful to prevent inadvertent keyword expansion and to speed up
463 execution by including only files that are actual candidates for
463 execution by including only files that are actual candidates for
464 expansion.
464 expansion.
465
465
466 See :hg:`help keyword` on how to construct patterns both for
466 See :hg:`help keyword` on how to construct patterns both for
467 inclusion and exclusion of files.
467 inclusion and exclusion of files.
468
468
469 With -A/--all and -v/--verbose the codes used to show the status
469 With -A/--all and -v/--verbose the codes used to show the status
470 of files are::
470 of files are::
471
471
472 K = keyword expansion candidate
472 K = keyword expansion candidate
473 k = keyword expansion candidate (not tracked)
473 k = keyword expansion candidate (not tracked)
474 I = ignored
474 I = ignored
475 i = ignored (not tracked)
475 i = ignored (not tracked)
476 '''
476 '''
477 kwt = kwtools['templater']
477 kwt = kwtools['templater']
478 status = _status(ui, repo, kwt, *pats, **opts)
478 status = _status(ui, repo, kwt, *pats, **opts)
479 cwd = pats and repo.getcwd() or ''
479 cwd = pats and repo.getcwd() or ''
480 modified, added, removed, deleted, unknown, ignored, clean = status
480 modified, added, removed, deleted, unknown, ignored, clean = status
481 files = []
481 files = []
482 if not opts.get('unknown') or opts.get('all'):
482 if not opts.get('unknown') or opts.get('all'):
483 files = sorted(modified + added + clean)
483 files = sorted(modified + added + clean)
484 wctx = repo[None]
484 wctx = repo[None]
485 kwfiles = kwt.iskwfile(files, wctx)
485 kwfiles = kwt.iskwfile(files, wctx)
486 kwdeleted = kwt.iskwfile(deleted, wctx)
486 kwdeleted = kwt.iskwfile(deleted, wctx)
487 kwunknown = kwt.iskwfile(unknown, wctx)
487 kwunknown = kwt.iskwfile(unknown, wctx)
488 if not opts.get('ignore') or opts.get('all'):
488 if not opts.get('ignore') or opts.get('all'):
489 showfiles = kwfiles, kwdeleted, kwunknown
489 showfiles = kwfiles, kwdeleted, kwunknown
490 else:
490 else:
491 showfiles = [], [], []
491 showfiles = [], [], []
492 if opts.get('all') or opts.get('ignore'):
492 if opts.get('all') or opts.get('ignore'):
493 showfiles += ([f for f in files if f not in kwfiles],
493 showfiles += ([f for f in files if f not in kwfiles],
494 [f for f in unknown if f not in kwunknown])
494 [f for f in unknown if f not in kwunknown])
495 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
495 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
496 kwstates = zip('K!kIi', showfiles, kwlabels)
496 kwstates = zip('K!kIi', showfiles, kwlabels)
497 for char, filenames, kwstate in kwstates:
497 for char, filenames, kwstate in kwstates:
498 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
498 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
499 for f in filenames:
499 for f in filenames:
500 ui.write(fmt % repo.pathto(f, cwd), label='kwfiles.' + kwstate)
500 ui.write(fmt % repo.pathto(f, cwd), label='kwfiles.' + kwstate)
501
501
502 @command('kwshrink', commands.walkopts, _('hg kwshrink [OPTION]... [FILE]...'))
502 @command('kwshrink', commands.walkopts, _('hg kwshrink [OPTION]... [FILE]...'))
503 def shrink(ui, repo, *pats, **opts):
503 def shrink(ui, repo, *pats, **opts):
504 '''revert expanded keywords in the working directory
504 '''revert expanded keywords in the working directory
505
505
506 Must be run before changing/disabling active keywords.
506 Must be run before changing/disabling active keywords.
507
507
508 kwshrink refuses to run if given files contain local changes.
508 kwshrink refuses to run if given files contain local changes.
509 '''
509 '''
510 # 3rd argument sets expansion to False
510 # 3rd argument sets expansion to False
511 _kwfwrite(ui, repo, False, *pats, **opts)
511 _kwfwrite(ui, repo, False, *pats, **opts)
512
512
513
513
514 def uisetup(ui):
514 def uisetup(ui):
515 ''' Monkeypatches dispatch._parse to retrieve user command.'''
515 ''' Monkeypatches dispatch._parse to retrieve user command.'''
516
516
517 def kwdispatch_parse(orig, ui, args):
517 def kwdispatch_parse(orig, ui, args):
518 '''Monkeypatch dispatch._parse to obtain running hg command.'''
518 '''Monkeypatch dispatch._parse to obtain running hg command.'''
519 cmd, func, args, options, cmdoptions = orig(ui, args)
519 cmd, func, args, options, cmdoptions = orig(ui, args)
520 kwtools['hgcmd'] = cmd
520 kwtools['hgcmd'] = cmd
521 return cmd, func, args, options, cmdoptions
521 return cmd, func, args, options, cmdoptions
522
522
523 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
523 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
524
524
525 def reposetup(ui, repo):
525 def reposetup(ui, repo):
526 '''Sets up repo as kwrepo for keyword substitution.
526 '''Sets up repo as kwrepo for keyword substitution.
527 Overrides file method to return kwfilelog instead of filelog
527 Overrides file method to return kwfilelog instead of filelog
528 if file matches user configuration.
528 if file matches user configuration.
529 Wraps commit to overwrite configured files with updated
529 Wraps commit to overwrite configured files with updated
530 keyword substitutions.
530 keyword substitutions.
531 Monkeypatches patch and webcommands.'''
531 Monkeypatches patch and webcommands.'''
532
532
533 try:
533 try:
534 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
534 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
535 or '.hg' in util.splitpath(repo.root)
535 or '.hg' in util.splitpath(repo.root)
536 or repo._url.startswith('bundle:')):
536 or repo._url.startswith('bundle:')):
537 return
537 return
538 except AttributeError:
538 except AttributeError:
539 pass
539 pass
540
540
541 inc, exc = [], ['.hg*']
541 inc, exc = [], ['.hg*']
542 for pat, opt in ui.configitems('keyword'):
542 for pat, opt in ui.configitems('keyword'):
543 if opt != 'ignore':
543 if opt != 'ignore':
544 inc.append(pat)
544 inc.append(pat)
545 else:
545 else:
546 exc.append(pat)
546 exc.append(pat)
547 if not inc:
547 if not inc:
548 return
548 return
549
549
550 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
550 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
551
551
552 class kwrepo(repo.__class__):
552 class kwrepo(repo.__class__):
553 def file(self, f):
553 def file(self, f):
554 if f[0] == '/':
554 if f[0] == '/':
555 f = f[1:]
555 f = f[1:]
556 return kwfilelog(self.sopener, kwt, f)
556 return kwfilelog(self.sopener, kwt, f)
557
557
558 def wread(self, filename):
558 def wread(self, filename):
559 data = super(kwrepo, self).wread(filename)
559 data = super(kwrepo, self).wread(filename)
560 return kwt.wread(filename, data)
560 return kwt.wread(filename, data)
561
561
562 def commit(self, *args, **opts):
562 def commit(self, *args, **opts):
563 # use custom commitctx for user commands
563 # use custom commitctx for user commands
564 # other extensions can still wrap repo.commitctx directly
564 # other extensions can still wrap repo.commitctx directly
565 self.commitctx = self.kwcommitctx
565 self.commitctx = self.kwcommitctx
566 try:
566 try:
567 return super(kwrepo, self).commit(*args, **opts)
567 return super(kwrepo, self).commit(*args, **opts)
568 finally:
568 finally:
569 del self.commitctx
569 del self.commitctx
570
570
571 def kwcommitctx(self, ctx, error=False):
571 def kwcommitctx(self, ctx, error=False):
572 n = super(kwrepo, self).commitctx(ctx, error)
572 n = super(kwrepo, self).commitctx(ctx, error)
573 # no lock needed, only called from repo.commit() which already locks
573 # no lock needed, only called from repo.commit() which already locks
574 if not kwt.record:
574 if not kwt.record:
575 restrict = kwt.restrict
575 restrict = kwt.restrict
576 kwt.restrict = True
576 kwt.restrict = True
577 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
577 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
578 False, True)
578 False, True)
579 kwt.restrict = restrict
579 kwt.restrict = restrict
580 return n
580 return n
581
581
582 def rollback(self, dryrun=False):
582 def rollback(self, dryrun=False):
583 wlock = self.wlock()
583 wlock = self.wlock()
584 try:
584 try:
585 if not dryrun:
585 if not dryrun:
586 changed = self['.'].files()
586 changed = self['.'].files()
587 ret = super(kwrepo, self).rollback(dryrun)
587 ret = super(kwrepo, self).rollback(dryrun)
588 if not dryrun:
588 if not dryrun:
589 ctx = self['.']
589 ctx = self['.']
590 modified, added = _preselect(self[None].status(), changed)
590 modified, added = _preselect(self[None].status(), changed)
591 kwt.overwrite(ctx, modified, True, True)
591 kwt.overwrite(ctx, modified, True, True)
592 kwt.overwrite(ctx, added, True, False)
592 kwt.overwrite(ctx, added, True, False)
593 return ret
593 return ret
594 finally:
594 finally:
595 wlock.release()
595 wlock.release()
596
596
597 # monkeypatches
597 # monkeypatches
598 def kwpatchfile_init(orig, self, ui, fname, backend, mode, create, remove,
598 def kwpatchfile_init(orig, self, ui, fname, backend, store, mode, create,
599 missing=False, eolmode=None):
599 remove, eolmode=None, copysource=None):
600 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
600 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
601 rejects or conflicts due to expanded keywords in working dir.'''
601 rejects or conflicts due to expanded keywords in working dir.'''
602 orig(self, ui, fname, backend, mode, create, remove, missing, eolmode)
602 orig(self, ui, fname, backend, store, mode, create, remove,
603 eolmode, copysource)
603 # shrink keywords read from working dir
604 # shrink keywords read from working dir
604 self.lines = kwt.shrinklines(self.fname, self.lines)
605 self.lines = kwt.shrinklines(self.fname, self.lines)
605
606
606 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
607 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
607 opts=None, prefix=''):
608 opts=None, prefix=''):
608 '''Monkeypatch patch.diff to avoid expansion.'''
609 '''Monkeypatch patch.diff to avoid expansion.'''
609 kwt.restrict = True
610 kwt.restrict = True
610 return orig(repo, node1, node2, match, changes, opts, prefix)
611 return orig(repo, node1, node2, match, changes, opts, prefix)
611
612
612 def kwweb_skip(orig, web, req, tmpl):
613 def kwweb_skip(orig, web, req, tmpl):
613 '''Wraps webcommands.x turning off keyword expansion.'''
614 '''Wraps webcommands.x turning off keyword expansion.'''
614 kwt.match = util.never
615 kwt.match = util.never
615 return orig(web, req, tmpl)
616 return orig(web, req, tmpl)
616
617
617 def kw_copy(orig, ui, repo, pats, opts, rename=False):
618 def kw_copy(orig, ui, repo, pats, opts, rename=False):
618 '''Wraps cmdutil.copy so that copy/rename destinations do not
619 '''Wraps cmdutil.copy so that copy/rename destinations do not
619 contain expanded keywords.
620 contain expanded keywords.
620 Note that the source of a regular file destination may also be a
621 Note that the source of a regular file destination may also be a
621 symlink:
622 symlink:
622 hg cp sym x -> x is symlink
623 hg cp sym x -> x is symlink
623 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
624 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
624 For the latter we have to follow the symlink to find out whether its
625 For the latter we have to follow the symlink to find out whether its
625 target is configured for expansion and we therefore must unexpand the
626 target is configured for expansion and we therefore must unexpand the
626 keywords in the destination.'''
627 keywords in the destination.'''
627 orig(ui, repo, pats, opts, rename)
628 orig(ui, repo, pats, opts, rename)
628 if opts.get('dry_run'):
629 if opts.get('dry_run'):
629 return
630 return
630 wctx = repo[None]
631 wctx = repo[None]
631 cwd = repo.getcwd()
632 cwd = repo.getcwd()
632
633
633 def haskwsource(dest):
634 def haskwsource(dest):
634 '''Returns true if dest is a regular file and configured for
635 '''Returns true if dest is a regular file and configured for
635 expansion or a symlink which points to a file configured for
636 expansion or a symlink which points to a file configured for
636 expansion. '''
637 expansion. '''
637 source = repo.dirstate.copied(dest)
638 source = repo.dirstate.copied(dest)
638 if 'l' in wctx.flags(source):
639 if 'l' in wctx.flags(source):
639 source = scmutil.canonpath(repo.root, cwd,
640 source = scmutil.canonpath(repo.root, cwd,
640 os.path.realpath(source))
641 os.path.realpath(source))
641 return kwt.match(source)
642 return kwt.match(source)
642
643
643 candidates = [f for f in repo.dirstate.copies() if
644 candidates = [f for f in repo.dirstate.copies() if
644 not 'l' in wctx.flags(f) and haskwsource(f)]
645 not 'l' in wctx.flags(f) and haskwsource(f)]
645 kwt.overwrite(wctx, candidates, False, False)
646 kwt.overwrite(wctx, candidates, False, False)
646
647
647 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
648 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
648 '''Wraps record.dorecord expanding keywords after recording.'''
649 '''Wraps record.dorecord expanding keywords after recording.'''
649 wlock = repo.wlock()
650 wlock = repo.wlock()
650 try:
651 try:
651 # record returns 0 even when nothing has changed
652 # record returns 0 even when nothing has changed
652 # therefore compare nodes before and after
653 # therefore compare nodes before and after
653 kwt.record = True
654 kwt.record = True
654 ctx = repo['.']
655 ctx = repo['.']
655 wstatus = repo[None].status()
656 wstatus = repo[None].status()
656 ret = orig(ui, repo, commitfunc, *pats, **opts)
657 ret = orig(ui, repo, commitfunc, *pats, **opts)
657 recctx = repo['.']
658 recctx = repo['.']
658 if ctx != recctx:
659 if ctx != recctx:
659 modified, added = _preselect(wstatus, recctx.files())
660 modified, added = _preselect(wstatus, recctx.files())
660 kwt.restrict = False
661 kwt.restrict = False
661 kwt.overwrite(recctx, modified, False, True)
662 kwt.overwrite(recctx, modified, False, True)
662 kwt.overwrite(recctx, added, False, True, True)
663 kwt.overwrite(recctx, added, False, True, True)
663 kwt.restrict = True
664 kwt.restrict = True
664 return ret
665 return ret
665 finally:
666 finally:
666 wlock.release()
667 wlock.release()
667
668
668 def kwfilectx_cmp(orig, self, fctx):
669 def kwfilectx_cmp(orig, self, fctx):
669 # keyword affects data size, comparing wdir and filelog size does
670 # keyword affects data size, comparing wdir and filelog size does
670 # not make sense
671 # not make sense
671 if (fctx._filerev is None and
672 if (fctx._filerev is None and
672 (self._repo._encodefilterpats or
673 (self._repo._encodefilterpats or
673 kwt.match(fctx.path()) and not 'l' in fctx.flags()) or
674 kwt.match(fctx.path()) and not 'l' in fctx.flags()) or
674 self.size() == fctx.size()):
675 self.size() == fctx.size()):
675 return self._filelog.cmp(self._filenode, fctx.data())
676 return self._filelog.cmp(self._filenode, fctx.data())
676 return True
677 return True
677
678
678 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
679 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
679 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
680 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
680 extensions.wrapfunction(patch, 'diff', kw_diff)
681 extensions.wrapfunction(patch, 'diff', kw_diff)
681 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
682 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
682 for c in 'annotate changeset rev filediff diff'.split():
683 for c in 'annotate changeset rev filediff diff'.split():
683 extensions.wrapfunction(webcommands, c, kwweb_skip)
684 extensions.wrapfunction(webcommands, c, kwweb_skip)
684 for name in recordextensions.split():
685 for name in recordextensions.split():
685 try:
686 try:
686 record = extensions.find(name)
687 record = extensions.find(name)
687 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
688 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
688 except KeyError:
689 except KeyError:
689 pass
690 pass
690
691
691 repo.__class__ = kwrepo
692 repo.__class__ = kwrepo
@@ -1,1751 +1,1780 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import cStringIO, email.Parser, os, errno, re
9 import cStringIO, email.Parser, os, errno, re
10 import tempfile, zlib
10 import tempfile, zlib, shutil
11
11
12 from i18n import _
12 from i18n import _
13 from node import hex, nullid, short
13 from node import hex, nullid, short
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding
15
15
16 gitre = re.compile('diff --git a/(.*) b/(.*)')
16 gitre = re.compile('diff --git a/(.*) b/(.*)')
17
17
18 class PatchError(Exception):
18 class PatchError(Exception):
19 pass
19 pass
20
20
21
21
22 # public functions
22 # public functions
23
23
24 def split(stream):
24 def split(stream):
25 '''return an iterator of individual patches from a stream'''
25 '''return an iterator of individual patches from a stream'''
26 def isheader(line, inheader):
26 def isheader(line, inheader):
27 if inheader and line[0] in (' ', '\t'):
27 if inheader and line[0] in (' ', '\t'):
28 # continuation
28 # continuation
29 return True
29 return True
30 if line[0] in (' ', '-', '+'):
30 if line[0] in (' ', '-', '+'):
31 # diff line - don't check for header pattern in there
31 # diff line - don't check for header pattern in there
32 return False
32 return False
33 l = line.split(': ', 1)
33 l = line.split(': ', 1)
34 return len(l) == 2 and ' ' not in l[0]
34 return len(l) == 2 and ' ' not in l[0]
35
35
36 def chunk(lines):
36 def chunk(lines):
37 return cStringIO.StringIO(''.join(lines))
37 return cStringIO.StringIO(''.join(lines))
38
38
39 def hgsplit(stream, cur):
39 def hgsplit(stream, cur):
40 inheader = True
40 inheader = True
41
41
42 for line in stream:
42 for line in stream:
43 if not line.strip():
43 if not line.strip():
44 inheader = False
44 inheader = False
45 if not inheader and line.startswith('# HG changeset patch'):
45 if not inheader and line.startswith('# HG changeset patch'):
46 yield chunk(cur)
46 yield chunk(cur)
47 cur = []
47 cur = []
48 inheader = True
48 inheader = True
49
49
50 cur.append(line)
50 cur.append(line)
51
51
52 if cur:
52 if cur:
53 yield chunk(cur)
53 yield chunk(cur)
54
54
55 def mboxsplit(stream, cur):
55 def mboxsplit(stream, cur):
56 for line in stream:
56 for line in stream:
57 if line.startswith('From '):
57 if line.startswith('From '):
58 for c in split(chunk(cur[1:])):
58 for c in split(chunk(cur[1:])):
59 yield c
59 yield c
60 cur = []
60 cur = []
61
61
62 cur.append(line)
62 cur.append(line)
63
63
64 if cur:
64 if cur:
65 for c in split(chunk(cur[1:])):
65 for c in split(chunk(cur[1:])):
66 yield c
66 yield c
67
67
68 def mimesplit(stream, cur):
68 def mimesplit(stream, cur):
69 def msgfp(m):
69 def msgfp(m):
70 fp = cStringIO.StringIO()
70 fp = cStringIO.StringIO()
71 g = email.Generator.Generator(fp, mangle_from_=False)
71 g = email.Generator.Generator(fp, mangle_from_=False)
72 g.flatten(m)
72 g.flatten(m)
73 fp.seek(0)
73 fp.seek(0)
74 return fp
74 return fp
75
75
76 for line in stream:
76 for line in stream:
77 cur.append(line)
77 cur.append(line)
78 c = chunk(cur)
78 c = chunk(cur)
79
79
80 m = email.Parser.Parser().parse(c)
80 m = email.Parser.Parser().parse(c)
81 if not m.is_multipart():
81 if not m.is_multipart():
82 yield msgfp(m)
82 yield msgfp(m)
83 else:
83 else:
84 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
84 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
85 for part in m.walk():
85 for part in m.walk():
86 ct = part.get_content_type()
86 ct = part.get_content_type()
87 if ct not in ok_types:
87 if ct not in ok_types:
88 continue
88 continue
89 yield msgfp(part)
89 yield msgfp(part)
90
90
91 def headersplit(stream, cur):
91 def headersplit(stream, cur):
92 inheader = False
92 inheader = False
93
93
94 for line in stream:
94 for line in stream:
95 if not inheader and isheader(line, inheader):
95 if not inheader and isheader(line, inheader):
96 yield chunk(cur)
96 yield chunk(cur)
97 cur = []
97 cur = []
98 inheader = True
98 inheader = True
99 if inheader and not isheader(line, inheader):
99 if inheader and not isheader(line, inheader):
100 inheader = False
100 inheader = False
101
101
102 cur.append(line)
102 cur.append(line)
103
103
104 if cur:
104 if cur:
105 yield chunk(cur)
105 yield chunk(cur)
106
106
107 def remainder(cur):
107 def remainder(cur):
108 yield chunk(cur)
108 yield chunk(cur)
109
109
110 class fiter(object):
110 class fiter(object):
111 def __init__(self, fp):
111 def __init__(self, fp):
112 self.fp = fp
112 self.fp = fp
113
113
114 def __iter__(self):
114 def __iter__(self):
115 return self
115 return self
116
116
117 def next(self):
117 def next(self):
118 l = self.fp.readline()
118 l = self.fp.readline()
119 if not l:
119 if not l:
120 raise StopIteration
120 raise StopIteration
121 return l
121 return l
122
122
123 inheader = False
123 inheader = False
124 cur = []
124 cur = []
125
125
126 mimeheaders = ['content-type']
126 mimeheaders = ['content-type']
127
127
128 if not hasattr(stream, 'next'):
128 if not hasattr(stream, 'next'):
129 # http responses, for example, have readline but not next
129 # http responses, for example, have readline but not next
130 stream = fiter(stream)
130 stream = fiter(stream)
131
131
132 for line in stream:
132 for line in stream:
133 cur.append(line)
133 cur.append(line)
134 if line.startswith('# HG changeset patch'):
134 if line.startswith('# HG changeset patch'):
135 return hgsplit(stream, cur)
135 return hgsplit(stream, cur)
136 elif line.startswith('From '):
136 elif line.startswith('From '):
137 return mboxsplit(stream, cur)
137 return mboxsplit(stream, cur)
138 elif isheader(line, inheader):
138 elif isheader(line, inheader):
139 inheader = True
139 inheader = True
140 if line.split(':', 1)[0].lower() in mimeheaders:
140 if line.split(':', 1)[0].lower() in mimeheaders:
141 # let email parser handle this
141 # let email parser handle this
142 return mimesplit(stream, cur)
142 return mimesplit(stream, cur)
143 elif line.startswith('--- ') and inheader:
143 elif line.startswith('--- ') and inheader:
144 # No evil headers seen by diff start, split by hand
144 # No evil headers seen by diff start, split by hand
145 return headersplit(stream, cur)
145 return headersplit(stream, cur)
146 # Not enough info, keep reading
146 # Not enough info, keep reading
147
147
148 # if we are here, we have a very plain patch
148 # if we are here, we have a very plain patch
149 return remainder(cur)
149 return remainder(cur)
150
150
151 def extract(ui, fileobj):
151 def extract(ui, fileobj):
152 '''extract patch from data read from fileobj.
152 '''extract patch from data read from fileobj.
153
153
154 patch can be a normal patch or contained in an email message.
154 patch can be a normal patch or contained in an email message.
155
155
156 return tuple (filename, message, user, date, branch, node, p1, p2).
156 return tuple (filename, message, user, date, branch, node, p1, p2).
157 Any item in the returned tuple can be None. If filename is None,
157 Any item in the returned tuple can be None. If filename is None,
158 fileobj did not contain a patch. Caller must unlink filename when done.'''
158 fileobj did not contain a patch. Caller must unlink filename when done.'''
159
159
160 # attempt to detect the start of a patch
160 # attempt to detect the start of a patch
161 # (this heuristic is borrowed from quilt)
161 # (this heuristic is borrowed from quilt)
162 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
162 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
163 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
163 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
164 r'---[ \t].*?^\+\+\+[ \t]|'
164 r'---[ \t].*?^\+\+\+[ \t]|'
165 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
165 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
166
166
167 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
167 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
168 tmpfp = os.fdopen(fd, 'w')
168 tmpfp = os.fdopen(fd, 'w')
169 try:
169 try:
170 msg = email.Parser.Parser().parse(fileobj)
170 msg = email.Parser.Parser().parse(fileobj)
171
171
172 subject = msg['Subject']
172 subject = msg['Subject']
173 user = msg['From']
173 user = msg['From']
174 if not subject and not user:
174 if not subject and not user:
175 # Not an email, restore parsed headers if any
175 # Not an email, restore parsed headers if any
176 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
176 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
177
177
178 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
178 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
179 # should try to parse msg['Date']
179 # should try to parse msg['Date']
180 date = None
180 date = None
181 nodeid = None
181 nodeid = None
182 branch = None
182 branch = None
183 parents = []
183 parents = []
184
184
185 if subject:
185 if subject:
186 if subject.startswith('[PATCH'):
186 if subject.startswith('[PATCH'):
187 pend = subject.find(']')
187 pend = subject.find(']')
188 if pend >= 0:
188 if pend >= 0:
189 subject = subject[pend + 1:].lstrip()
189 subject = subject[pend + 1:].lstrip()
190 subject = subject.replace('\n\t', ' ')
190 subject = subject.replace('\n\t', ' ')
191 ui.debug('Subject: %s\n' % subject)
191 ui.debug('Subject: %s\n' % subject)
192 if user:
192 if user:
193 ui.debug('From: %s\n' % user)
193 ui.debug('From: %s\n' % user)
194 diffs_seen = 0
194 diffs_seen = 0
195 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
195 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
196 message = ''
196 message = ''
197 for part in msg.walk():
197 for part in msg.walk():
198 content_type = part.get_content_type()
198 content_type = part.get_content_type()
199 ui.debug('Content-Type: %s\n' % content_type)
199 ui.debug('Content-Type: %s\n' % content_type)
200 if content_type not in ok_types:
200 if content_type not in ok_types:
201 continue
201 continue
202 payload = part.get_payload(decode=True)
202 payload = part.get_payload(decode=True)
203 m = diffre.search(payload)
203 m = diffre.search(payload)
204 if m:
204 if m:
205 hgpatch = False
205 hgpatch = False
206 hgpatchheader = False
206 hgpatchheader = False
207 ignoretext = False
207 ignoretext = False
208
208
209 ui.debug('found patch at byte %d\n' % m.start(0))
209 ui.debug('found patch at byte %d\n' % m.start(0))
210 diffs_seen += 1
210 diffs_seen += 1
211 cfp = cStringIO.StringIO()
211 cfp = cStringIO.StringIO()
212 for line in payload[:m.start(0)].splitlines():
212 for line in payload[:m.start(0)].splitlines():
213 if line.startswith('# HG changeset patch') and not hgpatch:
213 if line.startswith('# HG changeset patch') and not hgpatch:
214 ui.debug('patch generated by hg export\n')
214 ui.debug('patch generated by hg export\n')
215 hgpatch = True
215 hgpatch = True
216 hgpatchheader = True
216 hgpatchheader = True
217 # drop earlier commit message content
217 # drop earlier commit message content
218 cfp.seek(0)
218 cfp.seek(0)
219 cfp.truncate()
219 cfp.truncate()
220 subject = None
220 subject = None
221 elif hgpatchheader:
221 elif hgpatchheader:
222 if line.startswith('# User '):
222 if line.startswith('# User '):
223 user = line[7:]
223 user = line[7:]
224 ui.debug('From: %s\n' % user)
224 ui.debug('From: %s\n' % user)
225 elif line.startswith("# Date "):
225 elif line.startswith("# Date "):
226 date = line[7:]
226 date = line[7:]
227 elif line.startswith("# Branch "):
227 elif line.startswith("# Branch "):
228 branch = line[9:]
228 branch = line[9:]
229 elif line.startswith("# Node ID "):
229 elif line.startswith("# Node ID "):
230 nodeid = line[10:]
230 nodeid = line[10:]
231 elif line.startswith("# Parent "):
231 elif line.startswith("# Parent "):
232 parents.append(line[10:])
232 parents.append(line[10:])
233 elif not line.startswith("# "):
233 elif not line.startswith("# "):
234 hgpatchheader = False
234 hgpatchheader = False
235 elif line == '---' and gitsendmail:
235 elif line == '---' and gitsendmail:
236 ignoretext = True
236 ignoretext = True
237 if not hgpatchheader and not ignoretext:
237 if not hgpatchheader and not ignoretext:
238 cfp.write(line)
238 cfp.write(line)
239 cfp.write('\n')
239 cfp.write('\n')
240 message = cfp.getvalue()
240 message = cfp.getvalue()
241 if tmpfp:
241 if tmpfp:
242 tmpfp.write(payload)
242 tmpfp.write(payload)
243 if not payload.endswith('\n'):
243 if not payload.endswith('\n'):
244 tmpfp.write('\n')
244 tmpfp.write('\n')
245 elif not diffs_seen and message and content_type == 'text/plain':
245 elif not diffs_seen and message and content_type == 'text/plain':
246 message += '\n' + payload
246 message += '\n' + payload
247 except:
247 except:
248 tmpfp.close()
248 tmpfp.close()
249 os.unlink(tmpname)
249 os.unlink(tmpname)
250 raise
250 raise
251
251
252 if subject and not message.startswith(subject):
252 if subject and not message.startswith(subject):
253 message = '%s\n%s' % (subject, message)
253 message = '%s\n%s' % (subject, message)
254 tmpfp.close()
254 tmpfp.close()
255 if not diffs_seen:
255 if not diffs_seen:
256 os.unlink(tmpname)
256 os.unlink(tmpname)
257 return None, message, user, date, branch, None, None, None
257 return None, message, user, date, branch, None, None, None
258 p1 = parents and parents.pop(0) or None
258 p1 = parents and parents.pop(0) or None
259 p2 = parents and parents.pop(0) or None
259 p2 = parents and parents.pop(0) or None
260 return tmpname, message, user, date, branch, nodeid, p1, p2
260 return tmpname, message, user, date, branch, nodeid, p1, p2
261
261
262 class patchmeta(object):
262 class patchmeta(object):
263 """Patched file metadata
263 """Patched file metadata
264
264
265 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
265 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
266 or COPY. 'path' is patched file path. 'oldpath' is set to the
266 or COPY. 'path' is patched file path. 'oldpath' is set to the
267 origin file when 'op' is either COPY or RENAME, None otherwise. If
267 origin file when 'op' is either COPY or RENAME, None otherwise. If
268 file mode is changed, 'mode' is a tuple (islink, isexec) where
268 file mode is changed, 'mode' is a tuple (islink, isexec) where
269 'islink' is True if the file is a symlink and 'isexec' is True if
269 'islink' is True if the file is a symlink and 'isexec' is True if
270 the file is executable. Otherwise, 'mode' is None.
270 the file is executable. Otherwise, 'mode' is None.
271 """
271 """
272 def __init__(self, path):
272 def __init__(self, path):
273 self.path = path
273 self.path = path
274 self.oldpath = None
274 self.oldpath = None
275 self.mode = None
275 self.mode = None
276 self.op = 'MODIFY'
276 self.op = 'MODIFY'
277 self.binary = False
277 self.binary = False
278
278
279 def setmode(self, mode):
279 def setmode(self, mode):
280 islink = mode & 020000
280 islink = mode & 020000
281 isexec = mode & 0100
281 isexec = mode & 0100
282 self.mode = (islink, isexec)
282 self.mode = (islink, isexec)
283
283
284 def __repr__(self):
284 def __repr__(self):
285 return "<patchmeta %s %r>" % (self.op, self.path)
285 return "<patchmeta %s %r>" % (self.op, self.path)
286
286
287 def readgitpatch(lr):
287 def readgitpatch(lr):
288 """extract git-style metadata about patches from <patchname>"""
288 """extract git-style metadata about patches from <patchname>"""
289
289
290 # Filter patch for git information
290 # Filter patch for git information
291 gp = None
291 gp = None
292 gitpatches = []
292 gitpatches = []
293 for line in lr:
293 for line in lr:
294 line = line.rstrip(' \r\n')
294 line = line.rstrip(' \r\n')
295 if line.startswith('diff --git'):
295 if line.startswith('diff --git'):
296 m = gitre.match(line)
296 m = gitre.match(line)
297 if m:
297 if m:
298 if gp:
298 if gp:
299 gitpatches.append(gp)
299 gitpatches.append(gp)
300 dst = m.group(2)
300 dst = m.group(2)
301 gp = patchmeta(dst)
301 gp = patchmeta(dst)
302 elif gp:
302 elif gp:
303 if line.startswith('--- '):
303 if line.startswith('--- '):
304 gitpatches.append(gp)
304 gitpatches.append(gp)
305 gp = None
305 gp = None
306 continue
306 continue
307 if line.startswith('rename from '):
307 if line.startswith('rename from '):
308 gp.op = 'RENAME'
308 gp.op = 'RENAME'
309 gp.oldpath = line[12:]
309 gp.oldpath = line[12:]
310 elif line.startswith('rename to '):
310 elif line.startswith('rename to '):
311 gp.path = line[10:]
311 gp.path = line[10:]
312 elif line.startswith('copy from '):
312 elif line.startswith('copy from '):
313 gp.op = 'COPY'
313 gp.op = 'COPY'
314 gp.oldpath = line[10:]
314 gp.oldpath = line[10:]
315 elif line.startswith('copy to '):
315 elif line.startswith('copy to '):
316 gp.path = line[8:]
316 gp.path = line[8:]
317 elif line.startswith('deleted file'):
317 elif line.startswith('deleted file'):
318 gp.op = 'DELETE'
318 gp.op = 'DELETE'
319 elif line.startswith('new file mode '):
319 elif line.startswith('new file mode '):
320 gp.op = 'ADD'
320 gp.op = 'ADD'
321 gp.setmode(int(line[-6:], 8))
321 gp.setmode(int(line[-6:], 8))
322 elif line.startswith('new mode '):
322 elif line.startswith('new mode '):
323 gp.setmode(int(line[-6:], 8))
323 gp.setmode(int(line[-6:], 8))
324 elif line.startswith('GIT binary patch'):
324 elif line.startswith('GIT binary patch'):
325 gp.binary = True
325 gp.binary = True
326 if gp:
326 if gp:
327 gitpatches.append(gp)
327 gitpatches.append(gp)
328
328
329 return gitpatches
329 return gitpatches
330
330
331 class linereader(object):
331 class linereader(object):
332 # simple class to allow pushing lines back into the input stream
332 # simple class to allow pushing lines back into the input stream
333 def __init__(self, fp):
333 def __init__(self, fp):
334 self.fp = fp
334 self.fp = fp
335 self.buf = []
335 self.buf = []
336
336
337 def push(self, line):
337 def push(self, line):
338 if line is not None:
338 if line is not None:
339 self.buf.append(line)
339 self.buf.append(line)
340
340
341 def readline(self):
341 def readline(self):
342 if self.buf:
342 if self.buf:
343 l = self.buf[0]
343 l = self.buf[0]
344 del self.buf[0]
344 del self.buf[0]
345 return l
345 return l
346 return self.fp.readline()
346 return self.fp.readline()
347
347
348 def __iter__(self):
348 def __iter__(self):
349 while 1:
349 while 1:
350 l = self.readline()
350 l = self.readline()
351 if not l:
351 if not l:
352 break
352 break
353 yield l
353 yield l
354
354
355 class abstractbackend(object):
355 class abstractbackend(object):
356 def __init__(self, ui):
356 def __init__(self, ui):
357 self.ui = ui
357 self.ui = ui
358
358
359 def getfile(self, fname):
359 def getfile(self, fname):
360 """Return target file data and flags as a (data, (islink,
360 """Return target file data and flags as a (data, (islink,
361 isexec)) tuple.
361 isexec)) tuple.
362 """
362 """
363 raise NotImplementedError
363 raise NotImplementedError
364
364
365 def setfile(self, fname, data, mode):
365 def setfile(self, fname, data, mode, copysource):
366 """Write data to target file fname and set its mode. mode is a
366 """Write data to target file fname and set its mode. mode is a
367 (islink, isexec) tuple. If data is None, the file content should
367 (islink, isexec) tuple. If data is None, the file content should
368 be left unchanged.
368 be left unchanged. If the file is modified after being copied,
369 copysource is set to the original file name.
369 """
370 """
370 raise NotImplementedError
371 raise NotImplementedError
371
372
372 def unlink(self, fname):
373 def unlink(self, fname):
373 """Unlink target file."""
374 """Unlink target file."""
374 raise NotImplementedError
375 raise NotImplementedError
375
376
376 def writerej(self, fname, failed, total, lines):
377 def writerej(self, fname, failed, total, lines):
377 """Write rejected lines for fname. total is the number of hunks
378 """Write rejected lines for fname. total is the number of hunks
378 which failed to apply and total the total number of hunks for this
379 which failed to apply and total the total number of hunks for this
379 files.
380 files.
380 """
381 """
381 pass
382 pass
382
383
383 def copy(self, src, dst):
384 """Copy src file into dst file. Create intermediate directories if
385 necessary. Files are specified relatively to the patching base
386 directory.
387 """
388 raise NotImplementedError
389
390 def exists(self, fname):
384 def exists(self, fname):
391 raise NotImplementedError
385 raise NotImplementedError
392
386
393 class fsbackend(abstractbackend):
387 class fsbackend(abstractbackend):
394 def __init__(self, ui, basedir):
388 def __init__(self, ui, basedir):
395 super(fsbackend, self).__init__(ui)
389 super(fsbackend, self).__init__(ui)
396 self.opener = scmutil.opener(basedir)
390 self.opener = scmutil.opener(basedir)
397
391
398 def _join(self, f):
392 def _join(self, f):
399 return os.path.join(self.opener.base, f)
393 return os.path.join(self.opener.base, f)
400
394
401 def getfile(self, fname):
395 def getfile(self, fname):
402 path = self._join(fname)
396 path = self._join(fname)
403 if os.path.islink(path):
397 if os.path.islink(path):
404 return (os.readlink(path), (True, False))
398 return (os.readlink(path), (True, False))
405 isexec, islink = False, False
399 isexec, islink = False, False
406 try:
400 try:
407 isexec = os.lstat(path).st_mode & 0100 != 0
401 isexec = os.lstat(path).st_mode & 0100 != 0
408 islink = os.path.islink(path)
402 islink = os.path.islink(path)
409 except OSError, e:
403 except OSError, e:
410 if e.errno != errno.ENOENT:
404 if e.errno != errno.ENOENT:
411 raise
405 raise
412 return (self.opener.read(fname), (islink, isexec))
406 return (self.opener.read(fname), (islink, isexec))
413
407
414 def setfile(self, fname, data, mode):
408 def setfile(self, fname, data, mode, copysource):
415 islink, isexec = mode
409 islink, isexec = mode
416 if data is None:
410 if data is None:
417 util.setflags(self._join(fname), islink, isexec)
411 util.setflags(self._join(fname), islink, isexec)
418 return
412 return
419 if islink:
413 if islink:
420 self.opener.symlink(data, fname)
414 self.opener.symlink(data, fname)
421 else:
415 else:
422 self.opener.write(fname, data)
416 self.opener.write(fname, data)
423 if isexec:
417 if isexec:
424 util.setflags(self._join(fname), False, True)
418 util.setflags(self._join(fname), False, True)
425
419
426 def unlink(self, fname):
420 def unlink(self, fname):
427 try:
421 try:
428 util.unlinkpath(self._join(fname))
422 util.unlinkpath(self._join(fname))
429 except OSError, inst:
423 except OSError, inst:
430 if inst.errno != errno.ENOENT:
424 if inst.errno != errno.ENOENT:
431 raise
425 raise
432
426
433 def writerej(self, fname, failed, total, lines):
427 def writerej(self, fname, failed, total, lines):
434 fname = fname + ".rej"
428 fname = fname + ".rej"
435 self.ui.warn(
429 self.ui.warn(
436 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
430 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
437 (failed, total, fname))
431 (failed, total, fname))
438 fp = self.opener(fname, 'w')
432 fp = self.opener(fname, 'w')
439 fp.writelines(lines)
433 fp.writelines(lines)
440 fp.close()
434 fp.close()
441
435
442 def copy(self, src, dst):
443 basedir = self.opener.base
444 abssrc, absdst = [scmutil.canonpath(basedir, basedir, x)
445 for x in [src, dst]]
446 if os.path.lexists(absdst):
447 raise util.Abort(_("cannot create %s: destination already exists")
448 % dst)
449 dstdir = os.path.dirname(absdst)
450 if dstdir and not os.path.isdir(dstdir):
451 try:
452 os.makedirs(dstdir)
453 except IOError:
454 raise util.Abort(
455 _("cannot create %s: unable to create destination directory")
456 % dst)
457 util.copyfile(abssrc, absdst)
458
459 def exists(self, fname):
436 def exists(self, fname):
460 return os.path.lexists(self._join(fname))
437 return os.path.lexists(self._join(fname))
461
438
462 class workingbackend(fsbackend):
439 class workingbackend(fsbackend):
463 def __init__(self, ui, repo, similarity):
440 def __init__(self, ui, repo, similarity):
464 super(workingbackend, self).__init__(ui, repo.root)
441 super(workingbackend, self).__init__(ui, repo.root)
465 self.repo = repo
442 self.repo = repo
466 self.similarity = similarity
443 self.similarity = similarity
467 self.removed = set()
444 self.removed = set()
468 self.changed = set()
445 self.changed = set()
469 self.copied = []
446 self.copied = []
470
447
471 def setfile(self, fname, data, mode):
448 def setfile(self, fname, data, mode, copysource):
472 super(workingbackend, self).setfile(fname, data, mode)
449 super(workingbackend, self).setfile(fname, data, mode, copysource)
450 if copysource is not None:
451 self.copied.append((copysource, fname))
473 self.changed.add(fname)
452 self.changed.add(fname)
474
453
475 def unlink(self, fname):
454 def unlink(self, fname):
476 super(workingbackend, self).unlink(fname)
455 super(workingbackend, self).unlink(fname)
477 self.removed.add(fname)
456 self.removed.add(fname)
478 self.changed.add(fname)
457 self.changed.add(fname)
479
458
480 def copy(self, src, dst):
481 super(workingbackend, self).copy(src, dst)
482 self.copied.append((src, dst))
483 self.changed.add(dst)
484
485 def close(self):
459 def close(self):
486 wctx = self.repo[None]
460 wctx = self.repo[None]
487 addremoved = set(self.changed)
461 addremoved = set(self.changed)
488 for src, dst in self.copied:
462 for src, dst in self.copied:
489 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
463 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
490 addremoved.discard(src)
464 addremoved.discard(src)
491 if (not self.similarity) and self.removed:
465 if (not self.similarity) and self.removed:
492 wctx.forget(sorted(self.removed))
466 wctx.forget(sorted(self.removed))
493 if addremoved:
467 if addremoved:
494 cwd = self.repo.getcwd()
468 cwd = self.repo.getcwd()
495 if cwd:
469 if cwd:
496 addremoved = [util.pathto(self.repo.root, cwd, f)
470 addremoved = [util.pathto(self.repo.root, cwd, f)
497 for f in addremoved]
471 for f in addremoved]
498 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
472 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
499 return sorted(self.changed)
473 return sorted(self.changed)
500
474
475 class filestore(object):
476 def __init__(self):
477 self.opener = None
478 self.files = {}
479 self.created = 0
480
481 def setfile(self, fname, data, mode):
482 if self.opener is None:
483 root = tempfile.mkdtemp(prefix='hg-patch-')
484 self.opener = scmutil.opener(root)
485 # Avoid filename issues with these simple names
486 fn = str(self.created)
487 self.opener.write(fn, data)
488 self.created += 1
489 self.files[fname] = (fn, mode)
490
491 def getfile(self, fname):
492 if fname not in self.files:
493 raise IOError()
494 fn, mode = self.files[fname]
495 return self.opener.read(fn), mode
496
497 def close(self):
498 if self.opener:
499 shutil.rmtree(self.opener.base)
500
501 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
501 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
502 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
502 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
503 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
503 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
504 eolmodes = ['strict', 'crlf', 'lf', 'auto']
504 eolmodes = ['strict', 'crlf', 'lf', 'auto']
505
505
506 class patchfile(object):
506 class patchfile(object):
507 def __init__(self, ui, fname, backend, mode, create, remove, missing=False,
507 def __init__(self, ui, fname, backend, store, mode, create, remove,
508 eolmode='strict'):
508 eolmode='strict', copysource=None):
509 self.fname = fname
509 self.fname = fname
510 self.eolmode = eolmode
510 self.eolmode = eolmode
511 self.eol = None
511 self.eol = None
512 self.backend = backend
512 self.backend = backend
513 self.ui = ui
513 self.ui = ui
514 self.lines = []
514 self.lines = []
515 self.exists = False
515 self.exists = False
516 self.missing = missing
516 self.missing = True
517 self.mode = mode
517 self.mode = mode
518 self.copysource = copysource
518 self.create = create
519 self.create = create
519 self.remove = remove
520 self.remove = remove
520 if not missing:
521 try:
521 try:
522 data, mode = self.backend.getfile(fname)
522 if copysource is None:
523 data, mode = backend.getfile(fname)
524 self.exists = True
525 else:
526 data, mode = store.getfile(copysource)
527 self.exists = backend.exists(fname)
528 self.missing = False
523 if data:
529 if data:
524 self.lines = data.splitlines(True)
530 self.lines = data.splitlines(True)
525 if self.mode is None:
531 if self.mode is None:
526 self.mode = mode
532 self.mode = mode
527 if self.lines:
533 if self.lines:
528 # Normalize line endings
534 # Normalize line endings
529 if self.lines[0].endswith('\r\n'):
535 if self.lines[0].endswith('\r\n'):
530 self.eol = '\r\n'
536 self.eol = '\r\n'
531 elif self.lines[0].endswith('\n'):
537 elif self.lines[0].endswith('\n'):
532 self.eol = '\n'
538 self.eol = '\n'
533 if eolmode != 'strict':
539 if eolmode != 'strict':
534 nlines = []
540 nlines = []
535 for l in self.lines:
541 for l in self.lines:
536 if l.endswith('\r\n'):
542 if l.endswith('\r\n'):
537 l = l[:-2] + '\n'
543 l = l[:-2] + '\n'
538 nlines.append(l)
544 nlines.append(l)
539 self.lines = nlines
545 self.lines = nlines
540 self.exists = True
541 except IOError:
546 except IOError:
547 if create:
548 self.missing = False
542 if self.mode is None:
549 if self.mode is None:
543 self.mode = (False, False)
550 self.mode = (False, False)
544 else:
551 if self.missing:
545 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
552 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
546
553
547 self.hash = {}
554 self.hash = {}
548 self.dirty = 0
555 self.dirty = 0
549 self.offset = 0
556 self.offset = 0
550 self.skew = 0
557 self.skew = 0
551 self.rej = []
558 self.rej = []
552 self.fileprinted = False
559 self.fileprinted = False
553 self.printfile(False)
560 self.printfile(False)
554 self.hunks = 0
561 self.hunks = 0
555
562
556 def writelines(self, fname, lines, mode):
563 def writelines(self, fname, lines, mode):
557 if self.eolmode == 'auto':
564 if self.eolmode == 'auto':
558 eol = self.eol
565 eol = self.eol
559 elif self.eolmode == 'crlf':
566 elif self.eolmode == 'crlf':
560 eol = '\r\n'
567 eol = '\r\n'
561 else:
568 else:
562 eol = '\n'
569 eol = '\n'
563
570
564 if self.eolmode != 'strict' and eol and eol != '\n':
571 if self.eolmode != 'strict' and eol and eol != '\n':
565 rawlines = []
572 rawlines = []
566 for l in lines:
573 for l in lines:
567 if l and l[-1] == '\n':
574 if l and l[-1] == '\n':
568 l = l[:-1] + eol
575 l = l[:-1] + eol
569 rawlines.append(l)
576 rawlines.append(l)
570 lines = rawlines
577 lines = rawlines
571
578
572 self.backend.setfile(fname, ''.join(lines), mode)
579 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
573
580
574 def printfile(self, warn):
581 def printfile(self, warn):
575 if self.fileprinted:
582 if self.fileprinted:
576 return
583 return
577 if warn or self.ui.verbose:
584 if warn or self.ui.verbose:
578 self.fileprinted = True
585 self.fileprinted = True
579 s = _("patching file %s\n") % self.fname
586 s = _("patching file %s\n") % self.fname
580 if warn:
587 if warn:
581 self.ui.warn(s)
588 self.ui.warn(s)
582 else:
589 else:
583 self.ui.note(s)
590 self.ui.note(s)
584
591
585
592
586 def findlines(self, l, linenum):
593 def findlines(self, l, linenum):
587 # looks through the hash and finds candidate lines. The
594 # looks through the hash and finds candidate lines. The
588 # result is a list of line numbers sorted based on distance
595 # result is a list of line numbers sorted based on distance
589 # from linenum
596 # from linenum
590
597
591 cand = self.hash.get(l, [])
598 cand = self.hash.get(l, [])
592 if len(cand) > 1:
599 if len(cand) > 1:
593 # resort our list of potentials forward then back.
600 # resort our list of potentials forward then back.
594 cand.sort(key=lambda x: abs(x - linenum))
601 cand.sort(key=lambda x: abs(x - linenum))
595 return cand
602 return cand
596
603
597 def write_rej(self):
604 def write_rej(self):
598 # our rejects are a little different from patch(1). This always
605 # our rejects are a little different from patch(1). This always
599 # creates rejects in the same form as the original patch. A file
606 # creates rejects in the same form as the original patch. A file
600 # header is inserted so that you can run the reject through patch again
607 # header is inserted so that you can run the reject through patch again
601 # without having to type the filename.
608 # without having to type the filename.
602 if not self.rej:
609 if not self.rej:
603 return
610 return
604 base = os.path.basename(self.fname)
611 base = os.path.basename(self.fname)
605 lines = ["--- %s\n+++ %s\n" % (base, base)]
612 lines = ["--- %s\n+++ %s\n" % (base, base)]
606 for x in self.rej:
613 for x in self.rej:
607 for l in x.hunk:
614 for l in x.hunk:
608 lines.append(l)
615 lines.append(l)
609 if l[-1] != '\n':
616 if l[-1] != '\n':
610 lines.append("\n\ No newline at end of file\n")
617 lines.append("\n\ No newline at end of file\n")
611 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
618 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
612
619
613 def apply(self, h):
620 def apply(self, h):
614 if not h.complete():
621 if not h.complete():
615 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
622 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
616 (h.number, h.desc, len(h.a), h.lena, len(h.b),
623 (h.number, h.desc, len(h.a), h.lena, len(h.b),
617 h.lenb))
624 h.lenb))
618
625
619 self.hunks += 1
626 self.hunks += 1
620
627
621 if self.missing:
628 if self.missing:
622 self.rej.append(h)
629 self.rej.append(h)
623 return -1
630 return -1
624
631
625 if self.exists and self.create:
632 if self.exists and self.create:
633 if self.copysource:
634 self.ui.warn(_("cannot create %s: destination already "
635 "exists\n" % self.fname))
636 else:
626 self.ui.warn(_("file %s already exists\n") % self.fname)
637 self.ui.warn(_("file %s already exists\n") % self.fname)
627 self.rej.append(h)
638 self.rej.append(h)
628 return -1
639 return -1
629
640
630 if isinstance(h, binhunk):
641 if isinstance(h, binhunk):
631 if self.remove:
642 if self.remove:
632 self.backend.unlink(self.fname)
643 self.backend.unlink(self.fname)
633 else:
644 else:
634 self.lines[:] = h.new()
645 self.lines[:] = h.new()
635 self.offset += len(h.new())
646 self.offset += len(h.new())
636 self.dirty = True
647 self.dirty = True
637 return 0
648 return 0
638
649
639 horig = h
650 horig = h
640 if (self.eolmode in ('crlf', 'lf')
651 if (self.eolmode in ('crlf', 'lf')
641 or self.eolmode == 'auto' and self.eol):
652 or self.eolmode == 'auto' and self.eol):
642 # If new eols are going to be normalized, then normalize
653 # If new eols are going to be normalized, then normalize
643 # hunk data before patching. Otherwise, preserve input
654 # hunk data before patching. Otherwise, preserve input
644 # line-endings.
655 # line-endings.
645 h = h.getnormalized()
656 h = h.getnormalized()
646
657
647 # fast case first, no offsets, no fuzz
658 # fast case first, no offsets, no fuzz
648 old = h.old()
659 old = h.old()
649 # patch starts counting at 1 unless we are adding the file
660 # patch starts counting at 1 unless we are adding the file
650 if h.starta == 0:
661 if h.starta == 0:
651 start = 0
662 start = 0
652 else:
663 else:
653 start = h.starta + self.offset - 1
664 start = h.starta + self.offset - 1
654 orig_start = start
665 orig_start = start
655 # if there's skew we want to emit the "(offset %d lines)" even
666 # if there's skew we want to emit the "(offset %d lines)" even
656 # when the hunk cleanly applies at start + skew, so skip the
667 # when the hunk cleanly applies at start + skew, so skip the
657 # fast case code
668 # fast case code
658 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
669 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
659 if self.remove:
670 if self.remove:
660 self.backend.unlink(self.fname)
671 self.backend.unlink(self.fname)
661 else:
672 else:
662 self.lines[start : start + h.lena] = h.new()
673 self.lines[start : start + h.lena] = h.new()
663 self.offset += h.lenb - h.lena
674 self.offset += h.lenb - h.lena
664 self.dirty = True
675 self.dirty = True
665 return 0
676 return 0
666
677
667 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
678 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
668 self.hash = {}
679 self.hash = {}
669 for x, s in enumerate(self.lines):
680 for x, s in enumerate(self.lines):
670 self.hash.setdefault(s, []).append(x)
681 self.hash.setdefault(s, []).append(x)
671 if h.hunk[-1][0] != ' ':
682 if h.hunk[-1][0] != ' ':
672 # if the hunk tried to put something at the bottom of the file
683 # if the hunk tried to put something at the bottom of the file
673 # override the start line and use eof here
684 # override the start line and use eof here
674 search_start = len(self.lines)
685 search_start = len(self.lines)
675 else:
686 else:
676 search_start = orig_start + self.skew
687 search_start = orig_start + self.skew
677
688
678 for fuzzlen in xrange(3):
689 for fuzzlen in xrange(3):
679 for toponly in [True, False]:
690 for toponly in [True, False]:
680 old = h.old(fuzzlen, toponly)
691 old = h.old(fuzzlen, toponly)
681
692
682 cand = self.findlines(old[0][1:], search_start)
693 cand = self.findlines(old[0][1:], search_start)
683 for l in cand:
694 for l in cand:
684 if diffhelpers.testhunk(old, self.lines, l) == 0:
695 if diffhelpers.testhunk(old, self.lines, l) == 0:
685 newlines = h.new(fuzzlen, toponly)
696 newlines = h.new(fuzzlen, toponly)
686 self.lines[l : l + len(old)] = newlines
697 self.lines[l : l + len(old)] = newlines
687 self.offset += len(newlines) - len(old)
698 self.offset += len(newlines) - len(old)
688 self.skew = l - orig_start
699 self.skew = l - orig_start
689 self.dirty = True
700 self.dirty = True
690 offset = l - orig_start - fuzzlen
701 offset = l - orig_start - fuzzlen
691 if fuzzlen:
702 if fuzzlen:
692 msg = _("Hunk #%d succeeded at %d "
703 msg = _("Hunk #%d succeeded at %d "
693 "with fuzz %d "
704 "with fuzz %d "
694 "(offset %d lines).\n")
705 "(offset %d lines).\n")
695 self.printfile(True)
706 self.printfile(True)
696 self.ui.warn(msg %
707 self.ui.warn(msg %
697 (h.number, l + 1, fuzzlen, offset))
708 (h.number, l + 1, fuzzlen, offset))
698 else:
709 else:
699 msg = _("Hunk #%d succeeded at %d "
710 msg = _("Hunk #%d succeeded at %d "
700 "(offset %d lines).\n")
711 "(offset %d lines).\n")
701 self.ui.note(msg % (h.number, l + 1, offset))
712 self.ui.note(msg % (h.number, l + 1, offset))
702 return fuzzlen
713 return fuzzlen
703 self.printfile(True)
714 self.printfile(True)
704 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
715 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
705 self.rej.append(horig)
716 self.rej.append(horig)
706 return -1
717 return -1
707
718
708 def close(self):
719 def close(self):
709 if self.dirty:
720 if self.dirty:
710 self.writelines(self.fname, self.lines, self.mode)
721 self.writelines(self.fname, self.lines, self.mode)
711 self.write_rej()
722 self.write_rej()
712 return len(self.rej)
723 return len(self.rej)
713
724
714 class hunk(object):
725 class hunk(object):
715 def __init__(self, desc, num, lr, context):
726 def __init__(self, desc, num, lr, context):
716 self.number = num
727 self.number = num
717 self.desc = desc
728 self.desc = desc
718 self.hunk = [desc]
729 self.hunk = [desc]
719 self.a = []
730 self.a = []
720 self.b = []
731 self.b = []
721 self.starta = self.lena = None
732 self.starta = self.lena = None
722 self.startb = self.lenb = None
733 self.startb = self.lenb = None
723 if lr is not None:
734 if lr is not None:
724 if context:
735 if context:
725 self.read_context_hunk(lr)
736 self.read_context_hunk(lr)
726 else:
737 else:
727 self.read_unified_hunk(lr)
738 self.read_unified_hunk(lr)
728
739
729 def getnormalized(self):
740 def getnormalized(self):
730 """Return a copy with line endings normalized to LF."""
741 """Return a copy with line endings normalized to LF."""
731
742
732 def normalize(lines):
743 def normalize(lines):
733 nlines = []
744 nlines = []
734 for line in lines:
745 for line in lines:
735 if line.endswith('\r\n'):
746 if line.endswith('\r\n'):
736 line = line[:-2] + '\n'
747 line = line[:-2] + '\n'
737 nlines.append(line)
748 nlines.append(line)
738 return nlines
749 return nlines
739
750
740 # Dummy object, it is rebuilt manually
751 # Dummy object, it is rebuilt manually
741 nh = hunk(self.desc, self.number, None, None)
752 nh = hunk(self.desc, self.number, None, None)
742 nh.number = self.number
753 nh.number = self.number
743 nh.desc = self.desc
754 nh.desc = self.desc
744 nh.hunk = self.hunk
755 nh.hunk = self.hunk
745 nh.a = normalize(self.a)
756 nh.a = normalize(self.a)
746 nh.b = normalize(self.b)
757 nh.b = normalize(self.b)
747 nh.starta = self.starta
758 nh.starta = self.starta
748 nh.startb = self.startb
759 nh.startb = self.startb
749 nh.lena = self.lena
760 nh.lena = self.lena
750 nh.lenb = self.lenb
761 nh.lenb = self.lenb
751 return nh
762 return nh
752
763
753 def read_unified_hunk(self, lr):
764 def read_unified_hunk(self, lr):
754 m = unidesc.match(self.desc)
765 m = unidesc.match(self.desc)
755 if not m:
766 if not m:
756 raise PatchError(_("bad hunk #%d") % self.number)
767 raise PatchError(_("bad hunk #%d") % self.number)
757 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
768 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
758 if self.lena is None:
769 if self.lena is None:
759 self.lena = 1
770 self.lena = 1
760 else:
771 else:
761 self.lena = int(self.lena)
772 self.lena = int(self.lena)
762 if self.lenb is None:
773 if self.lenb is None:
763 self.lenb = 1
774 self.lenb = 1
764 else:
775 else:
765 self.lenb = int(self.lenb)
776 self.lenb = int(self.lenb)
766 self.starta = int(self.starta)
777 self.starta = int(self.starta)
767 self.startb = int(self.startb)
778 self.startb = int(self.startb)
768 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
779 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
769 # if we hit eof before finishing out the hunk, the last line will
780 # if we hit eof before finishing out the hunk, the last line will
770 # be zero length. Lets try to fix it up.
781 # be zero length. Lets try to fix it up.
771 while len(self.hunk[-1]) == 0:
782 while len(self.hunk[-1]) == 0:
772 del self.hunk[-1]
783 del self.hunk[-1]
773 del self.a[-1]
784 del self.a[-1]
774 del self.b[-1]
785 del self.b[-1]
775 self.lena -= 1
786 self.lena -= 1
776 self.lenb -= 1
787 self.lenb -= 1
777 self._fixnewline(lr)
788 self._fixnewline(lr)
778
789
779 def read_context_hunk(self, lr):
790 def read_context_hunk(self, lr):
780 self.desc = lr.readline()
791 self.desc = lr.readline()
781 m = contextdesc.match(self.desc)
792 m = contextdesc.match(self.desc)
782 if not m:
793 if not m:
783 raise PatchError(_("bad hunk #%d") % self.number)
794 raise PatchError(_("bad hunk #%d") % self.number)
784 foo, self.starta, foo2, aend, foo3 = m.groups()
795 foo, self.starta, foo2, aend, foo3 = m.groups()
785 self.starta = int(self.starta)
796 self.starta = int(self.starta)
786 if aend is None:
797 if aend is None:
787 aend = self.starta
798 aend = self.starta
788 self.lena = int(aend) - self.starta
799 self.lena = int(aend) - self.starta
789 if self.starta:
800 if self.starta:
790 self.lena += 1
801 self.lena += 1
791 for x in xrange(self.lena):
802 for x in xrange(self.lena):
792 l = lr.readline()
803 l = lr.readline()
793 if l.startswith('---'):
804 if l.startswith('---'):
794 # lines addition, old block is empty
805 # lines addition, old block is empty
795 lr.push(l)
806 lr.push(l)
796 break
807 break
797 s = l[2:]
808 s = l[2:]
798 if l.startswith('- ') or l.startswith('! '):
809 if l.startswith('- ') or l.startswith('! '):
799 u = '-' + s
810 u = '-' + s
800 elif l.startswith(' '):
811 elif l.startswith(' '):
801 u = ' ' + s
812 u = ' ' + s
802 else:
813 else:
803 raise PatchError(_("bad hunk #%d old text line %d") %
814 raise PatchError(_("bad hunk #%d old text line %d") %
804 (self.number, x))
815 (self.number, x))
805 self.a.append(u)
816 self.a.append(u)
806 self.hunk.append(u)
817 self.hunk.append(u)
807
818
808 l = lr.readline()
819 l = lr.readline()
809 if l.startswith('\ '):
820 if l.startswith('\ '):
810 s = self.a[-1][:-1]
821 s = self.a[-1][:-1]
811 self.a[-1] = s
822 self.a[-1] = s
812 self.hunk[-1] = s
823 self.hunk[-1] = s
813 l = lr.readline()
824 l = lr.readline()
814 m = contextdesc.match(l)
825 m = contextdesc.match(l)
815 if not m:
826 if not m:
816 raise PatchError(_("bad hunk #%d") % self.number)
827 raise PatchError(_("bad hunk #%d") % self.number)
817 foo, self.startb, foo2, bend, foo3 = m.groups()
828 foo, self.startb, foo2, bend, foo3 = m.groups()
818 self.startb = int(self.startb)
829 self.startb = int(self.startb)
819 if bend is None:
830 if bend is None:
820 bend = self.startb
831 bend = self.startb
821 self.lenb = int(bend) - self.startb
832 self.lenb = int(bend) - self.startb
822 if self.startb:
833 if self.startb:
823 self.lenb += 1
834 self.lenb += 1
824 hunki = 1
835 hunki = 1
825 for x in xrange(self.lenb):
836 for x in xrange(self.lenb):
826 l = lr.readline()
837 l = lr.readline()
827 if l.startswith('\ '):
838 if l.startswith('\ '):
828 # XXX: the only way to hit this is with an invalid line range.
839 # XXX: the only way to hit this is with an invalid line range.
829 # The no-eol marker is not counted in the line range, but I
840 # The no-eol marker is not counted in the line range, but I
830 # guess there are diff(1) out there which behave differently.
841 # guess there are diff(1) out there which behave differently.
831 s = self.b[-1][:-1]
842 s = self.b[-1][:-1]
832 self.b[-1] = s
843 self.b[-1] = s
833 self.hunk[hunki - 1] = s
844 self.hunk[hunki - 1] = s
834 continue
845 continue
835 if not l:
846 if not l:
836 # line deletions, new block is empty and we hit EOF
847 # line deletions, new block is empty and we hit EOF
837 lr.push(l)
848 lr.push(l)
838 break
849 break
839 s = l[2:]
850 s = l[2:]
840 if l.startswith('+ ') or l.startswith('! '):
851 if l.startswith('+ ') or l.startswith('! '):
841 u = '+' + s
852 u = '+' + s
842 elif l.startswith(' '):
853 elif l.startswith(' '):
843 u = ' ' + s
854 u = ' ' + s
844 elif len(self.b) == 0:
855 elif len(self.b) == 0:
845 # line deletions, new block is empty
856 # line deletions, new block is empty
846 lr.push(l)
857 lr.push(l)
847 break
858 break
848 else:
859 else:
849 raise PatchError(_("bad hunk #%d old text line %d") %
860 raise PatchError(_("bad hunk #%d old text line %d") %
850 (self.number, x))
861 (self.number, x))
851 self.b.append(s)
862 self.b.append(s)
852 while True:
863 while True:
853 if hunki >= len(self.hunk):
864 if hunki >= len(self.hunk):
854 h = ""
865 h = ""
855 else:
866 else:
856 h = self.hunk[hunki]
867 h = self.hunk[hunki]
857 hunki += 1
868 hunki += 1
858 if h == u:
869 if h == u:
859 break
870 break
860 elif h.startswith('-'):
871 elif h.startswith('-'):
861 continue
872 continue
862 else:
873 else:
863 self.hunk.insert(hunki - 1, u)
874 self.hunk.insert(hunki - 1, u)
864 break
875 break
865
876
866 if not self.a:
877 if not self.a:
867 # this happens when lines were only added to the hunk
878 # this happens when lines were only added to the hunk
868 for x in self.hunk:
879 for x in self.hunk:
869 if x.startswith('-') or x.startswith(' '):
880 if x.startswith('-') or x.startswith(' '):
870 self.a.append(x)
881 self.a.append(x)
871 if not self.b:
882 if not self.b:
872 # this happens when lines were only deleted from the hunk
883 # this happens when lines were only deleted from the hunk
873 for x in self.hunk:
884 for x in self.hunk:
874 if x.startswith('+') or x.startswith(' '):
885 if x.startswith('+') or x.startswith(' '):
875 self.b.append(x[1:])
886 self.b.append(x[1:])
876 # @@ -start,len +start,len @@
887 # @@ -start,len +start,len @@
877 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
888 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
878 self.startb, self.lenb)
889 self.startb, self.lenb)
879 self.hunk[0] = self.desc
890 self.hunk[0] = self.desc
880 self._fixnewline(lr)
891 self._fixnewline(lr)
881
892
882 def _fixnewline(self, lr):
893 def _fixnewline(self, lr):
883 l = lr.readline()
894 l = lr.readline()
884 if l.startswith('\ '):
895 if l.startswith('\ '):
885 diffhelpers.fix_newline(self.hunk, self.a, self.b)
896 diffhelpers.fix_newline(self.hunk, self.a, self.b)
886 else:
897 else:
887 lr.push(l)
898 lr.push(l)
888
899
889 def complete(self):
900 def complete(self):
890 return len(self.a) == self.lena and len(self.b) == self.lenb
901 return len(self.a) == self.lena and len(self.b) == self.lenb
891
902
892 def fuzzit(self, l, fuzz, toponly):
903 def fuzzit(self, l, fuzz, toponly):
893 # this removes context lines from the top and bottom of list 'l'. It
904 # this removes context lines from the top and bottom of list 'l'. It
894 # checks the hunk to make sure only context lines are removed, and then
905 # checks the hunk to make sure only context lines are removed, and then
895 # returns a new shortened list of lines.
906 # returns a new shortened list of lines.
896 fuzz = min(fuzz, len(l)-1)
907 fuzz = min(fuzz, len(l)-1)
897 if fuzz:
908 if fuzz:
898 top = 0
909 top = 0
899 bot = 0
910 bot = 0
900 hlen = len(self.hunk)
911 hlen = len(self.hunk)
901 for x in xrange(hlen - 1):
912 for x in xrange(hlen - 1):
902 # the hunk starts with the @@ line, so use x+1
913 # the hunk starts with the @@ line, so use x+1
903 if self.hunk[x + 1][0] == ' ':
914 if self.hunk[x + 1][0] == ' ':
904 top += 1
915 top += 1
905 else:
916 else:
906 break
917 break
907 if not toponly:
918 if not toponly:
908 for x in xrange(hlen - 1):
919 for x in xrange(hlen - 1):
909 if self.hunk[hlen - bot - 1][0] == ' ':
920 if self.hunk[hlen - bot - 1][0] == ' ':
910 bot += 1
921 bot += 1
911 else:
922 else:
912 break
923 break
913
924
914 # top and bot now count context in the hunk
925 # top and bot now count context in the hunk
915 # adjust them if either one is short
926 # adjust them if either one is short
916 context = max(top, bot, 3)
927 context = max(top, bot, 3)
917 if bot < context:
928 if bot < context:
918 bot = max(0, fuzz - (context - bot))
929 bot = max(0, fuzz - (context - bot))
919 else:
930 else:
920 bot = min(fuzz, bot)
931 bot = min(fuzz, bot)
921 if top < context:
932 if top < context:
922 top = max(0, fuzz - (context - top))
933 top = max(0, fuzz - (context - top))
923 else:
934 else:
924 top = min(fuzz, top)
935 top = min(fuzz, top)
925
936
926 return l[top:len(l)-bot]
937 return l[top:len(l)-bot]
927 return l
938 return l
928
939
929 def old(self, fuzz=0, toponly=False):
940 def old(self, fuzz=0, toponly=False):
930 return self.fuzzit(self.a, fuzz, toponly)
941 return self.fuzzit(self.a, fuzz, toponly)
931
942
932 def new(self, fuzz=0, toponly=False):
943 def new(self, fuzz=0, toponly=False):
933 return self.fuzzit(self.b, fuzz, toponly)
944 return self.fuzzit(self.b, fuzz, toponly)
934
945
935 class binhunk:
946 class binhunk:
936 'A binary patch file. Only understands literals so far.'
947 'A binary patch file. Only understands literals so far.'
937 def __init__(self, lr):
948 def __init__(self, lr):
938 self.text = None
949 self.text = None
939 self.hunk = ['GIT binary patch\n']
950 self.hunk = ['GIT binary patch\n']
940 self._read(lr)
951 self._read(lr)
941
952
942 def complete(self):
953 def complete(self):
943 return self.text is not None
954 return self.text is not None
944
955
945 def new(self):
956 def new(self):
946 return [self.text]
957 return [self.text]
947
958
948 def _read(self, lr):
959 def _read(self, lr):
949 line = lr.readline()
960 line = lr.readline()
950 self.hunk.append(line)
961 self.hunk.append(line)
951 while line and not line.startswith('literal '):
962 while line and not line.startswith('literal '):
952 line = lr.readline()
963 line = lr.readline()
953 self.hunk.append(line)
964 self.hunk.append(line)
954 if not line:
965 if not line:
955 raise PatchError(_('could not extract binary patch'))
966 raise PatchError(_('could not extract binary patch'))
956 size = int(line[8:].rstrip())
967 size = int(line[8:].rstrip())
957 dec = []
968 dec = []
958 line = lr.readline()
969 line = lr.readline()
959 self.hunk.append(line)
970 self.hunk.append(line)
960 while len(line) > 1:
971 while len(line) > 1:
961 l = line[0]
972 l = line[0]
962 if l <= 'Z' and l >= 'A':
973 if l <= 'Z' and l >= 'A':
963 l = ord(l) - ord('A') + 1
974 l = ord(l) - ord('A') + 1
964 else:
975 else:
965 l = ord(l) - ord('a') + 27
976 l = ord(l) - ord('a') + 27
966 dec.append(base85.b85decode(line[1:-1])[:l])
977 dec.append(base85.b85decode(line[1:-1])[:l])
967 line = lr.readline()
978 line = lr.readline()
968 self.hunk.append(line)
979 self.hunk.append(line)
969 text = zlib.decompress(''.join(dec))
980 text = zlib.decompress(''.join(dec))
970 if len(text) != size:
981 if len(text) != size:
971 raise PatchError(_('binary patch is %d bytes, not %d') %
982 raise PatchError(_('binary patch is %d bytes, not %d') %
972 len(text), size)
983 len(text), size)
973 self.text = text
984 self.text = text
974
985
975 def parsefilename(str):
986 def parsefilename(str):
976 # --- filename \t|space stuff
987 # --- filename \t|space stuff
977 s = str[4:].rstrip('\r\n')
988 s = str[4:].rstrip('\r\n')
978 i = s.find('\t')
989 i = s.find('\t')
979 if i < 0:
990 if i < 0:
980 i = s.find(' ')
991 i = s.find(' ')
981 if i < 0:
992 if i < 0:
982 return s
993 return s
983 return s[:i]
994 return s[:i]
984
995
985 def pathstrip(path, strip):
996 def pathstrip(path, strip):
986 pathlen = len(path)
997 pathlen = len(path)
987 i = 0
998 i = 0
988 if strip == 0:
999 if strip == 0:
989 return '', path.rstrip()
1000 return '', path.rstrip()
990 count = strip
1001 count = strip
991 while count > 0:
1002 while count > 0:
992 i = path.find('/', i)
1003 i = path.find('/', i)
993 if i == -1:
1004 if i == -1:
994 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1005 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
995 (count, strip, path))
1006 (count, strip, path))
996 i += 1
1007 i += 1
997 # consume '//' in the path
1008 # consume '//' in the path
998 while i < pathlen - 1 and path[i] == '/':
1009 while i < pathlen - 1 and path[i] == '/':
999 i += 1
1010 i += 1
1000 count -= 1
1011 count -= 1
1001 return path[:i].lstrip(), path[i:].rstrip()
1012 return path[:i].lstrip(), path[i:].rstrip()
1002
1013
1003 def selectfile(backend, afile_orig, bfile_orig, hunk, strip, gp):
1014 def selectfile(backend, afile_orig, bfile_orig, hunk, strip, gp):
1004 if gp:
1015 if gp:
1005 # Git patches do not play games. Excluding copies from the
1016 # Git patches do not play games. Excluding copies from the
1006 # following heuristic avoids a lot of confusion
1017 # following heuristic avoids a lot of confusion
1007 fname = pathstrip(gp.path, strip - 1)[1]
1018 fname = pathstrip(gp.path, strip - 1)[1]
1008 create = gp.op == 'ADD'
1019 create = gp.op in ('ADD', 'COPY', 'RENAME')
1009 remove = gp.op == 'DELETE'
1020 remove = gp.op == 'DELETE'
1010 missing = not create and not backend.exists(fname)
1021 missing = not create and not backend.exists(fname)
1011 return fname, missing, create, remove
1022 return fname, create, remove
1012 nulla = afile_orig == "/dev/null"
1023 nulla = afile_orig == "/dev/null"
1013 nullb = bfile_orig == "/dev/null"
1024 nullb = bfile_orig == "/dev/null"
1014 create = nulla and hunk.starta == 0 and hunk.lena == 0
1025 create = nulla and hunk.starta == 0 and hunk.lena == 0
1015 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1026 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1016 abase, afile = pathstrip(afile_orig, strip)
1027 abase, afile = pathstrip(afile_orig, strip)
1017 gooda = not nulla and backend.exists(afile)
1028 gooda = not nulla and backend.exists(afile)
1018 bbase, bfile = pathstrip(bfile_orig, strip)
1029 bbase, bfile = pathstrip(bfile_orig, strip)
1019 if afile == bfile:
1030 if afile == bfile:
1020 goodb = gooda
1031 goodb = gooda
1021 else:
1032 else:
1022 goodb = not nullb and backend.exists(bfile)
1033 goodb = not nullb and backend.exists(bfile)
1023 missing = not goodb and not gooda and not create
1034 missing = not goodb and not gooda and not create
1024
1035
1025 # some diff programs apparently produce patches where the afile is
1036 # some diff programs apparently produce patches where the afile is
1026 # not /dev/null, but afile starts with bfile
1037 # not /dev/null, but afile starts with bfile
1027 abasedir = afile[:afile.rfind('/') + 1]
1038 abasedir = afile[:afile.rfind('/') + 1]
1028 bbasedir = bfile[:bfile.rfind('/') + 1]
1039 bbasedir = bfile[:bfile.rfind('/') + 1]
1029 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1040 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1030 and hunk.starta == 0 and hunk.lena == 0):
1041 and hunk.starta == 0 and hunk.lena == 0):
1031 create = True
1042 create = True
1032 missing = False
1043 missing = False
1033
1044
1034 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1045 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1035 # diff is between a file and its backup. In this case, the original
1046 # diff is between a file and its backup. In this case, the original
1036 # file should be patched (see original mpatch code).
1047 # file should be patched (see original mpatch code).
1037 isbackup = (abase == bbase and bfile.startswith(afile))
1048 isbackup = (abase == bbase and bfile.startswith(afile))
1038 fname = None
1049 fname = None
1039 if not missing:
1050 if not missing:
1040 if gooda and goodb:
1051 if gooda and goodb:
1041 fname = isbackup and afile or bfile
1052 fname = isbackup and afile or bfile
1042 elif gooda:
1053 elif gooda:
1043 fname = afile
1054 fname = afile
1044
1055
1045 if not fname:
1056 if not fname:
1046 if not nullb:
1057 if not nullb:
1047 fname = isbackup and afile or bfile
1058 fname = isbackup and afile or bfile
1048 elif not nulla:
1059 elif not nulla:
1049 fname = afile
1060 fname = afile
1050 else:
1061 else:
1051 raise PatchError(_("undefined source and destination files"))
1062 raise PatchError(_("undefined source and destination files"))
1052
1063
1053 return fname, missing, create, remove
1064 return fname, create, remove
1054
1065
1055 def scangitpatch(lr, firstline):
1066 def scangitpatch(lr, firstline):
1056 """
1067 """
1057 Git patches can emit:
1068 Git patches can emit:
1058 - rename a to b
1069 - rename a to b
1059 - change b
1070 - change b
1060 - copy a to c
1071 - copy a to c
1061 - change c
1072 - change c
1062
1073
1063 We cannot apply this sequence as-is, the renamed 'a' could not be
1074 We cannot apply this sequence as-is, the renamed 'a' could not be
1064 found for it would have been renamed already. And we cannot copy
1075 found for it would have been renamed already. And we cannot copy
1065 from 'b' instead because 'b' would have been changed already. So
1076 from 'b' instead because 'b' would have been changed already. So
1066 we scan the git patch for copy and rename commands so we can
1077 we scan the git patch for copy and rename commands so we can
1067 perform the copies ahead of time.
1078 perform the copies ahead of time.
1068 """
1079 """
1069 pos = 0
1080 pos = 0
1070 try:
1081 try:
1071 pos = lr.fp.tell()
1082 pos = lr.fp.tell()
1072 fp = lr.fp
1083 fp = lr.fp
1073 except IOError:
1084 except IOError:
1074 fp = cStringIO.StringIO(lr.fp.read())
1085 fp = cStringIO.StringIO(lr.fp.read())
1075 gitlr = linereader(fp)
1086 gitlr = linereader(fp)
1076 gitlr.push(firstline)
1087 gitlr.push(firstline)
1077 gitpatches = readgitpatch(gitlr)
1088 gitpatches = readgitpatch(gitlr)
1078 fp.seek(pos)
1089 fp.seek(pos)
1079 return gitpatches
1090 return gitpatches
1080
1091
1081 def iterhunks(fp):
1092 def iterhunks(fp):
1082 """Read a patch and yield the following events:
1093 """Read a patch and yield the following events:
1083 - ("file", afile, bfile, firsthunk): select a new target file.
1094 - ("file", afile, bfile, firsthunk): select a new target file.
1084 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1095 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1085 "file" event.
1096 "file" event.
1086 - ("git", gitchanges): current diff is in git format, gitchanges
1097 - ("git", gitchanges): current diff is in git format, gitchanges
1087 maps filenames to gitpatch records. Unique event.
1098 maps filenames to gitpatch records. Unique event.
1088 """
1099 """
1089 afile = ""
1100 afile = ""
1090 bfile = ""
1101 bfile = ""
1091 state = None
1102 state = None
1092 hunknum = 0
1103 hunknum = 0
1093 emitfile = newfile = False
1104 emitfile = newfile = False
1094 gitpatches = None
1105 gitpatches = None
1095
1106
1096 # our states
1107 # our states
1097 BFILE = 1
1108 BFILE = 1
1098 context = None
1109 context = None
1099 lr = linereader(fp)
1110 lr = linereader(fp)
1100
1111
1101 while True:
1112 while True:
1102 x = lr.readline()
1113 x = lr.readline()
1103 if not x:
1114 if not x:
1104 break
1115 break
1105 if state == BFILE and (
1116 if state == BFILE and (
1106 (not context and x[0] == '@')
1117 (not context and x[0] == '@')
1107 or (context is not False and x.startswith('***************'))
1118 or (context is not False and x.startswith('***************'))
1108 or x.startswith('GIT binary patch')):
1119 or x.startswith('GIT binary patch')):
1109 gp = None
1120 gp = None
1110 if gitpatches and gitpatches[-1][0] == bfile:
1121 if gitpatches and gitpatches[-1][0] == bfile:
1111 gp = gitpatches.pop()[1]
1122 gp = gitpatches.pop()[1]
1112 if x.startswith('GIT binary patch'):
1123 if x.startswith('GIT binary patch'):
1113 h = binhunk(lr)
1124 h = binhunk(lr)
1114 else:
1125 else:
1115 if context is None and x.startswith('***************'):
1126 if context is None and x.startswith('***************'):
1116 context = True
1127 context = True
1117 h = hunk(x, hunknum + 1, lr, context)
1128 h = hunk(x, hunknum + 1, lr, context)
1118 hunknum += 1
1129 hunknum += 1
1119 if emitfile:
1130 if emitfile:
1120 emitfile = False
1131 emitfile = False
1121 yield 'file', (afile, bfile, h, gp)
1132 yield 'file', (afile, bfile, h, gp)
1122 yield 'hunk', h
1133 yield 'hunk', h
1123 elif x.startswith('diff --git'):
1134 elif x.startswith('diff --git'):
1124 m = gitre.match(x)
1135 m = gitre.match(x)
1125 if not m:
1136 if not m:
1126 continue
1137 continue
1127 if gitpatches is None:
1138 if gitpatches is None:
1128 # scan whole input for git metadata
1139 # scan whole input for git metadata
1129 gitpatches = [('b/' + gp.path, gp) for gp
1140 gitpatches = [('b/' + gp.path, gp) for gp
1130 in scangitpatch(lr, x)]
1141 in scangitpatch(lr, x)]
1131 yield 'git', [g[1] for g in gitpatches
1142 yield 'git', [g[1] for g in gitpatches
1132 if g[1].op in ('COPY', 'RENAME')]
1143 if g[1].op in ('COPY', 'RENAME')]
1133 gitpatches.reverse()
1144 gitpatches.reverse()
1134 afile = 'a/' + m.group(1)
1145 afile = 'a/' + m.group(1)
1135 bfile = 'b/' + m.group(2)
1146 bfile = 'b/' + m.group(2)
1136 while bfile != gitpatches[-1][0]:
1147 while bfile != gitpatches[-1][0]:
1137 gp = gitpatches.pop()[1]
1148 gp = gitpatches.pop()[1]
1138 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp)
1149 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp)
1139 gp = gitpatches[-1][1]
1150 gp = gitpatches[-1][1]
1140 # copy/rename + modify should modify target, not source
1151 # copy/rename + modify should modify target, not source
1141 if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode:
1152 if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode:
1142 afile = bfile
1153 afile = bfile
1143 newfile = True
1154 newfile = True
1144 elif x.startswith('---'):
1155 elif x.startswith('---'):
1145 # check for a unified diff
1156 # check for a unified diff
1146 l2 = lr.readline()
1157 l2 = lr.readline()
1147 if not l2.startswith('+++'):
1158 if not l2.startswith('+++'):
1148 lr.push(l2)
1159 lr.push(l2)
1149 continue
1160 continue
1150 newfile = True
1161 newfile = True
1151 context = False
1162 context = False
1152 afile = parsefilename(x)
1163 afile = parsefilename(x)
1153 bfile = parsefilename(l2)
1164 bfile = parsefilename(l2)
1154 elif x.startswith('***'):
1165 elif x.startswith('***'):
1155 # check for a context diff
1166 # check for a context diff
1156 l2 = lr.readline()
1167 l2 = lr.readline()
1157 if not l2.startswith('---'):
1168 if not l2.startswith('---'):
1158 lr.push(l2)
1169 lr.push(l2)
1159 continue
1170 continue
1160 l3 = lr.readline()
1171 l3 = lr.readline()
1161 lr.push(l3)
1172 lr.push(l3)
1162 if not l3.startswith("***************"):
1173 if not l3.startswith("***************"):
1163 lr.push(l2)
1174 lr.push(l2)
1164 continue
1175 continue
1165 newfile = True
1176 newfile = True
1166 context = True
1177 context = True
1167 afile = parsefilename(x)
1178 afile = parsefilename(x)
1168 bfile = parsefilename(l2)
1179 bfile = parsefilename(l2)
1169
1180
1170 if newfile:
1181 if newfile:
1171 newfile = False
1182 newfile = False
1172 emitfile = True
1183 emitfile = True
1173 state = BFILE
1184 state = BFILE
1174 hunknum = 0
1185 hunknum = 0
1175
1186
1176 while gitpatches:
1187 while gitpatches:
1177 gp = gitpatches.pop()[1]
1188 gp = gitpatches.pop()[1]
1178 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp)
1189 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp)
1179
1190
1180 def applydiff(ui, fp, changed, backend, strip=1, eolmode='strict'):
1191 def applydiff(ui, fp, changed, backend, store, strip=1, eolmode='strict'):
1181 """Reads a patch from fp and tries to apply it.
1192 """Reads a patch from fp and tries to apply it.
1182
1193
1183 The dict 'changed' is filled in with all of the filenames changed
1194 The dict 'changed' is filled in with all of the filenames changed
1184 by the patch. Returns 0 for a clean patch, -1 if any rejects were
1195 by the patch. Returns 0 for a clean patch, -1 if any rejects were
1185 found and 1 if there was any fuzz.
1196 found and 1 if there was any fuzz.
1186
1197
1187 If 'eolmode' is 'strict', the patch content and patched file are
1198 If 'eolmode' is 'strict', the patch content and patched file are
1188 read in binary mode. Otherwise, line endings are ignored when
1199 read in binary mode. Otherwise, line endings are ignored when
1189 patching then normalized according to 'eolmode'.
1200 patching then normalized according to 'eolmode'.
1190 """
1201 """
1191 return _applydiff(ui, fp, patchfile, backend, changed, strip=strip,
1202 return _applydiff(ui, fp, patchfile, backend, store, changed, strip=strip,
1192 eolmode=eolmode)
1203 eolmode=eolmode)
1193
1204
1194 def _applydiff(ui, fp, patcher, backend, changed, strip=1, eolmode='strict'):
1205 def _applydiff(ui, fp, patcher, backend, store, changed, strip=1,
1206 eolmode='strict'):
1195
1207
1196 def pstrip(p):
1208 def pstrip(p):
1197 return pathstrip(p, strip - 1)[1]
1209 return pathstrip(p, strip - 1)[1]
1198
1210
1199 rejects = 0
1211 rejects = 0
1200 err = 0
1212 err = 0
1201 current_file = None
1213 current_file = None
1202
1214
1203 for state, values in iterhunks(fp):
1215 for state, values in iterhunks(fp):
1204 if state == 'hunk':
1216 if state == 'hunk':
1205 if not current_file:
1217 if not current_file:
1206 continue
1218 continue
1207 ret = current_file.apply(values)
1219 ret = current_file.apply(values)
1208 if ret >= 0:
1220 if ret >= 0:
1209 changed.setdefault(current_file.fname, None)
1221 changed.setdefault(current_file.fname, None)
1210 if ret > 0:
1222 if ret > 0:
1211 err = 1
1223 err = 1
1212 elif state == 'file':
1224 elif state == 'file':
1213 if current_file:
1225 if current_file:
1214 rejects += current_file.close()
1226 rejects += current_file.close()
1215 current_file = None
1227 current_file = None
1216 afile, bfile, first_hunk, gp = values
1228 afile, bfile, first_hunk, gp = values
1229 copysource = None
1217 if gp:
1230 if gp:
1218 path = pstrip(gp.path)
1231 path = pstrip(gp.path)
1232 if gp.oldpath:
1233 copysource = pstrip(gp.oldpath)
1219 changed[path] = gp
1234 changed[path] = gp
1220 if gp.op == 'DELETE':
1235 if gp.op == 'DELETE':
1221 backend.unlink(path)
1236 backend.unlink(path)
1222 continue
1237 continue
1223 if gp.op == 'RENAME':
1238 if gp.op == 'RENAME':
1224 backend.unlink(pstrip(gp.oldpath))
1239 backend.unlink(copysource)
1225 if gp.mode and not first_hunk:
1240 if not first_hunk:
1226 data = None
1241 data, mode = None, None
1242 if gp.op in ('RENAME', 'COPY'):
1243 data, mode = store.getfile(copysource)
1244 if gp.mode:
1245 mode = gp.mode
1227 if gp.op == 'ADD':
1246 if gp.op == 'ADD':
1228 # Added files without content have no hunk and
1247 # Added files without content have no hunk and
1229 # must be created
1248 # must be created
1230 data = ''
1249 data = ''
1231 backend.setfile(path, data, gp.mode)
1250 if data or mode:
1251 if (gp.op in ('ADD', 'RENAME', 'COPY')
1252 and backend.exists(path)):
1253 raise PatchError(_("cannot create %s: destination "
1254 "already exists") % path)
1255 backend.setfile(path, data, mode, copysource)
1232 if not first_hunk:
1256 if not first_hunk:
1233 continue
1257 continue
1234 try:
1258 try:
1235 mode = gp and gp.mode or None
1259 mode = gp and gp.mode or None
1236 current_file, missing, create, remove = selectfile(
1260 current_file, create, remove = selectfile(
1237 backend, afile, bfile, first_hunk, strip, gp)
1261 backend, afile, bfile, first_hunk, strip, gp)
1238 current_file = patcher(ui, current_file, backend, mode,
1262 current_file = patcher(ui, current_file, backend, store, mode,
1239 create, remove, missing=missing,
1263 create, remove, eolmode=eolmode,
1240 eolmode=eolmode)
1264 copysource=copysource)
1241 except PatchError, inst:
1265 except PatchError, inst:
1242 ui.warn(str(inst) + '\n')
1266 ui.warn(str(inst) + '\n')
1243 current_file = None
1267 current_file = None
1244 rejects += 1
1268 rejects += 1
1245 continue
1269 continue
1246 elif state == 'git':
1270 elif state == 'git':
1247 for gp in values:
1271 for gp in values:
1248 backend.copy(pstrip(gp.oldpath), pstrip(gp.path))
1272 path = pstrip(gp.oldpath)
1273 data, mode = backend.getfile(path)
1274 store.setfile(path, data, mode)
1249 else:
1275 else:
1250 raise util.Abort(_('unsupported parser state: %s') % state)
1276 raise util.Abort(_('unsupported parser state: %s') % state)
1251
1277
1252 if current_file:
1278 if current_file:
1253 rejects += current_file.close()
1279 rejects += current_file.close()
1254
1280
1255 if rejects:
1281 if rejects:
1256 return -1
1282 return -1
1257 return err
1283 return err
1258
1284
1259 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1285 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1260 similarity):
1286 similarity):
1261 """use <patcher> to apply <patchname> to the working directory.
1287 """use <patcher> to apply <patchname> to the working directory.
1262 returns whether patch was applied with fuzz factor."""
1288 returns whether patch was applied with fuzz factor."""
1263
1289
1264 fuzz = False
1290 fuzz = False
1265 args = []
1291 args = []
1266 cwd = repo.root
1292 cwd = repo.root
1267 if cwd:
1293 if cwd:
1268 args.append('-d %s' % util.shellquote(cwd))
1294 args.append('-d %s' % util.shellquote(cwd))
1269 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1295 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1270 util.shellquote(patchname)))
1296 util.shellquote(patchname)))
1271 try:
1297 try:
1272 for line in fp:
1298 for line in fp:
1273 line = line.rstrip()
1299 line = line.rstrip()
1274 ui.note(line + '\n')
1300 ui.note(line + '\n')
1275 if line.startswith('patching file '):
1301 if line.startswith('patching file '):
1276 pf = util.parsepatchoutput(line)
1302 pf = util.parsepatchoutput(line)
1277 printed_file = False
1303 printed_file = False
1278 files.setdefault(pf, None)
1304 files.setdefault(pf, None)
1279 elif line.find('with fuzz') >= 0:
1305 elif line.find('with fuzz') >= 0:
1280 fuzz = True
1306 fuzz = True
1281 if not printed_file:
1307 if not printed_file:
1282 ui.warn(pf + '\n')
1308 ui.warn(pf + '\n')
1283 printed_file = True
1309 printed_file = True
1284 ui.warn(line + '\n')
1310 ui.warn(line + '\n')
1285 elif line.find('saving rejects to file') >= 0:
1311 elif line.find('saving rejects to file') >= 0:
1286 ui.warn(line + '\n')
1312 ui.warn(line + '\n')
1287 elif line.find('FAILED') >= 0:
1313 elif line.find('FAILED') >= 0:
1288 if not printed_file:
1314 if not printed_file:
1289 ui.warn(pf + '\n')
1315 ui.warn(pf + '\n')
1290 printed_file = True
1316 printed_file = True
1291 ui.warn(line + '\n')
1317 ui.warn(line + '\n')
1292 finally:
1318 finally:
1293 if files:
1319 if files:
1294 cfiles = list(files)
1320 cfiles = list(files)
1295 cwd = repo.getcwd()
1321 cwd = repo.getcwd()
1296 if cwd:
1322 if cwd:
1297 cfiles = [util.pathto(repo.root, cwd, f)
1323 cfiles = [util.pathto(repo.root, cwd, f)
1298 for f in cfile]
1324 for f in cfile]
1299 scmutil.addremove(repo, cfiles, similarity=similarity)
1325 scmutil.addremove(repo, cfiles, similarity=similarity)
1300 code = fp.close()
1326 code = fp.close()
1301 if code:
1327 if code:
1302 raise PatchError(_("patch command failed: %s") %
1328 raise PatchError(_("patch command failed: %s") %
1303 util.explainexit(code)[0])
1329 util.explainexit(code)[0])
1304 return fuzz
1330 return fuzz
1305
1331
1306 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1332 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1307 similarity=0):
1333 similarity=0):
1308 """use builtin patch to apply <patchobj> to the working directory.
1334 """use builtin patch to apply <patchobj> to the working directory.
1309 returns whether patch was applied with fuzz factor."""
1335 returns whether patch was applied with fuzz factor."""
1310
1336
1311 if files is None:
1337 if files is None:
1312 files = {}
1338 files = {}
1313 if eolmode is None:
1339 if eolmode is None:
1314 eolmode = ui.config('patch', 'eol', 'strict')
1340 eolmode = ui.config('patch', 'eol', 'strict')
1315 if eolmode.lower() not in eolmodes:
1341 if eolmode.lower() not in eolmodes:
1316 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1342 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1317 eolmode = eolmode.lower()
1343 eolmode = eolmode.lower()
1318
1344
1345 store = filestore()
1319 backend = workingbackend(ui, repo, similarity)
1346 backend = workingbackend(ui, repo, similarity)
1320 try:
1347 try:
1321 fp = open(patchobj, 'rb')
1348 fp = open(patchobj, 'rb')
1322 except TypeError:
1349 except TypeError:
1323 fp = patchobj
1350 fp = patchobj
1324 try:
1351 try:
1325 ret = applydiff(ui, fp, files, backend, strip=strip, eolmode=eolmode)
1352 ret = applydiff(ui, fp, files, backend, store, strip=strip,
1353 eolmode=eolmode)
1326 finally:
1354 finally:
1327 if fp != patchobj:
1355 if fp != patchobj:
1328 fp.close()
1356 fp.close()
1329 files.update(dict.fromkeys(backend.close()))
1357 files.update(dict.fromkeys(backend.close()))
1358 store.close()
1330 if ret < 0:
1359 if ret < 0:
1331 raise PatchError(_('patch failed to apply'))
1360 raise PatchError(_('patch failed to apply'))
1332 return ret > 0
1361 return ret > 0
1333
1362
1334 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1363 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1335 similarity=0):
1364 similarity=0):
1336 """Apply <patchname> to the working directory.
1365 """Apply <patchname> to the working directory.
1337
1366
1338 'eolmode' specifies how end of lines should be handled. It can be:
1367 'eolmode' specifies how end of lines should be handled. It can be:
1339 - 'strict': inputs are read in binary mode, EOLs are preserved
1368 - 'strict': inputs are read in binary mode, EOLs are preserved
1340 - 'crlf': EOLs are ignored when patching and reset to CRLF
1369 - 'crlf': EOLs are ignored when patching and reset to CRLF
1341 - 'lf': EOLs are ignored when patching and reset to LF
1370 - 'lf': EOLs are ignored when patching and reset to LF
1342 - None: get it from user settings, default to 'strict'
1371 - None: get it from user settings, default to 'strict'
1343 'eolmode' is ignored when using an external patcher program.
1372 'eolmode' is ignored when using an external patcher program.
1344
1373
1345 Returns whether patch was applied with fuzz factor.
1374 Returns whether patch was applied with fuzz factor.
1346 """
1375 """
1347 patcher = ui.config('ui', 'patch')
1376 patcher = ui.config('ui', 'patch')
1348 if files is None:
1377 if files is None:
1349 files = {}
1378 files = {}
1350 try:
1379 try:
1351 if patcher:
1380 if patcher:
1352 return _externalpatch(ui, repo, patcher, patchname, strip,
1381 return _externalpatch(ui, repo, patcher, patchname, strip,
1353 files, similarity)
1382 files, similarity)
1354 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1383 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1355 similarity)
1384 similarity)
1356 except PatchError, err:
1385 except PatchError, err:
1357 raise util.Abort(str(err))
1386 raise util.Abort(str(err))
1358
1387
1359 def changedfiles(ui, repo, patchpath, strip=1):
1388 def changedfiles(ui, repo, patchpath, strip=1):
1360 backend = fsbackend(ui, repo.root)
1389 backend = fsbackend(ui, repo.root)
1361 fp = open(patchpath, 'rb')
1390 fp = open(patchpath, 'rb')
1362 try:
1391 try:
1363 changed = set()
1392 changed = set()
1364 for state, values in iterhunks(fp):
1393 for state, values in iterhunks(fp):
1365 if state == 'file':
1394 if state == 'file':
1366 afile, bfile, first_hunk, gp = values
1395 afile, bfile, first_hunk, gp = values
1367 if gp:
1396 if gp:
1368 changed.add(pathstrip(gp.path, strip - 1)[1])
1397 changed.add(pathstrip(gp.path, strip - 1)[1])
1369 if gp.op == 'RENAME':
1398 if gp.op == 'RENAME':
1370 changed.add(pathstrip(gp.oldpath, strip - 1)[1])
1399 changed.add(pathstrip(gp.oldpath, strip - 1)[1])
1371 if not first_hunk:
1400 if not first_hunk:
1372 continue
1401 continue
1373 current_file, missing, create, remove = selectfile(
1402 current_file, create, remove = selectfile(
1374 backend, afile, bfile, first_hunk, strip, gp)
1403 backend, afile, bfile, first_hunk, strip, gp)
1375 changed.add(current_file)
1404 changed.add(current_file)
1376 elif state not in ('hunk', 'git'):
1405 elif state not in ('hunk', 'git'):
1377 raise util.Abort(_('unsupported parser state: %s') % state)
1406 raise util.Abort(_('unsupported parser state: %s') % state)
1378 return changed
1407 return changed
1379 finally:
1408 finally:
1380 fp.close()
1409 fp.close()
1381
1410
1382 def b85diff(to, tn):
1411 def b85diff(to, tn):
1383 '''print base85-encoded binary diff'''
1412 '''print base85-encoded binary diff'''
1384 def gitindex(text):
1413 def gitindex(text):
1385 if not text:
1414 if not text:
1386 return hex(nullid)
1415 return hex(nullid)
1387 l = len(text)
1416 l = len(text)
1388 s = util.sha1('blob %d\0' % l)
1417 s = util.sha1('blob %d\0' % l)
1389 s.update(text)
1418 s.update(text)
1390 return s.hexdigest()
1419 return s.hexdigest()
1391
1420
1392 def fmtline(line):
1421 def fmtline(line):
1393 l = len(line)
1422 l = len(line)
1394 if l <= 26:
1423 if l <= 26:
1395 l = chr(ord('A') + l - 1)
1424 l = chr(ord('A') + l - 1)
1396 else:
1425 else:
1397 l = chr(l - 26 + ord('a') - 1)
1426 l = chr(l - 26 + ord('a') - 1)
1398 return '%c%s\n' % (l, base85.b85encode(line, True))
1427 return '%c%s\n' % (l, base85.b85encode(line, True))
1399
1428
1400 def chunk(text, csize=52):
1429 def chunk(text, csize=52):
1401 l = len(text)
1430 l = len(text)
1402 i = 0
1431 i = 0
1403 while i < l:
1432 while i < l:
1404 yield text[i:i + csize]
1433 yield text[i:i + csize]
1405 i += csize
1434 i += csize
1406
1435
1407 tohash = gitindex(to)
1436 tohash = gitindex(to)
1408 tnhash = gitindex(tn)
1437 tnhash = gitindex(tn)
1409 if tohash == tnhash:
1438 if tohash == tnhash:
1410 return ""
1439 return ""
1411
1440
1412 # TODO: deltas
1441 # TODO: deltas
1413 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1442 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1414 (tohash, tnhash, len(tn))]
1443 (tohash, tnhash, len(tn))]
1415 for l in chunk(zlib.compress(tn)):
1444 for l in chunk(zlib.compress(tn)):
1416 ret.append(fmtline(l))
1445 ret.append(fmtline(l))
1417 ret.append('\n')
1446 ret.append('\n')
1418 return ''.join(ret)
1447 return ''.join(ret)
1419
1448
1420 class GitDiffRequired(Exception):
1449 class GitDiffRequired(Exception):
1421 pass
1450 pass
1422
1451
1423 def diffopts(ui, opts=None, untrusted=False):
1452 def diffopts(ui, opts=None, untrusted=False):
1424 def get(key, name=None, getter=ui.configbool):
1453 def get(key, name=None, getter=ui.configbool):
1425 return ((opts and opts.get(key)) or
1454 return ((opts and opts.get(key)) or
1426 getter('diff', name or key, None, untrusted=untrusted))
1455 getter('diff', name or key, None, untrusted=untrusted))
1427 return mdiff.diffopts(
1456 return mdiff.diffopts(
1428 text=opts and opts.get('text'),
1457 text=opts and opts.get('text'),
1429 git=get('git'),
1458 git=get('git'),
1430 nodates=get('nodates'),
1459 nodates=get('nodates'),
1431 showfunc=get('show_function', 'showfunc'),
1460 showfunc=get('show_function', 'showfunc'),
1432 ignorews=get('ignore_all_space', 'ignorews'),
1461 ignorews=get('ignore_all_space', 'ignorews'),
1433 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1462 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1434 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1463 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1435 context=get('unified', getter=ui.config))
1464 context=get('unified', getter=ui.config))
1436
1465
1437 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1466 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1438 losedatafn=None, prefix=''):
1467 losedatafn=None, prefix=''):
1439 '''yields diff of changes to files between two nodes, or node and
1468 '''yields diff of changes to files between two nodes, or node and
1440 working directory.
1469 working directory.
1441
1470
1442 if node1 is None, use first dirstate parent instead.
1471 if node1 is None, use first dirstate parent instead.
1443 if node2 is None, compare node1 with working directory.
1472 if node2 is None, compare node1 with working directory.
1444
1473
1445 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1474 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1446 every time some change cannot be represented with the current
1475 every time some change cannot be represented with the current
1447 patch format. Return False to upgrade to git patch format, True to
1476 patch format. Return False to upgrade to git patch format, True to
1448 accept the loss or raise an exception to abort the diff. It is
1477 accept the loss or raise an exception to abort the diff. It is
1449 called with the name of current file being diffed as 'fn'. If set
1478 called with the name of current file being diffed as 'fn'. If set
1450 to None, patches will always be upgraded to git format when
1479 to None, patches will always be upgraded to git format when
1451 necessary.
1480 necessary.
1452
1481
1453 prefix is a filename prefix that is prepended to all filenames on
1482 prefix is a filename prefix that is prepended to all filenames on
1454 display (used for subrepos).
1483 display (used for subrepos).
1455 '''
1484 '''
1456
1485
1457 if opts is None:
1486 if opts is None:
1458 opts = mdiff.defaultopts
1487 opts = mdiff.defaultopts
1459
1488
1460 if not node1 and not node2:
1489 if not node1 and not node2:
1461 node1 = repo.dirstate.p1()
1490 node1 = repo.dirstate.p1()
1462
1491
1463 def lrugetfilectx():
1492 def lrugetfilectx():
1464 cache = {}
1493 cache = {}
1465 order = []
1494 order = []
1466 def getfilectx(f, ctx):
1495 def getfilectx(f, ctx):
1467 fctx = ctx.filectx(f, filelog=cache.get(f))
1496 fctx = ctx.filectx(f, filelog=cache.get(f))
1468 if f not in cache:
1497 if f not in cache:
1469 if len(cache) > 20:
1498 if len(cache) > 20:
1470 del cache[order.pop(0)]
1499 del cache[order.pop(0)]
1471 cache[f] = fctx.filelog()
1500 cache[f] = fctx.filelog()
1472 else:
1501 else:
1473 order.remove(f)
1502 order.remove(f)
1474 order.append(f)
1503 order.append(f)
1475 return fctx
1504 return fctx
1476 return getfilectx
1505 return getfilectx
1477 getfilectx = lrugetfilectx()
1506 getfilectx = lrugetfilectx()
1478
1507
1479 ctx1 = repo[node1]
1508 ctx1 = repo[node1]
1480 ctx2 = repo[node2]
1509 ctx2 = repo[node2]
1481
1510
1482 if not changes:
1511 if not changes:
1483 changes = repo.status(ctx1, ctx2, match=match)
1512 changes = repo.status(ctx1, ctx2, match=match)
1484 modified, added, removed = changes[:3]
1513 modified, added, removed = changes[:3]
1485
1514
1486 if not modified and not added and not removed:
1515 if not modified and not added and not removed:
1487 return []
1516 return []
1488
1517
1489 revs = None
1518 revs = None
1490 if not repo.ui.quiet:
1519 if not repo.ui.quiet:
1491 hexfunc = repo.ui.debugflag and hex or short
1520 hexfunc = repo.ui.debugflag and hex or short
1492 revs = [hexfunc(node) for node in [node1, node2] if node]
1521 revs = [hexfunc(node) for node in [node1, node2] if node]
1493
1522
1494 copy = {}
1523 copy = {}
1495 if opts.git or opts.upgrade:
1524 if opts.git or opts.upgrade:
1496 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1525 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1497
1526
1498 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1527 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1499 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1528 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1500 if opts.upgrade and not opts.git:
1529 if opts.upgrade and not opts.git:
1501 try:
1530 try:
1502 def losedata(fn):
1531 def losedata(fn):
1503 if not losedatafn or not losedatafn(fn=fn):
1532 if not losedatafn or not losedatafn(fn=fn):
1504 raise GitDiffRequired()
1533 raise GitDiffRequired()
1505 # Buffer the whole output until we are sure it can be generated
1534 # Buffer the whole output until we are sure it can be generated
1506 return list(difffn(opts.copy(git=False), losedata))
1535 return list(difffn(opts.copy(git=False), losedata))
1507 except GitDiffRequired:
1536 except GitDiffRequired:
1508 return difffn(opts.copy(git=True), None)
1537 return difffn(opts.copy(git=True), None)
1509 else:
1538 else:
1510 return difffn(opts, None)
1539 return difffn(opts, None)
1511
1540
1512 def difflabel(func, *args, **kw):
1541 def difflabel(func, *args, **kw):
1513 '''yields 2-tuples of (output, label) based on the output of func()'''
1542 '''yields 2-tuples of (output, label) based on the output of func()'''
1514 prefixes = [('diff', 'diff.diffline'),
1543 prefixes = [('diff', 'diff.diffline'),
1515 ('copy', 'diff.extended'),
1544 ('copy', 'diff.extended'),
1516 ('rename', 'diff.extended'),
1545 ('rename', 'diff.extended'),
1517 ('old', 'diff.extended'),
1546 ('old', 'diff.extended'),
1518 ('new', 'diff.extended'),
1547 ('new', 'diff.extended'),
1519 ('deleted', 'diff.extended'),
1548 ('deleted', 'diff.extended'),
1520 ('---', 'diff.file_a'),
1549 ('---', 'diff.file_a'),
1521 ('+++', 'diff.file_b'),
1550 ('+++', 'diff.file_b'),
1522 ('@@', 'diff.hunk'),
1551 ('@@', 'diff.hunk'),
1523 ('-', 'diff.deleted'),
1552 ('-', 'diff.deleted'),
1524 ('+', 'diff.inserted')]
1553 ('+', 'diff.inserted')]
1525
1554
1526 for chunk in func(*args, **kw):
1555 for chunk in func(*args, **kw):
1527 lines = chunk.split('\n')
1556 lines = chunk.split('\n')
1528 for i, line in enumerate(lines):
1557 for i, line in enumerate(lines):
1529 if i != 0:
1558 if i != 0:
1530 yield ('\n', '')
1559 yield ('\n', '')
1531 stripline = line
1560 stripline = line
1532 if line and line[0] in '+-':
1561 if line and line[0] in '+-':
1533 # highlight trailing whitespace, but only in changed lines
1562 # highlight trailing whitespace, but only in changed lines
1534 stripline = line.rstrip()
1563 stripline = line.rstrip()
1535 for prefix, label in prefixes:
1564 for prefix, label in prefixes:
1536 if stripline.startswith(prefix):
1565 if stripline.startswith(prefix):
1537 yield (stripline, label)
1566 yield (stripline, label)
1538 break
1567 break
1539 else:
1568 else:
1540 yield (line, '')
1569 yield (line, '')
1541 if line != stripline:
1570 if line != stripline:
1542 yield (line[len(stripline):], 'diff.trailingwhitespace')
1571 yield (line[len(stripline):], 'diff.trailingwhitespace')
1543
1572
1544 def diffui(*args, **kw):
1573 def diffui(*args, **kw):
1545 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1574 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1546 return difflabel(diff, *args, **kw)
1575 return difflabel(diff, *args, **kw)
1547
1576
1548
1577
1549 def _addmodehdr(header, omode, nmode):
1578 def _addmodehdr(header, omode, nmode):
1550 if omode != nmode:
1579 if omode != nmode:
1551 header.append('old mode %s\n' % omode)
1580 header.append('old mode %s\n' % omode)
1552 header.append('new mode %s\n' % nmode)
1581 header.append('new mode %s\n' % nmode)
1553
1582
1554 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1583 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1555 copy, getfilectx, opts, losedatafn, prefix):
1584 copy, getfilectx, opts, losedatafn, prefix):
1556
1585
1557 def join(f):
1586 def join(f):
1558 return os.path.join(prefix, f)
1587 return os.path.join(prefix, f)
1559
1588
1560 date1 = util.datestr(ctx1.date())
1589 date1 = util.datestr(ctx1.date())
1561 man1 = ctx1.manifest()
1590 man1 = ctx1.manifest()
1562
1591
1563 gone = set()
1592 gone = set()
1564 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1593 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1565
1594
1566 copyto = dict([(v, k) for k, v in copy.items()])
1595 copyto = dict([(v, k) for k, v in copy.items()])
1567
1596
1568 if opts.git:
1597 if opts.git:
1569 revs = None
1598 revs = None
1570
1599
1571 for f in sorted(modified + added + removed):
1600 for f in sorted(modified + added + removed):
1572 to = None
1601 to = None
1573 tn = None
1602 tn = None
1574 dodiff = True
1603 dodiff = True
1575 header = []
1604 header = []
1576 if f in man1:
1605 if f in man1:
1577 to = getfilectx(f, ctx1).data()
1606 to = getfilectx(f, ctx1).data()
1578 if f not in removed:
1607 if f not in removed:
1579 tn = getfilectx(f, ctx2).data()
1608 tn = getfilectx(f, ctx2).data()
1580 a, b = f, f
1609 a, b = f, f
1581 if opts.git or losedatafn:
1610 if opts.git or losedatafn:
1582 if f in added:
1611 if f in added:
1583 mode = gitmode[ctx2.flags(f)]
1612 mode = gitmode[ctx2.flags(f)]
1584 if f in copy or f in copyto:
1613 if f in copy or f in copyto:
1585 if opts.git:
1614 if opts.git:
1586 if f in copy:
1615 if f in copy:
1587 a = copy[f]
1616 a = copy[f]
1588 else:
1617 else:
1589 a = copyto[f]
1618 a = copyto[f]
1590 omode = gitmode[man1.flags(a)]
1619 omode = gitmode[man1.flags(a)]
1591 _addmodehdr(header, omode, mode)
1620 _addmodehdr(header, omode, mode)
1592 if a in removed and a not in gone:
1621 if a in removed and a not in gone:
1593 op = 'rename'
1622 op = 'rename'
1594 gone.add(a)
1623 gone.add(a)
1595 else:
1624 else:
1596 op = 'copy'
1625 op = 'copy'
1597 header.append('%s from %s\n' % (op, join(a)))
1626 header.append('%s from %s\n' % (op, join(a)))
1598 header.append('%s to %s\n' % (op, join(f)))
1627 header.append('%s to %s\n' % (op, join(f)))
1599 to = getfilectx(a, ctx1).data()
1628 to = getfilectx(a, ctx1).data()
1600 else:
1629 else:
1601 losedatafn(f)
1630 losedatafn(f)
1602 else:
1631 else:
1603 if opts.git:
1632 if opts.git:
1604 header.append('new file mode %s\n' % mode)
1633 header.append('new file mode %s\n' % mode)
1605 elif ctx2.flags(f):
1634 elif ctx2.flags(f):
1606 losedatafn(f)
1635 losedatafn(f)
1607 # In theory, if tn was copied or renamed we should check
1636 # In theory, if tn was copied or renamed we should check
1608 # if the source is binary too but the copy record already
1637 # if the source is binary too but the copy record already
1609 # forces git mode.
1638 # forces git mode.
1610 if util.binary(tn):
1639 if util.binary(tn):
1611 if opts.git:
1640 if opts.git:
1612 dodiff = 'binary'
1641 dodiff = 'binary'
1613 else:
1642 else:
1614 losedatafn(f)
1643 losedatafn(f)
1615 if not opts.git and not tn:
1644 if not opts.git and not tn:
1616 # regular diffs cannot represent new empty file
1645 # regular diffs cannot represent new empty file
1617 losedatafn(f)
1646 losedatafn(f)
1618 elif f in removed:
1647 elif f in removed:
1619 if opts.git:
1648 if opts.git:
1620 # have we already reported a copy above?
1649 # have we already reported a copy above?
1621 if ((f in copy and copy[f] in added
1650 if ((f in copy and copy[f] in added
1622 and copyto[copy[f]] == f) or
1651 and copyto[copy[f]] == f) or
1623 (f in copyto and copyto[f] in added
1652 (f in copyto and copyto[f] in added
1624 and copy[copyto[f]] == f)):
1653 and copy[copyto[f]] == f)):
1625 dodiff = False
1654 dodiff = False
1626 else:
1655 else:
1627 header.append('deleted file mode %s\n' %
1656 header.append('deleted file mode %s\n' %
1628 gitmode[man1.flags(f)])
1657 gitmode[man1.flags(f)])
1629 elif not to or util.binary(to):
1658 elif not to or util.binary(to):
1630 # regular diffs cannot represent empty file deletion
1659 # regular diffs cannot represent empty file deletion
1631 losedatafn(f)
1660 losedatafn(f)
1632 else:
1661 else:
1633 oflag = man1.flags(f)
1662 oflag = man1.flags(f)
1634 nflag = ctx2.flags(f)
1663 nflag = ctx2.flags(f)
1635 binary = util.binary(to) or util.binary(tn)
1664 binary = util.binary(to) or util.binary(tn)
1636 if opts.git:
1665 if opts.git:
1637 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1666 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1638 if binary:
1667 if binary:
1639 dodiff = 'binary'
1668 dodiff = 'binary'
1640 elif binary or nflag != oflag:
1669 elif binary or nflag != oflag:
1641 losedatafn(f)
1670 losedatafn(f)
1642 if opts.git:
1671 if opts.git:
1643 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1672 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1644
1673
1645 if dodiff:
1674 if dodiff:
1646 if dodiff == 'binary':
1675 if dodiff == 'binary':
1647 text = b85diff(to, tn)
1676 text = b85diff(to, tn)
1648 else:
1677 else:
1649 text = mdiff.unidiff(to, date1,
1678 text = mdiff.unidiff(to, date1,
1650 # ctx2 date may be dynamic
1679 # ctx2 date may be dynamic
1651 tn, util.datestr(ctx2.date()),
1680 tn, util.datestr(ctx2.date()),
1652 join(a), join(b), revs, opts=opts)
1681 join(a), join(b), revs, opts=opts)
1653 if header and (text or len(header) > 1):
1682 if header and (text or len(header) > 1):
1654 yield ''.join(header)
1683 yield ''.join(header)
1655 if text:
1684 if text:
1656 yield text
1685 yield text
1657
1686
1658 def diffstatsum(stats):
1687 def diffstatsum(stats):
1659 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1688 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1660 for f, a, r, b in stats:
1689 for f, a, r, b in stats:
1661 maxfile = max(maxfile, encoding.colwidth(f))
1690 maxfile = max(maxfile, encoding.colwidth(f))
1662 maxtotal = max(maxtotal, a + r)
1691 maxtotal = max(maxtotal, a + r)
1663 addtotal += a
1692 addtotal += a
1664 removetotal += r
1693 removetotal += r
1665 binary = binary or b
1694 binary = binary or b
1666
1695
1667 return maxfile, maxtotal, addtotal, removetotal, binary
1696 return maxfile, maxtotal, addtotal, removetotal, binary
1668
1697
1669 def diffstatdata(lines):
1698 def diffstatdata(lines):
1670 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1699 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1671
1700
1672 results = []
1701 results = []
1673 filename, adds, removes = None, 0, 0
1702 filename, adds, removes = None, 0, 0
1674
1703
1675 def addresult():
1704 def addresult():
1676 if filename:
1705 if filename:
1677 isbinary = adds == 0 and removes == 0
1706 isbinary = adds == 0 and removes == 0
1678 results.append((filename, adds, removes, isbinary))
1707 results.append((filename, adds, removes, isbinary))
1679
1708
1680 for line in lines:
1709 for line in lines:
1681 if line.startswith('diff'):
1710 if line.startswith('diff'):
1682 addresult()
1711 addresult()
1683 # set numbers to 0 anyway when starting new file
1712 # set numbers to 0 anyway when starting new file
1684 adds, removes = 0, 0
1713 adds, removes = 0, 0
1685 if line.startswith('diff --git'):
1714 if line.startswith('diff --git'):
1686 filename = gitre.search(line).group(1)
1715 filename = gitre.search(line).group(1)
1687 elif line.startswith('diff -r'):
1716 elif line.startswith('diff -r'):
1688 # format: "diff -r ... -r ... filename"
1717 # format: "diff -r ... -r ... filename"
1689 filename = diffre.search(line).group(1)
1718 filename = diffre.search(line).group(1)
1690 elif line.startswith('+') and not line.startswith('+++'):
1719 elif line.startswith('+') and not line.startswith('+++'):
1691 adds += 1
1720 adds += 1
1692 elif line.startswith('-') and not line.startswith('---'):
1721 elif line.startswith('-') and not line.startswith('---'):
1693 removes += 1
1722 removes += 1
1694 addresult()
1723 addresult()
1695 return results
1724 return results
1696
1725
1697 def diffstat(lines, width=80, git=False):
1726 def diffstat(lines, width=80, git=False):
1698 output = []
1727 output = []
1699 stats = diffstatdata(lines)
1728 stats = diffstatdata(lines)
1700 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1729 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1701
1730
1702 countwidth = len(str(maxtotal))
1731 countwidth = len(str(maxtotal))
1703 if hasbinary and countwidth < 3:
1732 if hasbinary and countwidth < 3:
1704 countwidth = 3
1733 countwidth = 3
1705 graphwidth = width - countwidth - maxname - 6
1734 graphwidth = width - countwidth - maxname - 6
1706 if graphwidth < 10:
1735 if graphwidth < 10:
1707 graphwidth = 10
1736 graphwidth = 10
1708
1737
1709 def scale(i):
1738 def scale(i):
1710 if maxtotal <= graphwidth:
1739 if maxtotal <= graphwidth:
1711 return i
1740 return i
1712 # If diffstat runs out of room it doesn't print anything,
1741 # If diffstat runs out of room it doesn't print anything,
1713 # which isn't very useful, so always print at least one + or -
1742 # which isn't very useful, so always print at least one + or -
1714 # if there were at least some changes.
1743 # if there were at least some changes.
1715 return max(i * graphwidth // maxtotal, int(bool(i)))
1744 return max(i * graphwidth // maxtotal, int(bool(i)))
1716
1745
1717 for filename, adds, removes, isbinary in stats:
1746 for filename, adds, removes, isbinary in stats:
1718 if git and isbinary:
1747 if git and isbinary:
1719 count = 'Bin'
1748 count = 'Bin'
1720 else:
1749 else:
1721 count = adds + removes
1750 count = adds + removes
1722 pluses = '+' * scale(adds)
1751 pluses = '+' * scale(adds)
1723 minuses = '-' * scale(removes)
1752 minuses = '-' * scale(removes)
1724 output.append(' %s%s | %*s %s%s\n' %
1753 output.append(' %s%s | %*s %s%s\n' %
1725 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1754 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1726 countwidth, count, pluses, minuses))
1755 countwidth, count, pluses, minuses))
1727
1756
1728 if stats:
1757 if stats:
1729 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1758 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1730 % (len(stats), totaladds, totalremoves))
1759 % (len(stats), totaladds, totalremoves))
1731
1760
1732 return ''.join(output)
1761 return ''.join(output)
1733
1762
1734 def diffstatui(*args, **kw):
1763 def diffstatui(*args, **kw):
1735 '''like diffstat(), but yields 2-tuples of (output, label) for
1764 '''like diffstat(), but yields 2-tuples of (output, label) for
1736 ui.write()
1765 ui.write()
1737 '''
1766 '''
1738
1767
1739 for line in diffstat(*args, **kw).splitlines():
1768 for line in diffstat(*args, **kw).splitlines():
1740 if line and line[-1] in '+-':
1769 if line and line[-1] in '+-':
1741 name, graph = line.rsplit(' ', 1)
1770 name, graph = line.rsplit(' ', 1)
1742 yield (name + ' ', '')
1771 yield (name + ' ', '')
1743 m = re.search(r'\++', graph)
1772 m = re.search(r'\++', graph)
1744 if m:
1773 if m:
1745 yield (m.group(0), 'diffstat.inserted')
1774 yield (m.group(0), 'diffstat.inserted')
1746 m = re.search(r'-+', graph)
1775 m = re.search(r'-+', graph)
1747 if m:
1776 if m:
1748 yield (m.group(0), 'diffstat.deleted')
1777 yield (m.group(0), 'diffstat.deleted')
1749 else:
1778 else:
1750 yield (line, '')
1779 yield (line, '')
1751 yield ('\n', '')
1780 yield ('\n', '')
@@ -1,443 +1,445 b''
1
1
2 $ hg init
2 $ hg init
3
3
4 New file:
4 New file:
5
5
6 $ hg import -d "1000000 0" -mnew - <<EOF
6 $ hg import -d "1000000 0" -mnew - <<EOF
7 > diff --git a/new b/new
7 > diff --git a/new b/new
8 > new file mode 100644
8 > new file mode 100644
9 > index 0000000..7898192
9 > index 0000000..7898192
10 > --- /dev/null
10 > --- /dev/null
11 > +++ b/new
11 > +++ b/new
12 > @@ -0,0 +1 @@
12 > @@ -0,0 +1 @@
13 > +a
13 > +a
14 > EOF
14 > EOF
15 applying patch from stdin
15 applying patch from stdin
16
16
17 $ hg tip -q
17 $ hg tip -q
18 0:ae3ee40d2079
18 0:ae3ee40d2079
19
19
20 New empty file:
20 New empty file:
21
21
22 $ hg import -d "1000000 0" -mempty - <<EOF
22 $ hg import -d "1000000 0" -mempty - <<EOF
23 > diff --git a/empty b/empty
23 > diff --git a/empty b/empty
24 > new file mode 100644
24 > new file mode 100644
25 > EOF
25 > EOF
26 applying patch from stdin
26 applying patch from stdin
27
27
28 $ hg tip -q
28 $ hg tip -q
29 1:ab199dc869b5
29 1:ab199dc869b5
30
30
31 $ hg locate empty
31 $ hg locate empty
32 empty
32 empty
33
33
34 chmod +x:
34 chmod +x:
35
35
36 $ hg import -d "1000000 0" -msetx - <<EOF
36 $ hg import -d "1000000 0" -msetx - <<EOF
37 > diff --git a/new b/new
37 > diff --git a/new b/new
38 > old mode 100644
38 > old mode 100644
39 > new mode 100755
39 > new mode 100755
40 > EOF
40 > EOF
41 applying patch from stdin
41 applying patch from stdin
42
42
43 $ hg tip -q
43 $ hg tip -q
44 2:3a34410f282e
44 2:3a34410f282e
45
45
46 $ test -x new
46 $ test -x new
47
47
48 Copy:
48 Copy:
49
49
50 $ hg import -d "1000000 0" -mcopy - <<EOF
50 $ hg import -d "1000000 0" -mcopy - <<EOF
51 > diff --git a/new b/copy
51 > diff --git a/new b/copy
52 > old mode 100755
52 > old mode 100755
53 > new mode 100644
53 > new mode 100644
54 > similarity index 100%
54 > similarity index 100%
55 > copy from new
55 > copy from new
56 > copy to copy
56 > copy to copy
57 > diff --git a/new b/copyx
57 > diff --git a/new b/copyx
58 > similarity index 100%
58 > similarity index 100%
59 > copy from new
59 > copy from new
60 > copy to copyx
60 > copy to copyx
61 > EOF
61 > EOF
62 applying patch from stdin
62 applying patch from stdin
63
63
64 $ hg tip -q
64 $ hg tip -q
65 3:37bacb7ca14d
65 3:37bacb7ca14d
66
66
67 $ if "$TESTDIR/hghave" -q execbit; then
67 $ if "$TESTDIR/hghave" -q execbit; then
68 > test -f copy -a ! -x copy || echo bad
68 > test -f copy -a ! -x copy || echo bad
69 > test -x copyx || echo bad
69 > test -x copyx || echo bad
70 > else
70 > else
71 > test -f copy || echo bad
71 > test -f copy || echo bad
72 > fi
72 > fi
73
73
74 $ cat copy
74 $ cat copy
75 a
75 a
76
76
77 $ hg cat copy
77 $ hg cat copy
78 a
78 a
79
79
80 Rename:
80 Rename:
81
81
82 $ hg import -d "1000000 0" -mrename - <<EOF
82 $ hg import -d "1000000 0" -mrename - <<EOF
83 > diff --git a/copy b/rename
83 > diff --git a/copy b/rename
84 > similarity index 100%
84 > similarity index 100%
85 > rename from copy
85 > rename from copy
86 > rename to rename
86 > rename to rename
87 > EOF
87 > EOF
88 applying patch from stdin
88 applying patch from stdin
89
89
90 $ hg tip -q
90 $ hg tip -q
91 4:47b81a94361d
91 4:47b81a94361d
92
92
93 $ hg locate
93 $ hg locate
94 copyx
94 copyx
95 empty
95 empty
96 new
96 new
97 rename
97 rename
98
98
99 Delete:
99 Delete:
100
100
101 $ hg import -d "1000000 0" -mdelete - <<EOF
101 $ hg import -d "1000000 0" -mdelete - <<EOF
102 > diff --git a/copyx b/copyx
102 > diff --git a/copyx b/copyx
103 > deleted file mode 100755
103 > deleted file mode 100755
104 > index 7898192..0000000
104 > index 7898192..0000000
105 > --- a/copyx
105 > --- a/copyx
106 > +++ /dev/null
106 > +++ /dev/null
107 > @@ -1 +0,0 @@
107 > @@ -1 +0,0 @@
108 > -a
108 > -a
109 > EOF
109 > EOF
110 applying patch from stdin
110 applying patch from stdin
111
111
112 $ hg tip -q
112 $ hg tip -q
113 5:d9b001d98336
113 5:d9b001d98336
114
114
115 $ hg locate
115 $ hg locate
116 empty
116 empty
117 new
117 new
118 rename
118 rename
119
119
120 $ test -f copyx
120 $ test -f copyx
121 [1]
121 [1]
122
122
123 Regular diff:
123 Regular diff:
124
124
125 $ hg import -d "1000000 0" -mregular - <<EOF
125 $ hg import -d "1000000 0" -mregular - <<EOF
126 > diff --git a/rename b/rename
126 > diff --git a/rename b/rename
127 > index 7898192..72e1fe3 100644
127 > index 7898192..72e1fe3 100644
128 > --- a/rename
128 > --- a/rename
129 > +++ b/rename
129 > +++ b/rename
130 > @@ -1 +1,5 @@
130 > @@ -1 +1,5 @@
131 > a
131 > a
132 > +a
132 > +a
133 > +a
133 > +a
134 > +a
134 > +a
135 > +a
135 > +a
136 > EOF
136 > EOF
137 applying patch from stdin
137 applying patch from stdin
138
138
139 $ hg tip -q
139 $ hg tip -q
140 6:ebe901e7576b
140 6:ebe901e7576b
141
141
142 Copy and modify:
142 Copy and modify:
143
143
144 $ hg import -d "1000000 0" -mcopymod - <<EOF
144 $ hg import -d "1000000 0" -mcopymod - <<EOF
145 > diff --git a/rename b/copy2
145 > diff --git a/rename b/copy2
146 > similarity index 80%
146 > similarity index 80%
147 > copy from rename
147 > copy from rename
148 > copy to copy2
148 > copy to copy2
149 > index 72e1fe3..b53c148 100644
149 > index 72e1fe3..b53c148 100644
150 > --- a/rename
150 > --- a/rename
151 > +++ b/copy2
151 > +++ b/copy2
152 > @@ -1,5 +1,5 @@
152 > @@ -1,5 +1,5 @@
153 > a
153 > a
154 > a
154 > a
155 > -a
155 > -a
156 > +b
156 > +b
157 > a
157 > a
158 > a
158 > a
159 > EOF
159 > EOF
160 applying patch from stdin
160 applying patch from stdin
161
161
162 $ hg tip -q
162 $ hg tip -q
163 7:18f368958ecd
163 7:18f368958ecd
164
164
165 $ hg cat copy2
165 $ hg cat copy2
166 a
166 a
167 a
167 a
168 b
168 b
169 a
169 a
170 a
170 a
171
171
172 Rename and modify:
172 Rename and modify:
173
173
174 $ hg import -d "1000000 0" -mrenamemod - <<EOF
174 $ hg import -d "1000000 0" -mrenamemod - <<EOF
175 > diff --git a/copy2 b/rename2
175 > diff --git a/copy2 b/rename2
176 > similarity index 80%
176 > similarity index 80%
177 > rename from copy2
177 > rename from copy2
178 > rename to rename2
178 > rename to rename2
179 > index b53c148..8f81e29 100644
179 > index b53c148..8f81e29 100644
180 > --- a/copy2
180 > --- a/copy2
181 > +++ b/rename2
181 > +++ b/rename2
182 > @@ -1,5 +1,5 @@
182 > @@ -1,5 +1,5 @@
183 > a
183 > a
184 > a
184 > a
185 > b
185 > b
186 > -a
186 > -a
187 > +c
187 > +c
188 > a
188 > a
189 > EOF
189 > EOF
190 applying patch from stdin
190 applying patch from stdin
191
191
192 $ hg tip -q
192 $ hg tip -q
193 8:c32b0d7e6f44
193 8:c32b0d7e6f44
194
194
195 $ hg locate copy2
195 $ hg locate copy2
196 [1]
196 [1]
197 $ hg cat rename2
197 $ hg cat rename2
198 a
198 a
199 a
199 a
200 b
200 b
201 c
201 c
202 a
202 a
203
203
204 One file renamed multiple times:
204 One file renamed multiple times:
205
205
206 $ hg import -d "1000000 0" -mmultirenames - <<EOF
206 $ hg import -d "1000000 0" -mmultirenames - <<EOF
207 > diff --git a/rename2 b/rename3
207 > diff --git a/rename2 b/rename3
208 > rename from rename2
208 > rename from rename2
209 > rename to rename3
209 > rename to rename3
210 > diff --git a/rename2 b/rename3-2
210 > diff --git a/rename2 b/rename3-2
211 > rename from rename2
211 > rename from rename2
212 > rename to rename3-2
212 > rename to rename3-2
213 > EOF
213 > EOF
214 applying patch from stdin
214 applying patch from stdin
215
215
216 $ hg tip -q
216 $ hg tip -q
217 9:034a6bf95330
217 9:034a6bf95330
218
218
219 $ hg log -vr. --template '{rev} {files} / {file_copies}\n'
219 $ hg log -vr. --template '{rev} {files} / {file_copies}\n'
220 9 rename2 rename3 rename3-2 / rename3 (rename2)rename3-2 (rename2)
220 9 rename2 rename3 rename3-2 / rename3 (rename2)rename3-2 (rename2)
221
221
222 $ hg locate rename2 rename3 rename3-2
222 $ hg locate rename2 rename3 rename3-2
223 rename3
223 rename3
224 rename3-2
224 rename3-2
225
225
226 $ hg cat rename3
226 $ hg cat rename3
227 a
227 a
228 a
228 a
229 b
229 b
230 c
230 c
231 a
231 a
232
232
233 $ hg cat rename3-2
233 $ hg cat rename3-2
234 a
234 a
235 a
235 a
236 b
236 b
237 c
237 c
238 a
238 a
239
239
240 $ echo foo > foo
240 $ echo foo > foo
241 $ hg add foo
241 $ hg add foo
242 $ hg ci -m 'add foo'
242 $ hg ci -m 'add foo'
243
243
244 Binary files and regular patch hunks:
244 Binary files and regular patch hunks:
245
245
246 $ hg import -d "1000000 0" -m binaryregular - <<EOF
246 $ hg import -d "1000000 0" -m binaryregular - <<EOF
247 > diff --git a/binary b/binary
247 > diff --git a/binary b/binary
248 > new file mode 100644
248 > new file mode 100644
249 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
249 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
250 > GIT binary patch
250 > GIT binary patch
251 > literal 4
251 > literal 4
252 > Lc\${NkU|;|M00aO5
252 > Lc\${NkU|;|M00aO5
253 >
253 >
254 > diff --git a/foo b/foo2
254 > diff --git a/foo b/foo2
255 > rename from foo
255 > rename from foo
256 > rename to foo2
256 > rename to foo2
257 > EOF
257 > EOF
258 applying patch from stdin
258 applying patch from stdin
259
259
260 $ hg tip -q
260 $ hg tip -q
261 11:c39bce63e786
261 11:c39bce63e786
262
262
263 $ cat foo2
263 $ cat foo2
264 foo
264 foo
265
265
266 $ hg manifest --debug | grep binary
266 $ hg manifest --debug | grep binary
267 045c85ba38952325e126c70962cc0f9d9077bc67 644 binary
267 045c85ba38952325e126c70962cc0f9d9077bc67 644 binary
268
268
269 Multiple binary files:
269 Multiple binary files:
270
270
271 $ hg import -d "1000000 0" -m multibinary - <<EOF
271 $ hg import -d "1000000 0" -m multibinary - <<EOF
272 > diff --git a/mbinary1 b/mbinary1
272 > diff --git a/mbinary1 b/mbinary1
273 > new file mode 100644
273 > new file mode 100644
274 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
274 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
275 > GIT binary patch
275 > GIT binary patch
276 > literal 4
276 > literal 4
277 > Lc\${NkU|;|M00aO5
277 > Lc\${NkU|;|M00aO5
278 >
278 >
279 > diff --git a/mbinary2 b/mbinary2
279 > diff --git a/mbinary2 b/mbinary2
280 > new file mode 100644
280 > new file mode 100644
281 > index 0000000000000000000000000000000000000000..112363ac1917b417ffbd7f376ca786a1e5fa7490
281 > index 0000000000000000000000000000000000000000..112363ac1917b417ffbd7f376ca786a1e5fa7490
282 > GIT binary patch
282 > GIT binary patch
283 > literal 5
283 > literal 5
284 > Mc\${NkU|\`?^000jF3jhEB
284 > Mc\${NkU|\`?^000jF3jhEB
285 >
285 >
286 > EOF
286 > EOF
287 applying patch from stdin
287 applying patch from stdin
288
288
289 $ hg tip -q
289 $ hg tip -q
290 12:30b530085242
290 12:30b530085242
291
291
292 $ hg manifest --debug | grep mbinary
292 $ hg manifest --debug | grep mbinary
293 045c85ba38952325e126c70962cc0f9d9077bc67 644 mbinary1
293 045c85ba38952325e126c70962cc0f9d9077bc67 644 mbinary1
294 a874b471193996e7cb034bb301cac7bdaf3e3f46 644 mbinary2
294 a874b471193996e7cb034bb301cac7bdaf3e3f46 644 mbinary2
295
295
296 Filenames with spaces:
296 Filenames with spaces:
297
297
298 $ hg import -d "1000000 0" -m spaces - <<EOF
298 $ hg import -d "1000000 0" -m spaces - <<EOF
299 > diff --git a/foo bar b/foo bar
299 > diff --git a/foo bar b/foo bar
300 > new file mode 100644
300 > new file mode 100644
301 > index 0000000..257cc56
301 > index 0000000..257cc56
302 > --- /dev/null
302 > --- /dev/null
303 > +++ b/foo bar
303 > +++ b/foo bar
304 > @@ -0,0 +1 @@
304 > @@ -0,0 +1 @@
305 > +foo
305 > +foo
306 > EOF
306 > EOF
307 applying patch from stdin
307 applying patch from stdin
308
308
309 $ hg tip -q
309 $ hg tip -q
310 13:04750ef42fb3
310 13:04750ef42fb3
311
311
312 $ cat "foo bar"
312 $ cat "foo bar"
313 foo
313 foo
314
314
315 Copy then modify the original file:
315 Copy then modify the original file:
316
316
317 $ hg import -d "1000000 0" -m copy-mod-orig - <<EOF
317 $ hg import -d "1000000 0" -m copy-mod-orig - <<EOF
318 > diff --git a/foo2 b/foo2
318 > diff --git a/foo2 b/foo2
319 > index 257cc56..fe08ec6 100644
319 > index 257cc56..fe08ec6 100644
320 > --- a/foo2
320 > --- a/foo2
321 > +++ b/foo2
321 > +++ b/foo2
322 > @@ -1 +1,2 @@
322 > @@ -1 +1,2 @@
323 > foo
323 > foo
324 > +new line
324 > +new line
325 > diff --git a/foo2 b/foo3
325 > diff --git a/foo2 b/foo3
326 > similarity index 100%
326 > similarity index 100%
327 > copy from foo2
327 > copy from foo2
328 > copy to foo3
328 > copy to foo3
329 > EOF
329 > EOF
330 applying patch from stdin
330 applying patch from stdin
331
331
332 $ hg tip -q
332 $ hg tip -q
333 14:c4cd9cdeaa74
333 14:c4cd9cdeaa74
334
334
335 $ cat foo3
335 $ cat foo3
336 foo
336 foo
337
337
338 Move text file and patch as binary
338 Move text file and patch as binary
339
339
340 $ echo a > text2
340 $ echo a > text2
341 $ hg ci -Am0
341 $ hg ci -Am0
342 adding text2
342 adding text2
343 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
343 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
344 > diff --git a/text2 b/binary2
344 > diff --git a/text2 b/binary2
345 > rename from text2
345 > rename from text2
346 > rename to binary2
346 > rename to binary2
347 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
347 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
348 > GIT binary patch
348 > GIT binary patch
349 > literal 5
349 > literal 5
350 > Mc$`b*O5$Pw00T?_*Z=?k
350 > Mc$`b*O5$Pw00T?_*Z=?k
351 >
351 >
352 > EOF
352 > EOF
353 applying patch from stdin
353 applying patch from stdin
354
354
355 $ cat binary2
355 $ cat binary2
356 a
356 a
357 b
357 b
358 \x00 (no-eol) (esc)
358 \x00 (no-eol) (esc)
359
359
360 $ hg st --copies --change .
360 $ hg st --copies --change .
361 A binary2
361 A binary2
362 text2
362 text2
363 R text2
363 R text2
364 $ cd ..
364 $ cd ..
365
365
366 Consecutive import with renames (issue2459)
366 Consecutive import with renames (issue2459)
367
367
368 $ hg init issue2459
368 $ hg init issue2459
369 $ cd issue2459
369 $ cd issue2459
370 $ hg import --no-commit --force - <<EOF
370 $ hg import --no-commit --force - <<EOF
371 > diff --git a/a b/a
371 > diff --git a/a b/a
372 > new file mode 100644
372 > new file mode 100644
373 > EOF
373 > EOF
374 applying patch from stdin
374 applying patch from stdin
375 $ hg import --no-commit --force - <<EOF
375 $ hg import --no-commit --force - <<EOF
376 > diff --git a/a b/b
376 > diff --git a/a b/b
377 > rename from a
377 > rename from a
378 > rename to b
378 > rename to b
379 > EOF
379 > EOF
380 applying patch from stdin
380 applying patch from stdin
381 a has not been committed yet, so no copy data will be stored for b.
381 a has not been committed yet, so no copy data will be stored for b.
382 $ hg debugstate
382 $ hg debugstate
383 a 0 -1 unset b
383 a 0 -1 unset b
384 $ hg ci -m done
384 $ hg ci -m done
385 $ cd ..
385 $ cd ..
386
386
387 Renames and strip
387 Renames and strip
388
388
389 $ hg init renameandstrip
389 $ hg init renameandstrip
390 $ cd renameandstrip
390 $ cd renameandstrip
391 $ echo a > a
391 $ echo a > a
392 $ hg ci -Am adda
392 $ hg ci -Am adda
393 adding a
393 adding a
394 $ hg import --no-commit -p2 - <<EOF
394 $ hg import --no-commit -p2 - <<EOF
395 > diff --git a/foo/a b/foo/b
395 > diff --git a/foo/a b/foo/b
396 > rename from foo/a
396 > rename from foo/a
397 > rename to foo/b
397 > rename to foo/b
398 > EOF
398 > EOF
399 applying patch from stdin
399 applying patch from stdin
400 $ hg st --copies
400 $ hg st --copies
401 A b
401 A b
402 a
402 a
403 R a
403 R a
404 $ cd ..
404 $ cd ..
405
405
406 Pure copy with existing destination
406 Pure copy with existing destination
407
407
408 $ hg init copytoexisting
408 $ hg init copytoexisting
409 $ cd copytoexisting
409 $ cd copytoexisting
410 $ echo a > a
410 $ echo a > a
411 $ echo b > b
411 $ echo b > b
412 $ hg ci -Am add
412 $ hg ci -Am add
413 adding a
413 adding a
414 adding b
414 adding b
415 $ hg import --no-commit - <<EOF
415 $ hg import --no-commit - <<EOF
416 > diff --git a/a b/b
416 > diff --git a/a b/b
417 > copy from a
417 > copy from a
418 > copy to b
418 > copy to b
419 > EOF
419 > EOF
420 applying patch from stdin
420 applying patch from stdin
421 abort: cannot create b: destination already exists
421 abort: cannot create b: destination already exists
422 [255]
422 [255]
423 $ cat b
423 $ cat b
424 b
424 b
425
425
426 Copy and changes with existing destination
426 Copy and changes with existing destination
427
427
428 $ hg import --no-commit - <<EOF
428 $ hg import --no-commit - <<EOF
429 > diff --git a/a b/b
429 > diff --git a/a b/b
430 > copy from a
430 > copy from a
431 > copy to b
431 > copy to b
432 > --- a/a
432 > --- a/a
433 > +++ b/b
433 > +++ b/b
434 > @@ -1,1 +1,2 @@
434 > @@ -1,1 +1,2 @@
435 > a
435 > a
436 > +b
436 > +b
437 > EOF
437 > EOF
438 applying patch from stdin
438 applying patch from stdin
439 abort: cannot create b: destination already exists
439 cannot create b: destination already exists
440 1 out of 1 hunks FAILED -- saving rejects to file b.rej
441 abort: patch failed to apply
440 [255]
442 [255]
441 $ cat b
443 $ cat b
442 b
444 b
443 $ cd ..
445 $ cd ..
@@ -1,930 +1,930 b''
1 $ hg init a
1 $ hg init a
2 $ mkdir a/d1
2 $ mkdir a/d1
3 $ mkdir a/d1/d2
3 $ mkdir a/d1/d2
4 $ echo line 1 > a/a
4 $ echo line 1 > a/a
5 $ echo line 1 > a/d1/d2/a
5 $ echo line 1 > a/d1/d2/a
6 $ hg --cwd a ci -Ama
6 $ hg --cwd a ci -Ama
7 adding a
7 adding a
8 adding d1/d2/a
8 adding d1/d2/a
9
9
10 $ echo line 2 >> a/a
10 $ echo line 2 >> a/a
11 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
11 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
12
12
13
13
14 generate patches for the test
14 generate patches for the test
15
15
16 $ hg --cwd a export tip > exported-tip.patch
16 $ hg --cwd a export tip > exported-tip.patch
17 $ hg --cwd a diff -r0:1 > diffed-tip.patch
17 $ hg --cwd a diff -r0:1 > diffed-tip.patch
18
18
19
19
20 import exported patch
20 import exported patch
21
21
22 $ hg clone -r0 a b
22 $ hg clone -r0 a b
23 adding changesets
23 adding changesets
24 adding manifests
24 adding manifests
25 adding file changes
25 adding file changes
26 added 1 changesets with 2 changes to 2 files
26 added 1 changesets with 2 changes to 2 files
27 updating to branch default
27 updating to branch default
28 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
28 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 $ hg --cwd b import ../exported-tip.patch
29 $ hg --cwd b import ../exported-tip.patch
30 applying ../exported-tip.patch
30 applying ../exported-tip.patch
31
31
32 message and committer should be same
32 message and committer should be same
33
33
34 $ hg --cwd b tip
34 $ hg --cwd b tip
35 changeset: 1:1d4bd90af0e4
35 changeset: 1:1d4bd90af0e4
36 tag: tip
36 tag: tip
37 user: someone
37 user: someone
38 date: Thu Jan 01 00:00:01 1970 +0000
38 date: Thu Jan 01 00:00:01 1970 +0000
39 summary: second change
39 summary: second change
40
40
41 $ rm -r b
41 $ rm -r b
42
42
43
43
44 import exported patch with external patcher
44 import exported patch with external patcher
45
45
46 $ cat > dummypatch.py <<EOF
46 $ cat > dummypatch.py <<EOF
47 > print 'patching file a'
47 > print 'patching file a'
48 > file('a', 'wb').write('line2\n')
48 > file('a', 'wb').write('line2\n')
49 > EOF
49 > EOF
50 $ chmod +x dummypatch.py
50 $ chmod +x dummypatch.py
51 $ hg clone -r0 a b
51 $ hg clone -r0 a b
52 adding changesets
52 adding changesets
53 adding manifests
53 adding manifests
54 adding file changes
54 adding file changes
55 added 1 changesets with 2 changes to 2 files
55 added 1 changesets with 2 changes to 2 files
56 updating to branch default
56 updating to branch default
57 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
57 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 $ hg --config ui.patch='python ../dummypatch.py' --cwd b import ../exported-tip.patch
58 $ hg --config ui.patch='python ../dummypatch.py' --cwd b import ../exported-tip.patch
59 applying ../exported-tip.patch
59 applying ../exported-tip.patch
60 $ cat b/a
60 $ cat b/a
61 line2
61 line2
62 $ rm -r b
62 $ rm -r b
63
63
64
64
65 import of plain diff should fail without message
65 import of plain diff should fail without message
66
66
67 $ hg clone -r0 a b
67 $ hg clone -r0 a b
68 adding changesets
68 adding changesets
69 adding manifests
69 adding manifests
70 adding file changes
70 adding file changes
71 added 1 changesets with 2 changes to 2 files
71 added 1 changesets with 2 changes to 2 files
72 updating to branch default
72 updating to branch default
73 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
73 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
74 $ hg --cwd b import ../diffed-tip.patch
74 $ hg --cwd b import ../diffed-tip.patch
75 applying ../diffed-tip.patch
75 applying ../diffed-tip.patch
76 abort: empty commit message
76 abort: empty commit message
77 [255]
77 [255]
78 $ rm -r b
78 $ rm -r b
79
79
80
80
81 import of plain diff should be ok with message
81 import of plain diff should be ok with message
82
82
83 $ hg clone -r0 a b
83 $ hg clone -r0 a b
84 adding changesets
84 adding changesets
85 adding manifests
85 adding manifests
86 adding file changes
86 adding file changes
87 added 1 changesets with 2 changes to 2 files
87 added 1 changesets with 2 changes to 2 files
88 updating to branch default
88 updating to branch default
89 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
89 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 $ hg --cwd b import -mpatch ../diffed-tip.patch
90 $ hg --cwd b import -mpatch ../diffed-tip.patch
91 applying ../diffed-tip.patch
91 applying ../diffed-tip.patch
92 $ rm -r b
92 $ rm -r b
93
93
94
94
95 import of plain diff with specific date and user
95 import of plain diff with specific date and user
96
96
97 $ hg clone -r0 a b
97 $ hg clone -r0 a b
98 adding changesets
98 adding changesets
99 adding manifests
99 adding manifests
100 adding file changes
100 adding file changes
101 added 1 changesets with 2 changes to 2 files
101 added 1 changesets with 2 changes to 2 files
102 updating to branch default
102 updating to branch default
103 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
103 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
104 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
104 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
105 applying ../diffed-tip.patch
105 applying ../diffed-tip.patch
106 $ hg -R b tip -pv
106 $ hg -R b tip -pv
107 changeset: 1:ca68f19f3a40
107 changeset: 1:ca68f19f3a40
108 tag: tip
108 tag: tip
109 user: user@nowhere.net
109 user: user@nowhere.net
110 date: Thu Jan 01 00:00:01 1970 +0000
110 date: Thu Jan 01 00:00:01 1970 +0000
111 files: a
111 files: a
112 description:
112 description:
113 patch
113 patch
114
114
115
115
116 diff -r 80971e65b431 -r ca68f19f3a40 a
116 diff -r 80971e65b431 -r ca68f19f3a40 a
117 --- a/a Thu Jan 01 00:00:00 1970 +0000
117 --- a/a Thu Jan 01 00:00:00 1970 +0000
118 +++ b/a Thu Jan 01 00:00:01 1970 +0000
118 +++ b/a Thu Jan 01 00:00:01 1970 +0000
119 @@ -1,1 +1,2 @@
119 @@ -1,1 +1,2 @@
120 line 1
120 line 1
121 +line 2
121 +line 2
122
122
123 $ rm -r b
123 $ rm -r b
124
124
125
125
126 import of plain diff should be ok with --no-commit
126 import of plain diff should be ok with --no-commit
127
127
128 $ hg clone -r0 a b
128 $ hg clone -r0 a b
129 adding changesets
129 adding changesets
130 adding manifests
130 adding manifests
131 adding file changes
131 adding file changes
132 added 1 changesets with 2 changes to 2 files
132 added 1 changesets with 2 changes to 2 files
133 updating to branch default
133 updating to branch default
134 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
134 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
135 $ hg --cwd b import --no-commit ../diffed-tip.patch
135 $ hg --cwd b import --no-commit ../diffed-tip.patch
136 applying ../diffed-tip.patch
136 applying ../diffed-tip.patch
137 $ hg --cwd b diff --nodates
137 $ hg --cwd b diff --nodates
138 diff -r 80971e65b431 a
138 diff -r 80971e65b431 a
139 --- a/a
139 --- a/a
140 +++ b/a
140 +++ b/a
141 @@ -1,1 +1,2 @@
141 @@ -1,1 +1,2 @@
142 line 1
142 line 1
143 +line 2
143 +line 2
144 $ rm -r b
144 $ rm -r b
145
145
146
146
147 import of malformed plain diff should fail
147 import of malformed plain diff should fail
148
148
149 $ hg clone -r0 a b
149 $ hg clone -r0 a b
150 adding changesets
150 adding changesets
151 adding manifests
151 adding manifests
152 adding file changes
152 adding file changes
153 added 1 changesets with 2 changes to 2 files
153 added 1 changesets with 2 changes to 2 files
154 updating to branch default
154 updating to branch default
155 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
155 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
156 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
156 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
157 $ hg --cwd b import -mpatch ../broken.patch
157 $ hg --cwd b import -mpatch ../broken.patch
158 applying ../broken.patch
158 applying ../broken.patch
159 abort: bad hunk #1
159 abort: bad hunk #1
160 [255]
160 [255]
161 $ rm -r b
161 $ rm -r b
162
162
163
163
164 hg -R repo import
164 hg -R repo import
165 put the clone in a subdir - having a directory named "a"
165 put the clone in a subdir - having a directory named "a"
166 used to hide a bug.
166 used to hide a bug.
167
167
168 $ mkdir dir
168 $ mkdir dir
169 $ hg clone -r0 a dir/b
169 $ hg clone -r0 a dir/b
170 adding changesets
170 adding changesets
171 adding manifests
171 adding manifests
172 adding file changes
172 adding file changes
173 added 1 changesets with 2 changes to 2 files
173 added 1 changesets with 2 changes to 2 files
174 updating to branch default
174 updating to branch default
175 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
175 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
176 $ cd dir
176 $ cd dir
177 $ hg -R b import ../exported-tip.patch
177 $ hg -R b import ../exported-tip.patch
178 applying ../exported-tip.patch
178 applying ../exported-tip.patch
179 $ cd ..
179 $ cd ..
180 $ rm -r dir
180 $ rm -r dir
181
181
182
182
183 import from stdin
183 import from stdin
184
184
185 $ hg clone -r0 a b
185 $ hg clone -r0 a b
186 adding changesets
186 adding changesets
187 adding manifests
187 adding manifests
188 adding file changes
188 adding file changes
189 added 1 changesets with 2 changes to 2 files
189 added 1 changesets with 2 changes to 2 files
190 updating to branch default
190 updating to branch default
191 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
191 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
192 $ hg --cwd b import - < exported-tip.patch
192 $ hg --cwd b import - < exported-tip.patch
193 applying patch from stdin
193 applying patch from stdin
194 $ rm -r b
194 $ rm -r b
195
195
196
196
197 import two patches in one stream
197 import two patches in one stream
198
198
199 $ hg init b
199 $ hg init b
200 $ hg --cwd a export 0:tip | hg --cwd b import -
200 $ hg --cwd a export 0:tip | hg --cwd b import -
201 applying patch from stdin
201 applying patch from stdin
202 applied 80971e65b431
202 applied 80971e65b431
203 $ hg --cwd a id
203 $ hg --cwd a id
204 1d4bd90af0e4 tip
204 1d4bd90af0e4 tip
205 $ hg --cwd b id
205 $ hg --cwd b id
206 1d4bd90af0e4 tip
206 1d4bd90af0e4 tip
207 $ rm -r b
207 $ rm -r b
208
208
209
209
210 override commit message
210 override commit message
211
211
212 $ hg clone -r0 a b
212 $ hg clone -r0 a b
213 adding changesets
213 adding changesets
214 adding manifests
214 adding manifests
215 adding file changes
215 adding file changes
216 added 1 changesets with 2 changes to 2 files
216 added 1 changesets with 2 changes to 2 files
217 updating to branch default
217 updating to branch default
218 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
218 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
219 $ hg --cwd b import -m 'override' - < exported-tip.patch
219 $ hg --cwd b import -m 'override' - < exported-tip.patch
220 applying patch from stdin
220 applying patch from stdin
221 $ hg --cwd b tip | grep override
221 $ hg --cwd b tip | grep override
222 summary: override
222 summary: override
223 $ rm -r b
223 $ rm -r b
224
224
225 $ cat > mkmsg.py <<EOF
225 $ cat > mkmsg.py <<EOF
226 > import email.Message, sys
226 > import email.Message, sys
227 > msg = email.Message.Message()
227 > msg = email.Message.Message()
228 > patch = open(sys.argv[1], 'rb').read()
228 > patch = open(sys.argv[1], 'rb').read()
229 > msg.set_payload('email commit message\n' + patch)
229 > msg.set_payload('email commit message\n' + patch)
230 > msg['Subject'] = 'email patch'
230 > msg['Subject'] = 'email patch'
231 > msg['From'] = 'email patcher'
231 > msg['From'] = 'email patcher'
232 > sys.stdout.write(msg.as_string())
232 > sys.stdout.write(msg.as_string())
233 > EOF
233 > EOF
234
234
235
235
236 plain diff in email, subject, message body
236 plain diff in email, subject, message body
237
237
238 $ hg clone -r0 a b
238 $ hg clone -r0 a b
239 adding changesets
239 adding changesets
240 adding manifests
240 adding manifests
241 adding file changes
241 adding file changes
242 added 1 changesets with 2 changes to 2 files
242 added 1 changesets with 2 changes to 2 files
243 updating to branch default
243 updating to branch default
244 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
245 $ python mkmsg.py diffed-tip.patch > msg.patch
245 $ python mkmsg.py diffed-tip.patch > msg.patch
246 $ hg --cwd b import ../msg.patch
246 $ hg --cwd b import ../msg.patch
247 applying ../msg.patch
247 applying ../msg.patch
248 $ hg --cwd b tip | grep email
248 $ hg --cwd b tip | grep email
249 user: email patcher
249 user: email patcher
250 summary: email patch
250 summary: email patch
251 $ rm -r b
251 $ rm -r b
252
252
253
253
254 plain diff in email, no subject, message body
254 plain diff in email, no subject, message body
255
255
256 $ hg clone -r0 a b
256 $ hg clone -r0 a b
257 adding changesets
257 adding changesets
258 adding manifests
258 adding manifests
259 adding file changes
259 adding file changes
260 added 1 changesets with 2 changes to 2 files
260 added 1 changesets with 2 changes to 2 files
261 updating to branch default
261 updating to branch default
262 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
262 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
263 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
263 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
264 applying patch from stdin
264 applying patch from stdin
265 $ rm -r b
265 $ rm -r b
266
266
267
267
268 plain diff in email, subject, no message body
268 plain diff in email, subject, no message body
269
269
270 $ hg clone -r0 a b
270 $ hg clone -r0 a b
271 adding changesets
271 adding changesets
272 adding manifests
272 adding manifests
273 adding file changes
273 adding file changes
274 added 1 changesets with 2 changes to 2 files
274 added 1 changesets with 2 changes to 2 files
275 updating to branch default
275 updating to branch default
276 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
276 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
277 $ grep -v '^email ' msg.patch | hg --cwd b import -
277 $ grep -v '^email ' msg.patch | hg --cwd b import -
278 applying patch from stdin
278 applying patch from stdin
279 $ rm -r b
279 $ rm -r b
280
280
281
281
282 plain diff in email, no subject, no message body, should fail
282 plain diff in email, no subject, no message body, should fail
283
283
284 $ hg clone -r0 a b
284 $ hg clone -r0 a b
285 adding changesets
285 adding changesets
286 adding manifests
286 adding manifests
287 adding file changes
287 adding file changes
288 added 1 changesets with 2 changes to 2 files
288 added 1 changesets with 2 changes to 2 files
289 updating to branch default
289 updating to branch default
290 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
290 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
291 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
291 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
292 applying patch from stdin
292 applying patch from stdin
293 abort: empty commit message
293 abort: empty commit message
294 [255]
294 [255]
295 $ rm -r b
295 $ rm -r b
296
296
297
297
298 hg export in email, should use patch header
298 hg export in email, should use patch header
299
299
300 $ hg clone -r0 a b
300 $ hg clone -r0 a b
301 adding changesets
301 adding changesets
302 adding manifests
302 adding manifests
303 adding file changes
303 adding file changes
304 added 1 changesets with 2 changes to 2 files
304 added 1 changesets with 2 changes to 2 files
305 updating to branch default
305 updating to branch default
306 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
306 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
307 $ python mkmsg.py exported-tip.patch | hg --cwd b import -
307 $ python mkmsg.py exported-tip.patch | hg --cwd b import -
308 applying patch from stdin
308 applying patch from stdin
309 $ hg --cwd b tip | grep second
309 $ hg --cwd b tip | grep second
310 summary: second change
310 summary: second change
311 $ rm -r b
311 $ rm -r b
312
312
313
313
314 subject: duplicate detection, removal of [PATCH]
314 subject: duplicate detection, removal of [PATCH]
315 The '---' tests the gitsendmail handling without proper mail headers
315 The '---' tests the gitsendmail handling without proper mail headers
316
316
317 $ cat > mkmsg2.py <<EOF
317 $ cat > mkmsg2.py <<EOF
318 > import email.Message, sys
318 > import email.Message, sys
319 > msg = email.Message.Message()
319 > msg = email.Message.Message()
320 > patch = open(sys.argv[1], 'rb').read()
320 > patch = open(sys.argv[1], 'rb').read()
321 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
321 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
322 > msg['Subject'] = '[PATCH] email patch'
322 > msg['Subject'] = '[PATCH] email patch'
323 > msg['From'] = 'email patcher'
323 > msg['From'] = 'email patcher'
324 > sys.stdout.write(msg.as_string())
324 > sys.stdout.write(msg.as_string())
325 > EOF
325 > EOF
326
326
327
327
328 plain diff in email, [PATCH] subject, message body with subject
328 plain diff in email, [PATCH] subject, message body with subject
329
329
330 $ hg clone -r0 a b
330 $ hg clone -r0 a b
331 adding changesets
331 adding changesets
332 adding manifests
332 adding manifests
333 adding file changes
333 adding file changes
334 added 1 changesets with 2 changes to 2 files
334 added 1 changesets with 2 changes to 2 files
335 updating to branch default
335 updating to branch default
336 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
336 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
337 $ python mkmsg2.py diffed-tip.patch | hg --cwd b import -
337 $ python mkmsg2.py diffed-tip.patch | hg --cwd b import -
338 applying patch from stdin
338 applying patch from stdin
339 $ hg --cwd b tip --template '{desc}\n'
339 $ hg --cwd b tip --template '{desc}\n'
340 email patch
340 email patch
341
341
342 next line
342 next line
343 ---
343 ---
344 $ rm -r b
344 $ rm -r b
345
345
346
346
347 Issue963: Parent of working dir incorrect after import of multiple
347 Issue963: Parent of working dir incorrect after import of multiple
348 patches and rollback
348 patches and rollback
349
349
350 We weren't backing up the correct dirstate file when importing many
350 We weren't backing up the correct dirstate file when importing many
351 patches: import patch1 patch2; rollback
351 patches: import patch1 patch2; rollback
352
352
353 $ echo line 3 >> a/a
353 $ echo line 3 >> a/a
354 $ hg --cwd a ci -m'third change'
354 $ hg --cwd a ci -m'third change'
355 $ hg --cwd a export -o '../patch%R' 1 2
355 $ hg --cwd a export -o '../patch%R' 1 2
356 $ hg clone -qr0 a b
356 $ hg clone -qr0 a b
357 $ hg --cwd b parents --template 'parent: {rev}\n'
357 $ hg --cwd b parents --template 'parent: {rev}\n'
358 parent: 0
358 parent: 0
359 $ hg --cwd b import ../patch1 ../patch2
359 $ hg --cwd b import ../patch1 ../patch2
360 applying ../patch1
360 applying ../patch1
361 applying ../patch2
361 applying ../patch2
362 applied 1d4bd90af0e4
362 applied 1d4bd90af0e4
363 $ hg --cwd b rollback
363 $ hg --cwd b rollback
364 repository tip rolled back to revision 1 (undo commit)
364 repository tip rolled back to revision 1 (undo commit)
365 working directory now based on revision 1
365 working directory now based on revision 1
366 $ hg --cwd b parents --template 'parent: {rev}\n'
366 $ hg --cwd b parents --template 'parent: {rev}\n'
367 parent: 1
367 parent: 1
368 $ rm -r b
368 $ rm -r b
369
369
370
370
371 importing a patch in a subdirectory failed at the commit stage
371 importing a patch in a subdirectory failed at the commit stage
372
372
373 $ echo line 2 >> a/d1/d2/a
373 $ echo line 2 >> a/d1/d2/a
374 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
374 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
375
375
376 hg import in a subdirectory
376 hg import in a subdirectory
377
377
378 $ hg clone -r0 a b
378 $ hg clone -r0 a b
379 adding changesets
379 adding changesets
380 adding manifests
380 adding manifests
381 adding file changes
381 adding file changes
382 added 1 changesets with 2 changes to 2 files
382 added 1 changesets with 2 changes to 2 files
383 updating to branch default
383 updating to branch default
384 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
384 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
385 $ hg --cwd a export tip > tmp
385 $ hg --cwd a export tip > tmp
386 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
386 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
387 $ dir=`pwd`
387 $ dir=`pwd`
388 $ cd b/d1/d2 2>&1 > /dev/null
388 $ cd b/d1/d2 2>&1 > /dev/null
389 $ hg import ../../../subdir-tip.patch
389 $ hg import ../../../subdir-tip.patch
390 applying ../../../subdir-tip.patch
390 applying ../../../subdir-tip.patch
391 $ cd "$dir"
391 $ cd "$dir"
392
392
393 message should be 'subdir change'
393 message should be 'subdir change'
394 committer should be 'someoneelse'
394 committer should be 'someoneelse'
395
395
396 $ hg --cwd b tip
396 $ hg --cwd b tip
397 changeset: 1:3577f5aea227
397 changeset: 1:3577f5aea227
398 tag: tip
398 tag: tip
399 user: someoneelse
399 user: someoneelse
400 date: Thu Jan 01 00:00:01 1970 +0000
400 date: Thu Jan 01 00:00:01 1970 +0000
401 summary: subdir change
401 summary: subdir change
402
402
403
403
404 should be empty
404 should be empty
405
405
406 $ hg --cwd b status
406 $ hg --cwd b status
407
407
408
408
409 Test fuzziness (ambiguous patch location, fuzz=2)
409 Test fuzziness (ambiguous patch location, fuzz=2)
410
410
411 $ hg init fuzzy
411 $ hg init fuzzy
412 $ cd fuzzy
412 $ cd fuzzy
413 $ echo line1 > a
413 $ echo line1 > a
414 $ echo line0 >> a
414 $ echo line0 >> a
415 $ echo line3 >> a
415 $ echo line3 >> a
416 $ hg ci -Am adda
416 $ hg ci -Am adda
417 adding a
417 adding a
418 $ echo line1 > a
418 $ echo line1 > a
419 $ echo line2 >> a
419 $ echo line2 >> a
420 $ echo line0 >> a
420 $ echo line0 >> a
421 $ echo line3 >> a
421 $ echo line3 >> a
422 $ hg ci -m change a
422 $ hg ci -m change a
423 $ hg export tip > fuzzy-tip.patch
423 $ hg export tip > fuzzy-tip.patch
424 $ hg up -C 0
424 $ hg up -C 0
425 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
425 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
426 $ echo line1 > a
426 $ echo line1 > a
427 $ echo line0 >> a
427 $ echo line0 >> a
428 $ echo line1 >> a
428 $ echo line1 >> a
429 $ echo line0 >> a
429 $ echo line0 >> a
430 $ hg ci -m brancha
430 $ hg ci -m brancha
431 created new head
431 created new head
432 $ hg import --no-commit -v fuzzy-tip.patch
432 $ hg import --no-commit -v fuzzy-tip.patch
433 applying fuzzy-tip.patch
433 applying fuzzy-tip.patch
434 patching file a
434 patching file a
435 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
435 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
436 $ hg revert -a
436 $ hg revert -a
437 reverting a
437 reverting a
438
438
439
439
440 import with --no-commit should have written .hg/last-message.txt
440 import with --no-commit should have written .hg/last-message.txt
441
441
442 $ cat .hg/last-message.txt
442 $ cat .hg/last-message.txt
443 change (no-eol)
443 change (no-eol)
444
444
445
445
446 test fuzziness with eol=auto
446 test fuzziness with eol=auto
447
447
448 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
448 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
449 applying fuzzy-tip.patch
449 applying fuzzy-tip.patch
450 patching file a
450 patching file a
451 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
451 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
452 $ cd ..
452 $ cd ..
453
453
454
454
455 Test hunk touching empty files (issue906)
455 Test hunk touching empty files (issue906)
456
456
457 $ hg init empty
457 $ hg init empty
458 $ cd empty
458 $ cd empty
459 $ touch a
459 $ touch a
460 $ touch b1
460 $ touch b1
461 $ touch c1
461 $ touch c1
462 $ echo d > d
462 $ echo d > d
463 $ hg ci -Am init
463 $ hg ci -Am init
464 adding a
464 adding a
465 adding b1
465 adding b1
466 adding c1
466 adding c1
467 adding d
467 adding d
468 $ echo a > a
468 $ echo a > a
469 $ echo b > b1
469 $ echo b > b1
470 $ hg mv b1 b2
470 $ hg mv b1 b2
471 $ echo c > c1
471 $ echo c > c1
472 $ hg copy c1 c2
472 $ hg copy c1 c2
473 $ rm d
473 $ rm d
474 $ touch d
474 $ touch d
475 $ hg diff --git
475 $ hg diff --git
476 diff --git a/a b/a
476 diff --git a/a b/a
477 --- a/a
477 --- a/a
478 +++ b/a
478 +++ b/a
479 @@ -0,0 +1,1 @@
479 @@ -0,0 +1,1 @@
480 +a
480 +a
481 diff --git a/b1 b/b2
481 diff --git a/b1 b/b2
482 rename from b1
482 rename from b1
483 rename to b2
483 rename to b2
484 --- a/b1
484 --- a/b1
485 +++ b/b2
485 +++ b/b2
486 @@ -0,0 +1,1 @@
486 @@ -0,0 +1,1 @@
487 +b
487 +b
488 diff --git a/c1 b/c1
488 diff --git a/c1 b/c1
489 --- a/c1
489 --- a/c1
490 +++ b/c1
490 +++ b/c1
491 @@ -0,0 +1,1 @@
491 @@ -0,0 +1,1 @@
492 +c
492 +c
493 diff --git a/c1 b/c2
493 diff --git a/c1 b/c2
494 copy from c1
494 copy from c1
495 copy to c2
495 copy to c2
496 --- a/c1
496 --- a/c1
497 +++ b/c2
497 +++ b/c2
498 @@ -0,0 +1,1 @@
498 @@ -0,0 +1,1 @@
499 +c
499 +c
500 diff --git a/d b/d
500 diff --git a/d b/d
501 --- a/d
501 --- a/d
502 +++ b/d
502 +++ b/d
503 @@ -1,1 +0,0 @@
503 @@ -1,1 +0,0 @@
504 -d
504 -d
505 $ hg ci -m empty
505 $ hg ci -m empty
506 $ hg export --git tip > empty.diff
506 $ hg export --git tip > empty.diff
507 $ hg up -C 0
507 $ hg up -C 0
508 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
508 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
509 $ hg import empty.diff
509 $ hg import empty.diff
510 applying empty.diff
510 applying empty.diff
511 $ for name in a b1 b2 c1 c2 d; do
511 $ for name in a b1 b2 c1 c2 d; do
512 > echo % $name file
512 > echo % $name file
513 > test -f $name && cat $name
513 > test -f $name && cat $name
514 > done
514 > done
515 % a file
515 % a file
516 a
516 a
517 % b1 file
517 % b1 file
518 % b2 file
518 % b2 file
519 b
519 b
520 % c1 file
520 % c1 file
521 c
521 c
522 % c2 file
522 % c2 file
523 c
523 c
524 % d file
524 % d file
525 $ cd ..
525 $ cd ..
526
526
527
527
528 Test importing a patch ending with a binary file removal
528 Test importing a patch ending with a binary file removal
529
529
530 $ hg init binaryremoval
530 $ hg init binaryremoval
531 $ cd binaryremoval
531 $ cd binaryremoval
532 $ echo a > a
532 $ echo a > a
533 $ python -c "file('b', 'wb').write('a\x00b')"
533 $ python -c "file('b', 'wb').write('a\x00b')"
534 $ hg ci -Am addall
534 $ hg ci -Am addall
535 adding a
535 adding a
536 adding b
536 adding b
537 $ hg rm a
537 $ hg rm a
538 $ hg rm b
538 $ hg rm b
539 $ hg st
539 $ hg st
540 R a
540 R a
541 R b
541 R b
542 $ hg ci -m remove
542 $ hg ci -m remove
543 $ hg export --git . > remove.diff
543 $ hg export --git . > remove.diff
544 $ cat remove.diff | grep git
544 $ cat remove.diff | grep git
545 diff --git a/a b/a
545 diff --git a/a b/a
546 diff --git a/b b/b
546 diff --git a/b b/b
547 $ hg up -C 0
547 $ hg up -C 0
548 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
548 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
549 $ hg import remove.diff
549 $ hg import remove.diff
550 applying remove.diff
550 applying remove.diff
551 $ hg manifest
551 $ hg manifest
552 $ cd ..
552 $ cd ..
553
553
554
554
555 Issue927: test update+rename with common name
555 Issue927: test update+rename with common name
556
556
557 $ hg init t
557 $ hg init t
558 $ cd t
558 $ cd t
559 $ touch a
559 $ touch a
560 $ hg ci -Am t
560 $ hg ci -Am t
561 adding a
561 adding a
562 $ echo a > a
562 $ echo a > a
563
563
564 Here, bfile.startswith(afile)
564 Here, bfile.startswith(afile)
565
565
566 $ hg copy a a2
566 $ hg copy a a2
567 $ hg ci -m copya
567 $ hg ci -m copya
568 $ hg export --git tip > copy.diff
568 $ hg export --git tip > copy.diff
569 $ hg up -C 0
569 $ hg up -C 0
570 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
570 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
571 $ hg import copy.diff
571 $ hg import copy.diff
572 applying copy.diff
572 applying copy.diff
573
573
574 a should contain an 'a'
574 a should contain an 'a'
575
575
576 $ cat a
576 $ cat a
577 a
577 a
578
578
579 and a2 should have duplicated it
579 and a2 should have duplicated it
580
580
581 $ cat a2
581 $ cat a2
582 a
582 a
583 $ cd ..
583 $ cd ..
584
584
585
585
586 test -p0
586 test -p0
587
587
588 $ hg init p0
588 $ hg init p0
589 $ cd p0
589 $ cd p0
590 $ echo a > a
590 $ echo a > a
591 $ hg ci -Am t
591 $ hg ci -Am t
592 adding a
592 adding a
593 $ hg import -p0 - << EOF
593 $ hg import -p0 - << EOF
594 > foobar
594 > foobar
595 > --- a Sat Apr 12 22:43:58 2008 -0400
595 > --- a Sat Apr 12 22:43:58 2008 -0400
596 > +++ a Sat Apr 12 22:44:05 2008 -0400
596 > +++ a Sat Apr 12 22:44:05 2008 -0400
597 > @@ -1,1 +1,1 @@
597 > @@ -1,1 +1,1 @@
598 > -a
598 > -a
599 > +bb
599 > +bb
600 > EOF
600 > EOF
601 applying patch from stdin
601 applying patch from stdin
602 $ hg status
602 $ hg status
603 $ cat a
603 $ cat a
604 bb
604 bb
605 $ cd ..
605 $ cd ..
606
606
607
607
608 test paths outside repo root
608 test paths outside repo root
609
609
610 $ mkdir outside
610 $ mkdir outside
611 $ touch outside/foo
611 $ touch outside/foo
612 $ hg init inside
612 $ hg init inside
613 $ cd inside
613 $ cd inside
614 $ hg import - <<EOF
614 $ hg import - <<EOF
615 > diff --git a/a b/b
615 > diff --git a/a b/b
616 > rename from ../outside/foo
616 > rename from ../outside/foo
617 > rename to bar
617 > rename to bar
618 > EOF
618 > EOF
619 applying patch from stdin
619 applying patch from stdin
620 abort: ../outside/foo not under root
620 abort: path contains illegal component: ../outside/foo
621 [255]
621 [255]
622 $ cd ..
622 $ cd ..
623
623
624
624
625 test import with similarity and git and strip (issue295 et al.)
625 test import with similarity and git and strip (issue295 et al.)
626
626
627 $ hg init sim
627 $ hg init sim
628 $ cd sim
628 $ cd sim
629 $ echo 'this is a test' > a
629 $ echo 'this is a test' > a
630 $ hg ci -Ama
630 $ hg ci -Ama
631 adding a
631 adding a
632 $ cat > ../rename.diff <<EOF
632 $ cat > ../rename.diff <<EOF
633 > diff --git a/foo/a b/foo/a
633 > diff --git a/foo/a b/foo/a
634 > deleted file mode 100644
634 > deleted file mode 100644
635 > --- a/foo/a
635 > --- a/foo/a
636 > +++ /dev/null
636 > +++ /dev/null
637 > @@ -1,1 +0,0 @@
637 > @@ -1,1 +0,0 @@
638 > -this is a test
638 > -this is a test
639 > diff --git a/foo/b b/foo/b
639 > diff --git a/foo/b b/foo/b
640 > new file mode 100644
640 > new file mode 100644
641 > --- /dev/null
641 > --- /dev/null
642 > +++ b/foo/b
642 > +++ b/foo/b
643 > @@ -0,0 +1,2 @@
643 > @@ -0,0 +1,2 @@
644 > +this is a test
644 > +this is a test
645 > +foo
645 > +foo
646 > EOF
646 > EOF
647 $ hg import --no-commit -v -s 1 ../rename.diff -p2
647 $ hg import --no-commit -v -s 1 ../rename.diff -p2
648 applying ../rename.diff
648 applying ../rename.diff
649 patching file a
649 patching file a
650 patching file b
650 patching file b
651 removing a
651 removing a
652 adding b
652 adding b
653 recording removal of a as rename to b (88% similar)
653 recording removal of a as rename to b (88% similar)
654 $ hg st -C
654 $ hg st -C
655 A b
655 A b
656 a
656 a
657 R a
657 R a
658 $ hg revert -a
658 $ hg revert -a
659 undeleting a
659 undeleting a
660 forgetting b
660 forgetting b
661 $ rm b
661 $ rm b
662 $ hg import --no-commit -v -s 100 ../rename.diff -p2
662 $ hg import --no-commit -v -s 100 ../rename.diff -p2
663 applying ../rename.diff
663 applying ../rename.diff
664 patching file a
664 patching file a
665 patching file b
665 patching file b
666 removing a
666 removing a
667 adding b
667 adding b
668 $ hg st -C
668 $ hg st -C
669 A b
669 A b
670 R a
670 R a
671 $ cd ..
671 $ cd ..
672
672
673
673
674 Issue1495: add empty file from the end of patch
674 Issue1495: add empty file from the end of patch
675
675
676 $ hg init addemptyend
676 $ hg init addemptyend
677 $ cd addemptyend
677 $ cd addemptyend
678 $ touch a
678 $ touch a
679 $ hg addremove
679 $ hg addremove
680 adding a
680 adding a
681 $ hg ci -m "commit"
681 $ hg ci -m "commit"
682 $ cat > a.patch <<EOF
682 $ cat > a.patch <<EOF
683 > diff --git a/a b/a
683 > diff --git a/a b/a
684 > --- a/a
684 > --- a/a
685 > +++ b/a
685 > +++ b/a
686 > @@ -0,0 +1,1 @@
686 > @@ -0,0 +1,1 @@
687 > +a
687 > +a
688 > diff --git a/b b/b
688 > diff --git a/b b/b
689 > new file mode 100644
689 > new file mode 100644
690 > EOF
690 > EOF
691 $ hg import --no-commit a.patch
691 $ hg import --no-commit a.patch
692 applying a.patch
692 applying a.patch
693 $ cd ..
693 $ cd ..
694
694
695
695
696 create file when source is not /dev/null
696 create file when source is not /dev/null
697
697
698 $ cat > create.patch <<EOF
698 $ cat > create.patch <<EOF
699 > diff -Naur proj-orig/foo proj-new/foo
699 > diff -Naur proj-orig/foo proj-new/foo
700 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
700 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
701 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
701 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
702 > @@ -0,0 +1,1 @@
702 > @@ -0,0 +1,1 @@
703 > +a
703 > +a
704 > EOF
704 > EOF
705
705
706 some people have patches like the following too
706 some people have patches like the following too
707
707
708 $ cat > create2.patch <<EOF
708 $ cat > create2.patch <<EOF
709 > diff -Naur proj-orig/foo proj-new/foo
709 > diff -Naur proj-orig/foo proj-new/foo
710 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
710 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
711 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
711 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
712 > @@ -0,0 +1,1 @@
712 > @@ -0,0 +1,1 @@
713 > +a
713 > +a
714 > EOF
714 > EOF
715 $ hg init oddcreate
715 $ hg init oddcreate
716 $ cd oddcreate
716 $ cd oddcreate
717 $ hg import --no-commit ../create.patch
717 $ hg import --no-commit ../create.patch
718 applying ../create.patch
718 applying ../create.patch
719 $ cat foo
719 $ cat foo
720 a
720 a
721 $ rm foo
721 $ rm foo
722 $ hg revert foo
722 $ hg revert foo
723 $ hg import --no-commit ../create2.patch
723 $ hg import --no-commit ../create2.patch
724 applying ../create2.patch
724 applying ../create2.patch
725 $ cat foo
725 $ cat foo
726 a
726 a
727
727
728
728
729 Issue1859: first line mistaken for email headers
729 Issue1859: first line mistaken for email headers
730
730
731 $ hg init emailconfusion
731 $ hg init emailconfusion
732 $ cd emailconfusion
732 $ cd emailconfusion
733 $ cat > a.patch <<EOF
733 $ cat > a.patch <<EOF
734 > module: summary
734 > module: summary
735 >
735 >
736 > description
736 > description
737 >
737 >
738 >
738 >
739 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
739 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
740 > --- /dev/null
740 > --- /dev/null
741 > +++ b/a
741 > +++ b/a
742 > @@ -0,0 +1,1 @@
742 > @@ -0,0 +1,1 @@
743 > +a
743 > +a
744 > EOF
744 > EOF
745 $ hg import -d '0 0' a.patch
745 $ hg import -d '0 0' a.patch
746 applying a.patch
746 applying a.patch
747 $ hg parents -v
747 $ hg parents -v
748 changeset: 0:5a681217c0ad
748 changeset: 0:5a681217c0ad
749 tag: tip
749 tag: tip
750 user: test
750 user: test
751 date: Thu Jan 01 00:00:00 1970 +0000
751 date: Thu Jan 01 00:00:00 1970 +0000
752 files: a
752 files: a
753 description:
753 description:
754 module: summary
754 module: summary
755
755
756 description
756 description
757
757
758
758
759 $ cd ..
759 $ cd ..
760
760
761
761
762 --- in commit message
762 --- in commit message
763
763
764 $ hg init commitconfusion
764 $ hg init commitconfusion
765 $ cd commitconfusion
765 $ cd commitconfusion
766 $ cat > a.patch <<EOF
766 $ cat > a.patch <<EOF
767 > module: summary
767 > module: summary
768 >
768 >
769 > --- description
769 > --- description
770 >
770 >
771 > diff --git a/a b/a
771 > diff --git a/a b/a
772 > new file mode 100644
772 > new file mode 100644
773 > --- /dev/null
773 > --- /dev/null
774 > +++ b/a
774 > +++ b/a
775 > @@ -0,0 +1,1 @@
775 > @@ -0,0 +1,1 @@
776 > +a
776 > +a
777 > EOF
777 > EOF
778 > hg import -d '0 0' a.patch
778 > hg import -d '0 0' a.patch
779 > hg parents -v
779 > hg parents -v
780 > cd ..
780 > cd ..
781 >
781 >
782 > echo '% tricky header splitting'
782 > echo '% tricky header splitting'
783 > cat > trickyheaders.patch <<EOF
783 > cat > trickyheaders.patch <<EOF
784 > From: User A <user@a>
784 > From: User A <user@a>
785 > Subject: [PATCH] from: tricky!
785 > Subject: [PATCH] from: tricky!
786 >
786 >
787 > # HG changeset patch
787 > # HG changeset patch
788 > # User User B
788 > # User User B
789 > # Date 1266264441 18000
789 > # Date 1266264441 18000
790 > # Branch stable
790 > # Branch stable
791 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
791 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
792 > # Parent 0000000000000000000000000000000000000000
792 > # Parent 0000000000000000000000000000000000000000
793 > from: tricky!
793 > from: tricky!
794 >
794 >
795 > That is not a header.
795 > That is not a header.
796 >
796 >
797 > diff -r 000000000000 -r f2be6a1170ac foo
797 > diff -r 000000000000 -r f2be6a1170ac foo
798 > --- /dev/null
798 > --- /dev/null
799 > +++ b/foo
799 > +++ b/foo
800 > @@ -0,0 +1,1 @@
800 > @@ -0,0 +1,1 @@
801 > +foo
801 > +foo
802 > EOF
802 > EOF
803 applying a.patch
803 applying a.patch
804 changeset: 0:f34d9187897d
804 changeset: 0:f34d9187897d
805 tag: tip
805 tag: tip
806 user: test
806 user: test
807 date: Thu Jan 01 00:00:00 1970 +0000
807 date: Thu Jan 01 00:00:00 1970 +0000
808 files: a
808 files: a
809 description:
809 description:
810 module: summary
810 module: summary
811
811
812
812
813 % tricky header splitting
813 % tricky header splitting
814
814
815 $ hg init trickyheaders
815 $ hg init trickyheaders
816 $ cd trickyheaders
816 $ cd trickyheaders
817 $ hg import -d '0 0' ../trickyheaders.patch
817 $ hg import -d '0 0' ../trickyheaders.patch
818 applying ../trickyheaders.patch
818 applying ../trickyheaders.patch
819 $ hg export --git tip
819 $ hg export --git tip
820 # HG changeset patch
820 # HG changeset patch
821 # User User B
821 # User User B
822 # Date 0 0
822 # Date 0 0
823 # Node ID eb56ab91903632294ac504838508cb370c0901d2
823 # Node ID eb56ab91903632294ac504838508cb370c0901d2
824 # Parent 0000000000000000000000000000000000000000
824 # Parent 0000000000000000000000000000000000000000
825 from: tricky!
825 from: tricky!
826
826
827 That is not a header.
827 That is not a header.
828
828
829 diff --git a/foo b/foo
829 diff --git a/foo b/foo
830 new file mode 100644
830 new file mode 100644
831 --- /dev/null
831 --- /dev/null
832 +++ b/foo
832 +++ b/foo
833 @@ -0,0 +1,1 @@
833 @@ -0,0 +1,1 @@
834 +foo
834 +foo
835 $ cd ..
835 $ cd ..
836
836
837
837
838 Issue2102: hg export and hg import speak different languages
838 Issue2102: hg export and hg import speak different languages
839
839
840 $ hg init issue2102
840 $ hg init issue2102
841 $ cd issue2102
841 $ cd issue2102
842 $ mkdir -p src/cmd/gc
842 $ mkdir -p src/cmd/gc
843 $ touch src/cmd/gc/mksys.bash
843 $ touch src/cmd/gc/mksys.bash
844 $ hg ci -Am init
844 $ hg ci -Am init
845 adding src/cmd/gc/mksys.bash
845 adding src/cmd/gc/mksys.bash
846 $ hg import - <<EOF
846 $ hg import - <<EOF
847 > # HG changeset patch
847 > # HG changeset patch
848 > # User Rob Pike
848 > # User Rob Pike
849 > # Date 1216685449 25200
849 > # Date 1216685449 25200
850 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
850 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
851 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
851 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
852 > help management of empty pkg and lib directories in perforce
852 > help management of empty pkg and lib directories in perforce
853 >
853 >
854 > R=gri
854 > R=gri
855 > DELTA=4 (4 added, 0 deleted, 0 changed)
855 > DELTA=4 (4 added, 0 deleted, 0 changed)
856 > OCL=13328
856 > OCL=13328
857 > CL=13328
857 > CL=13328
858 >
858 >
859 > diff --git a/lib/place-holder b/lib/place-holder
859 > diff --git a/lib/place-holder b/lib/place-holder
860 > new file mode 100644
860 > new file mode 100644
861 > --- /dev/null
861 > --- /dev/null
862 > +++ b/lib/place-holder
862 > +++ b/lib/place-holder
863 > @@ -0,0 +1,2 @@
863 > @@ -0,0 +1,2 @@
864 > +perforce does not maintain empty directories.
864 > +perforce does not maintain empty directories.
865 > +this file helps.
865 > +this file helps.
866 > diff --git a/pkg/place-holder b/pkg/place-holder
866 > diff --git a/pkg/place-holder b/pkg/place-holder
867 > new file mode 100644
867 > new file mode 100644
868 > --- /dev/null
868 > --- /dev/null
869 > +++ b/pkg/place-holder
869 > +++ b/pkg/place-holder
870 > @@ -0,0 +1,2 @@
870 > @@ -0,0 +1,2 @@
871 > +perforce does not maintain empty directories.
871 > +perforce does not maintain empty directories.
872 > +this file helps.
872 > +this file helps.
873 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
873 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
874 > old mode 100644
874 > old mode 100644
875 > new mode 100755
875 > new mode 100755
876 > EOF
876 > EOF
877 applying patch from stdin
877 applying patch from stdin
878 $ hg sum
878 $ hg sum
879 parent: 1:d59915696727 tip
879 parent: 1:d59915696727 tip
880 help management of empty pkg and lib directories in perforce
880 help management of empty pkg and lib directories in perforce
881 branch: default
881 branch: default
882 commit: (clean)
882 commit: (clean)
883 update: (current)
883 update: (current)
884 $ hg diff --git -c tip
884 $ hg diff --git -c tip
885 diff --git a/lib/place-holder b/lib/place-holder
885 diff --git a/lib/place-holder b/lib/place-holder
886 new file mode 100644
886 new file mode 100644
887 --- /dev/null
887 --- /dev/null
888 +++ b/lib/place-holder
888 +++ b/lib/place-holder
889 @@ -0,0 +1,2 @@
889 @@ -0,0 +1,2 @@
890 +perforce does not maintain empty directories.
890 +perforce does not maintain empty directories.
891 +this file helps.
891 +this file helps.
892 diff --git a/pkg/place-holder b/pkg/place-holder
892 diff --git a/pkg/place-holder b/pkg/place-holder
893 new file mode 100644
893 new file mode 100644
894 --- /dev/null
894 --- /dev/null
895 +++ b/pkg/place-holder
895 +++ b/pkg/place-holder
896 @@ -0,0 +1,2 @@
896 @@ -0,0 +1,2 @@
897 +perforce does not maintain empty directories.
897 +perforce does not maintain empty directories.
898 +this file helps.
898 +this file helps.
899 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
899 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
900 old mode 100644
900 old mode 100644
901 new mode 100755
901 new mode 100755
902 $ cd ..
902 $ cd ..
903
903
904
904
905 diff lines looking like headers
905 diff lines looking like headers
906
906
907 $ hg init difflineslikeheaders
907 $ hg init difflineslikeheaders
908 $ cd difflineslikeheaders
908 $ cd difflineslikeheaders
909 $ echo a >a
909 $ echo a >a
910 $ echo b >b
910 $ echo b >b
911 $ echo c >c
911 $ echo c >c
912 $ hg ci -Am1
912 $ hg ci -Am1
913 adding a
913 adding a
914 adding b
914 adding b
915 adding c
915 adding c
916
916
917 $ echo "key: value" >>a
917 $ echo "key: value" >>a
918 $ echo "key: value" >>b
918 $ echo "key: value" >>b
919 $ echo "foo" >>c
919 $ echo "foo" >>c
920 $ hg ci -m2
920 $ hg ci -m2
921
921
922 $ hg up -C 0
922 $ hg up -C 0
923 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
923 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
924 $ hg diff --git -c1 >want
924 $ hg diff --git -c1 >want
925 $ hg diff -c1 | hg import --no-commit -
925 $ hg diff -c1 | hg import --no-commit -
926 applying patch from stdin
926 applying patch from stdin
927 $ hg diff --git >have
927 $ hg diff --git >have
928 $ diff want have
928 $ diff want have
929 $ cd ..
929 $ cd ..
930
930
@@ -1,524 +1,525 b''
1 $ echo "[extensions]" >> $HGRCPATH
1 $ echo "[extensions]" >> $HGRCPATH
2 $ echo "mq=" >> $HGRCPATH
2 $ echo "mq=" >> $HGRCPATH
3 $ echo "[diff]" >> $HGRCPATH
3 $ echo "[diff]" >> $HGRCPATH
4 $ echo "nodates=1" >> $HGRCPATH
4 $ echo "nodates=1" >> $HGRCPATH
5
5
6 $ hg init a
6 $ hg init a
7 $ cd a
7 $ cd a
8
8
9 $ mkdir 1 2
9 $ mkdir 1 2
10 $ echo 'base' > 1/base
10 $ echo 'base' > 1/base
11 $ echo 'base' > 2/base
11 $ echo 'base' > 2/base
12 $ hg ci -Ambase
12 $ hg ci -Ambase
13 adding 1/base
13 adding 1/base
14 adding 2/base
14 adding 2/base
15
15
16 $ hg qnew -mmqbase mqbase
16 $ hg qnew -mmqbase mqbase
17
17
18 $ echo 'patched' > 1/base
18 $ echo 'patched' > 1/base
19 $ echo 'patched' > 2/base
19 $ echo 'patched' > 2/base
20 $ hg qrefresh
20 $ hg qrefresh
21
21
22 $ hg qdiff
22 $ hg qdiff
23 diff -r e7af5904b465 1/base
23 diff -r e7af5904b465 1/base
24 --- a/1/base
24 --- a/1/base
25 +++ b/1/base
25 +++ b/1/base
26 @@ -1,1 +1,1 @@
26 @@ -1,1 +1,1 @@
27 -base
27 -base
28 +patched
28 +patched
29 diff -r e7af5904b465 2/base
29 diff -r e7af5904b465 2/base
30 --- a/2/base
30 --- a/2/base
31 +++ b/2/base
31 +++ b/2/base
32 @@ -1,1 +1,1 @@
32 @@ -1,1 +1,1 @@
33 -base
33 -base
34 +patched
34 +patched
35
35
36 $ hg qdiff .
36 $ hg qdiff .
37 diff -r e7af5904b465 1/base
37 diff -r e7af5904b465 1/base
38 --- a/1/base
38 --- a/1/base
39 +++ b/1/base
39 +++ b/1/base
40 @@ -1,1 +1,1 @@
40 @@ -1,1 +1,1 @@
41 -base
41 -base
42 +patched
42 +patched
43 diff -r e7af5904b465 2/base
43 diff -r e7af5904b465 2/base
44 --- a/2/base
44 --- a/2/base
45 +++ b/2/base
45 +++ b/2/base
46 @@ -1,1 +1,1 @@
46 @@ -1,1 +1,1 @@
47 -base
47 -base
48 +patched
48 +patched
49
49
50 $ cat .hg/patches/mqbase
50 $ cat .hg/patches/mqbase
51 # HG changeset patch
51 # HG changeset patch
52 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
52 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
53 mqbase
53 mqbase
54
54
55 diff -r e7af5904b465 1/base
55 diff -r e7af5904b465 1/base
56 --- a/1/base
56 --- a/1/base
57 +++ b/1/base
57 +++ b/1/base
58 @@ -1,1 +1,1 @@
58 @@ -1,1 +1,1 @@
59 -base
59 -base
60 +patched
60 +patched
61 diff -r e7af5904b465 2/base
61 diff -r e7af5904b465 2/base
62 --- a/2/base
62 --- a/2/base
63 +++ b/2/base
63 +++ b/2/base
64 @@ -1,1 +1,1 @@
64 @@ -1,1 +1,1 @@
65 -base
65 -base
66 +patched
66 +patched
67
67
68 $ echo 'patched again' > base
68 $ echo 'patched again' > base
69 $ hg qrefresh 1
69 $ hg qrefresh 1
70
70
71 $ hg qdiff
71 $ hg qdiff
72 diff -r e7af5904b465 1/base
72 diff -r e7af5904b465 1/base
73 --- a/1/base
73 --- a/1/base
74 +++ b/1/base
74 +++ b/1/base
75 @@ -1,1 +1,1 @@
75 @@ -1,1 +1,1 @@
76 -base
76 -base
77 +patched
77 +patched
78 diff -r e7af5904b465 2/base
78 diff -r e7af5904b465 2/base
79 --- a/2/base
79 --- a/2/base
80 +++ b/2/base
80 +++ b/2/base
81 @@ -1,1 +1,1 @@
81 @@ -1,1 +1,1 @@
82 -base
82 -base
83 +patched
83 +patched
84
84
85 $ hg qdiff .
85 $ hg qdiff .
86 diff -r e7af5904b465 1/base
86 diff -r e7af5904b465 1/base
87 --- a/1/base
87 --- a/1/base
88 +++ b/1/base
88 +++ b/1/base
89 @@ -1,1 +1,1 @@
89 @@ -1,1 +1,1 @@
90 -base
90 -base
91 +patched
91 +patched
92 diff -r e7af5904b465 2/base
92 diff -r e7af5904b465 2/base
93 --- a/2/base
93 --- a/2/base
94 +++ b/2/base
94 +++ b/2/base
95 @@ -1,1 +1,1 @@
95 @@ -1,1 +1,1 @@
96 -base
96 -base
97 +patched
97 +patched
98
98
99 $ cat .hg/patches/mqbase
99 $ cat .hg/patches/mqbase
100 # HG changeset patch
100 # HG changeset patch
101 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
101 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
102 mqbase
102 mqbase
103
103
104 diff -r e7af5904b465 1/base
104 diff -r e7af5904b465 1/base
105 --- a/1/base
105 --- a/1/base
106 +++ b/1/base
106 +++ b/1/base
107 @@ -1,1 +1,1 @@
107 @@ -1,1 +1,1 @@
108 -base
108 -base
109 +patched
109 +patched
110
110
111 qrefresh . in subdir:
111 qrefresh . in subdir:
112
112
113 $ ( cd 1 ; hg qrefresh . )
113 $ ( cd 1 ; hg qrefresh . )
114
114
115 $ hg qdiff
115 $ hg qdiff
116 diff -r e7af5904b465 1/base
116 diff -r e7af5904b465 1/base
117 --- a/1/base
117 --- a/1/base
118 +++ b/1/base
118 +++ b/1/base
119 @@ -1,1 +1,1 @@
119 @@ -1,1 +1,1 @@
120 -base
120 -base
121 +patched
121 +patched
122 diff -r e7af5904b465 2/base
122 diff -r e7af5904b465 2/base
123 --- a/2/base
123 --- a/2/base
124 +++ b/2/base
124 +++ b/2/base
125 @@ -1,1 +1,1 @@
125 @@ -1,1 +1,1 @@
126 -base
126 -base
127 +patched
127 +patched
128
128
129 $ hg qdiff .
129 $ hg qdiff .
130 diff -r e7af5904b465 1/base
130 diff -r e7af5904b465 1/base
131 --- a/1/base
131 --- a/1/base
132 +++ b/1/base
132 +++ b/1/base
133 @@ -1,1 +1,1 @@
133 @@ -1,1 +1,1 @@
134 -base
134 -base
135 +patched
135 +patched
136 diff -r e7af5904b465 2/base
136 diff -r e7af5904b465 2/base
137 --- a/2/base
137 --- a/2/base
138 +++ b/2/base
138 +++ b/2/base
139 @@ -1,1 +1,1 @@
139 @@ -1,1 +1,1 @@
140 -base
140 -base
141 +patched
141 +patched
142
142
143 $ cat .hg/patches/mqbase
143 $ cat .hg/patches/mqbase
144 # HG changeset patch
144 # HG changeset patch
145 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
145 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
146 mqbase
146 mqbase
147
147
148 diff -r e7af5904b465 1/base
148 diff -r e7af5904b465 1/base
149 --- a/1/base
149 --- a/1/base
150 +++ b/1/base
150 +++ b/1/base
151 @@ -1,1 +1,1 @@
151 @@ -1,1 +1,1 @@
152 -base
152 -base
153 +patched
153 +patched
154
154
155 qrefresh in hg-root again:
155 qrefresh in hg-root again:
156
156
157 $ hg qrefresh
157 $ hg qrefresh
158
158
159 $ hg qdiff
159 $ hg qdiff
160 diff -r e7af5904b465 1/base
160 diff -r e7af5904b465 1/base
161 --- a/1/base
161 --- a/1/base
162 +++ b/1/base
162 +++ b/1/base
163 @@ -1,1 +1,1 @@
163 @@ -1,1 +1,1 @@
164 -base
164 -base
165 +patched
165 +patched
166 diff -r e7af5904b465 2/base
166 diff -r e7af5904b465 2/base
167 --- a/2/base
167 --- a/2/base
168 +++ b/2/base
168 +++ b/2/base
169 @@ -1,1 +1,1 @@
169 @@ -1,1 +1,1 @@
170 -base
170 -base
171 +patched
171 +patched
172
172
173 $ hg qdiff .
173 $ hg qdiff .
174 diff -r e7af5904b465 1/base
174 diff -r e7af5904b465 1/base
175 --- a/1/base
175 --- a/1/base
176 +++ b/1/base
176 +++ b/1/base
177 @@ -1,1 +1,1 @@
177 @@ -1,1 +1,1 @@
178 -base
178 -base
179 +patched
179 +patched
180 diff -r e7af5904b465 2/base
180 diff -r e7af5904b465 2/base
181 --- a/2/base
181 --- a/2/base
182 +++ b/2/base
182 +++ b/2/base
183 @@ -1,1 +1,1 @@
183 @@ -1,1 +1,1 @@
184 -base
184 -base
185 +patched
185 +patched
186
186
187 $ cat .hg/patches/mqbase
187 $ cat .hg/patches/mqbase
188 # HG changeset patch
188 # HG changeset patch
189 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
189 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
190 mqbase
190 mqbase
191
191
192 diff -r e7af5904b465 1/base
192 diff -r e7af5904b465 1/base
193 --- a/1/base
193 --- a/1/base
194 +++ b/1/base
194 +++ b/1/base
195 @@ -1,1 +1,1 @@
195 @@ -1,1 +1,1 @@
196 -base
196 -base
197 +patched
197 +patched
198 diff -r e7af5904b465 2/base
198 diff -r e7af5904b465 2/base
199 --- a/2/base
199 --- a/2/base
200 +++ b/2/base
200 +++ b/2/base
201 @@ -1,1 +1,1 @@
201 @@ -1,1 +1,1 @@
202 -base
202 -base
203 +patched
203 +patched
204
204
205
205
206 qrefresh --short tests:
206 qrefresh --short tests:
207
207
208 $ echo 'orphan' > orphanchild
208 $ echo 'orphan' > orphanchild
209 $ hg add orphanchild
209 $ hg add orphanchild
210 $ hg qrefresh nonexistingfilename # clear patch
210 $ hg qrefresh nonexistingfilename # clear patch
211 $ hg qrefresh --short 1/base
211 $ hg qrefresh --short 1/base
212 $ hg qrefresh --short 2/base
212 $ hg qrefresh --short 2/base
213
213
214 $ hg qdiff
214 $ hg qdiff
215 diff -r e7af5904b465 1/base
215 diff -r e7af5904b465 1/base
216 --- a/1/base
216 --- a/1/base
217 +++ b/1/base
217 +++ b/1/base
218 @@ -1,1 +1,1 @@
218 @@ -1,1 +1,1 @@
219 -base
219 -base
220 +patched
220 +patched
221 diff -r e7af5904b465 2/base
221 diff -r e7af5904b465 2/base
222 --- a/2/base
222 --- a/2/base
223 +++ b/2/base
223 +++ b/2/base
224 @@ -1,1 +1,1 @@
224 @@ -1,1 +1,1 @@
225 -base
225 -base
226 +patched
226 +patched
227 diff -r e7af5904b465 orphanchild
227 diff -r e7af5904b465 orphanchild
228 --- /dev/null
228 --- /dev/null
229 +++ b/orphanchild
229 +++ b/orphanchild
230 @@ -0,0 +1,1 @@
230 @@ -0,0 +1,1 @@
231 +orphan
231 +orphan
232
232
233 $ cat .hg/patches/mqbase
233 $ cat .hg/patches/mqbase
234 # HG changeset patch
234 # HG changeset patch
235 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
235 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
236 mqbase
236 mqbase
237
237
238 diff -r e7af5904b465 1/base
238 diff -r e7af5904b465 1/base
239 --- a/1/base
239 --- a/1/base
240 +++ b/1/base
240 +++ b/1/base
241 @@ -1,1 +1,1 @@
241 @@ -1,1 +1,1 @@
242 -base
242 -base
243 +patched
243 +patched
244 diff -r e7af5904b465 2/base
244 diff -r e7af5904b465 2/base
245 --- a/2/base
245 --- a/2/base
246 +++ b/2/base
246 +++ b/2/base
247 @@ -1,1 +1,1 @@
247 @@ -1,1 +1,1 @@
248 -base
248 -base
249 +patched
249 +patched
250
250
251 $ hg st
251 $ hg st
252 A orphanchild
252 A orphanchild
253 ? base
253 ? base
254
254
255 diff shows what is not in patch:
255 diff shows what is not in patch:
256
256
257 $ hg diff
257 $ hg diff
258 diff -r ???????????? orphanchild (glob)
258 diff -r ???????????? orphanchild (glob)
259 --- /dev/null
259 --- /dev/null
260 +++ b/orphanchild
260 +++ b/orphanchild
261 @@ -0,0 +1,1 @@
261 @@ -0,0 +1,1 @@
262 +orphan
262 +orphan
263
263
264 Before starting exclusive tests:
264 Before starting exclusive tests:
265
265
266 $ cat .hg/patches/mqbase
266 $ cat .hg/patches/mqbase
267 # HG changeset patch
267 # HG changeset patch
268 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
268 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
269 mqbase
269 mqbase
270
270
271 diff -r e7af5904b465 1/base
271 diff -r e7af5904b465 1/base
272 --- a/1/base
272 --- a/1/base
273 +++ b/1/base
273 +++ b/1/base
274 @@ -1,1 +1,1 @@
274 @@ -1,1 +1,1 @@
275 -base
275 -base
276 +patched
276 +patched
277 diff -r e7af5904b465 2/base
277 diff -r e7af5904b465 2/base
278 --- a/2/base
278 --- a/2/base
279 +++ b/2/base
279 +++ b/2/base
280 @@ -1,1 +1,1 @@
280 @@ -1,1 +1,1 @@
281 -base
281 -base
282 +patched
282 +patched
283
283
284 Exclude 2/base:
284 Exclude 2/base:
285
285
286 $ hg qref -s -X 2/base
286 $ hg qref -s -X 2/base
287
287
288 $ cat .hg/patches/mqbase
288 $ cat .hg/patches/mqbase
289 # HG changeset patch
289 # HG changeset patch
290 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
290 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
291 mqbase
291 mqbase
292
292
293 diff -r e7af5904b465 1/base
293 diff -r e7af5904b465 1/base
294 --- a/1/base
294 --- a/1/base
295 +++ b/1/base
295 +++ b/1/base
296 @@ -1,1 +1,1 @@
296 @@ -1,1 +1,1 @@
297 -base
297 -base
298 +patched
298 +patched
299
299
300 status shows 2/base as dirty:
300 status shows 2/base as dirty:
301
301
302 $ hg status
302 $ hg status
303 M 2/base
303 M 2/base
304 A orphanchild
304 A orphanchild
305 ? base
305 ? base
306
306
307 Remove 1/base and add 2/base again but not orphanchild:
307 Remove 1/base and add 2/base again but not orphanchild:
308
308
309 $ hg qref -s -X orphanchild -X 1/base 2/base orphanchild
309 $ hg qref -s -X orphanchild -X 1/base 2/base orphanchild
310
310
311 $ cat .hg/patches/mqbase
311 $ cat .hg/patches/mqbase
312 # HG changeset patch
312 # HG changeset patch
313 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
313 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
314 mqbase
314 mqbase
315
315
316 diff -r e7af5904b465 2/base
316 diff -r e7af5904b465 2/base
317 --- a/2/base
317 --- a/2/base
318 +++ b/2/base
318 +++ b/2/base
319 @@ -1,1 +1,1 @@
319 @@ -1,1 +1,1 @@
320 -base
320 -base
321 +patched
321 +patched
322
322
323 Add 1/base with include filter - and thus remove 2/base from patch:
323 Add 1/base with include filter - and thus remove 2/base from patch:
324
324
325 $ hg qref -s -I 1/ o* */*
325 $ hg qref -s -I 1/ o* */*
326
326
327 $ cat .hg/patches/mqbase
327 $ cat .hg/patches/mqbase
328 # HG changeset patch
328 # HG changeset patch
329 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
329 # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
330 mqbase
330 mqbase
331
331
332 diff -r e7af5904b465 1/base
332 diff -r e7af5904b465 1/base
333 --- a/1/base
333 --- a/1/base
334 +++ b/1/base
334 +++ b/1/base
335 @@ -1,1 +1,1 @@
335 @@ -1,1 +1,1 @@
336 -base
336 -base
337 +patched
337 +patched
338
338
339 $ cd ..
339 $ cd ..
340
340
341
341
342 Test qrefresh --git losing copy metadata:
342 Test qrefresh --git losing copy metadata:
343
343
344 $ hg init repo
344 $ hg init repo
345 $ cd repo
345 $ cd repo
346
346
347 $ echo "[diff]" >> .hg/hgrc
347 $ echo "[diff]" >> .hg/hgrc
348 $ echo "git=True" >> .hg/hgrc
348 $ echo "git=True" >> .hg/hgrc
349 $ echo a > a
349 $ echo a > a
350
350
351 $ hg ci -Am adda
351 $ hg ci -Am adda
352 adding a
352 adding a
353 $ hg copy a ab
353 $ hg copy a ab
354 $ echo b >> ab
354 $ echo b >> ab
355 $ hg copy a ac
355 $ hg copy a ac
356 $ echo c >> ac
356 $ echo c >> ac
357
357
358 Capture changes:
358 Capture changes:
359
359
360 $ hg qnew -f p1
360 $ hg qnew -f p1
361
361
362 $ hg qdiff
362 $ hg qdiff
363 diff --git a/a b/ab
363 diff --git a/a b/ab
364 copy from a
364 copy from a
365 copy to ab
365 copy to ab
366 --- a/a
366 --- a/a
367 +++ b/ab
367 +++ b/ab
368 @@ -1,1 +1,2 @@
368 @@ -1,1 +1,2 @@
369 a
369 a
370 +b
370 +b
371 diff --git a/a b/ac
371 diff --git a/a b/ac
372 copy from a
372 copy from a
373 copy to ac
373 copy to ac
374 --- a/a
374 --- a/a
375 +++ b/ac
375 +++ b/ac
376 @@ -1,1 +1,2 @@
376 @@ -1,1 +1,2 @@
377 a
377 a
378 +c
378 +c
379
379
380 Refresh and check changes again:
380 Refresh and check changes again:
381
381
382 $ hg qrefresh
382 $ hg qrefresh
383
383
384 $ hg qdiff
384 $ hg qdiff
385 diff --git a/a b/ab
385 diff --git a/a b/ab
386 copy from a
386 copy from a
387 copy to ab
387 copy to ab
388 --- a/a
388 --- a/a
389 +++ b/ab
389 +++ b/ab
390 @@ -1,1 +1,2 @@
390 @@ -1,1 +1,2 @@
391 a
391 a
392 +b
392 +b
393 diff --git a/a b/ac
393 diff --git a/a b/ac
394 copy from a
394 copy from a
395 copy to ac
395 copy to ac
396 --- a/a
396 --- a/a
397 +++ b/ac
397 +++ b/ac
398 @@ -1,1 +1,2 @@
398 @@ -1,1 +1,2 @@
399 a
399 a
400 +c
400 +c
401
401
402 $ cd ..
402 $ cd ..
403
403
404
404
405 Issue1441: qrefresh confused after hg rename:
405 Issue1441: qrefresh confused after hg rename:
406
406
407 $ hg init repo-1441
407 $ hg init repo-1441
408 $ cd repo-1441
408 $ cd repo-1441
409 $ echo a > a
409 $ echo a > a
410 $ hg add a
410 $ hg add a
411 $ hg qnew -f p
411 $ hg qnew -f p
412 $ hg mv a b
412 $ hg mv a b
413 $ hg qrefresh
413 $ hg qrefresh
414
414
415 $ hg qdiff
415 $ hg qdiff
416 diff -r 000000000000 b
416 diff -r 000000000000 b
417 --- /dev/null
417 --- /dev/null
418 +++ b/b
418 +++ b/b
419 @@ -0,0 +1,1 @@
419 @@ -0,0 +1,1 @@
420 +a
420 +a
421
421
422 $ cd ..
422 $ cd ..
423
423
424
424
425 Issue2025: qrefresh does not honor filtering options when tip !=
425 Issue2025: qrefresh does not honor filtering options when tip !=
426 qtip:
426 qtip:
427
427
428 $ hg init repo-2025
428 $ hg init repo-2025
429 $ cd repo-2025
429 $ cd repo-2025
430 $ echo a > a
430 $ echo a > a
431 $ echo b > b
431 $ echo b > b
432 $ hg ci -qAm addab
432 $ hg ci -qAm addab
433 $ echo a >> a
433 $ echo a >> a
434 $ echo b >> b
434 $ echo b >> b
435 $ hg qnew -f patch
435 $ hg qnew -f patch
436 $ hg up -qC 0
436 $ hg up -qC 0
437 $ echo c > c
437 $ echo c > c
438 $ hg ci -qAm addc
438 $ hg ci -qAm addc
439 $ hg up -qC 1
439 $ hg up -qC 1
440
440
441 refresh with tip != qtip:
441 refresh with tip != qtip:
442
442
443 $ hg --config diff.nodates=1 qrefresh -I b
443 $ hg --config diff.nodates=1 qrefresh -I b
444
444
445 $ hg st
445 $ hg st
446 M a
446 M a
447
447
448 $ cat b
448 $ cat b
449 b
449 b
450 b
450 b
451
451
452 $ cat .hg/patches/patch
452 $ cat .hg/patches/patch
453 # HG changeset patch
453 # HG changeset patch
454 # Parent 1a60229be7ac3e4a7f647508e99b87bef1f03593
454 # Parent 1a60229be7ac3e4a7f647508e99b87bef1f03593
455
455
456 diff -r 1a60229be7ac b
456 diff -r 1a60229be7ac b
457 --- a/b
457 --- a/b
458 +++ b/b
458 +++ b/b
459 @@ -1,1 +1,2 @@
459 @@ -1,1 +1,2 @@
460 b
460 b
461 +b
461 +b
462
462
463 $ cd ..
463 $ cd ..
464
464
465
465
466 Issue1441 with git patches:
466 Issue1441 with git patches:
467
467
468 $ hg init repo-1441-git
468 $ hg init repo-1441-git
469 $ cd repo-1441-git
469 $ cd repo-1441-git
470
470
471 $ echo "[diff]" >> .hg/hgrc
471 $ echo "[diff]" >> .hg/hgrc
472 $ echo "git=True" >> .hg/hgrc
472 $ echo "git=True" >> .hg/hgrc
473
473
474 $ echo a > a
474 $ echo a > a
475 $ hg add a
475 $ hg add a
476 $ hg qnew -f p
476 $ hg qnew -f p
477 $ hg mv a b
477 $ hg mv a b
478 $ hg qrefresh
478 $ hg qrefresh
479
479
480 $ hg qdiff --nodates
480 $ hg qdiff --nodates
481 diff --git a/b b/b
481 diff --git a/b b/b
482 new file mode 100644
482 new file mode 100644
483 --- /dev/null
483 --- /dev/null
484 +++ b/b
484 +++ b/b
485 @@ -0,0 +1,1 @@
485 @@ -0,0 +1,1 @@
486 +a
486 +a
487
487
488 $ cd ..
488 $ cd ..
489
489
490 Refresh with bad usernames. Mercurial used to abort on bad usernames,
490 Refresh with bad usernames. Mercurial used to abort on bad usernames,
491 but only after writing the bad name into the patch.
491 but only after writing the bad name into the patch.
492
492
493 $ hg init bad-usernames
493 $ hg init bad-usernames
494 $ cd bad-usernames
494 $ cd bad-usernames
495 $ touch a
495 $ touch a
496 $ hg add a
496 $ hg add a
497 $ hg qnew a
497 $ hg qnew a
498 $ hg qrefresh -u 'foo
498 $ hg qrefresh -u 'foo
499 > bar'
499 > bar'
500 transaction abort!
500 transaction abort!
501 rollback completed
501 rollback completed
502 refresh interrupted while patch was popped! (revert --all, qpush to recover)
502 refresh interrupted while patch was popped! (revert --all, qpush to recover)
503 abort: username 'foo\nbar' contains a newline!
503 abort: username 'foo\nbar' contains a newline!
504 [255]
504 [255]
505 $ rm a
505 $ cat .hg/patches/a
506 $ cat .hg/patches/a
506 # HG changeset patch
507 # HG changeset patch
507 # Parent 0000000000000000000000000000000000000000
508 # Parent 0000000000000000000000000000000000000000
508 diff --git a/a b/a
509 diff --git a/a b/a
509 new file mode 100644
510 new file mode 100644
510 $ hg qpush
511 $ hg qpush
511 applying a
512 applying a
512 now at: a
513 now at: a
513 $ hg qrefresh -u ' '
514 $ hg qrefresh -u ' '
514 transaction abort!
515 transaction abort!
515 rollback completed
516 rollback completed
516 refresh interrupted while patch was popped! (revert --all, qpush to recover)
517 refresh interrupted while patch was popped! (revert --all, qpush to recover)
517 abort: empty username!
518 abort: empty username!
518 [255]
519 [255]
519 $ cat .hg/patches/a
520 $ cat .hg/patches/a
520 # HG changeset patch
521 # HG changeset patch
521 # Parent 0000000000000000000000000000000000000000
522 # Parent 0000000000000000000000000000000000000000
522 diff --git a/a b/a
523 diff --git a/a b/a
523 new file mode 100644
524 new file mode 100644
524 $ cd ..
525 $ cd ..
@@ -1,121 +1,123 b''
1 $ "$TESTDIR/hghave" symlink || exit 80
1 $ "$TESTDIR/hghave" symlink || exit 80
2
2
3 $ echo "[extensions]" >> $HGRCPATH
3 $ echo "[extensions]" >> $HGRCPATH
4 $ echo "mq=" >> $HGRCPATH
4 $ echo "mq=" >> $HGRCPATH
5
5
6 $ hg init
6 $ hg init
7 $ hg qinit
7 $ hg qinit
8 $ hg qnew base.patch
8 $ hg qnew base.patch
9 $ echo aaa > a
9 $ echo aaa > a
10 $ echo bbb > b
10 $ echo bbb > b
11 $ echo ccc > c
11 $ echo ccc > c
12 $ hg add a b c
12 $ hg add a b c
13 $ hg qrefresh
13 $ hg qrefresh
14 $ $TESTDIR/readlink.py a
14 $ $TESTDIR/readlink.py a
15 a -> a not a symlink
15 a -> a not a symlink
16
16
17
17
18 test replacing a file with a symlink
18 test replacing a file with a symlink
19
19
20 $ hg qnew symlink.patch
20 $ hg qnew symlink.patch
21 $ rm a
21 $ rm a
22 $ ln -s b a
22 $ ln -s b a
23 $ hg qrefresh --git
23 $ hg qrefresh --git
24 $ $TESTDIR/readlink.py a
24 $ $TESTDIR/readlink.py a
25 a -> b
25 a -> b
26
26
27 $ hg qpop
27 $ hg qpop
28 popping symlink.patch
28 popping symlink.patch
29 now at: base.patch
29 now at: base.patch
30 $ hg qpush
30 $ hg qpush
31 applying symlink.patch
31 applying symlink.patch
32 now at: symlink.patch
32 now at: symlink.patch
33 $ $TESTDIR/readlink.py a
33 $ $TESTDIR/readlink.py a
34 a -> b
34 a -> b
35
35
36
36
37 test updating a symlink
37 test updating a symlink
38
38
39 $ rm a
39 $ rm a
40 $ ln -s c a
40 $ ln -s c a
41 $ hg qnew --git -f updatelink
41 $ hg qnew --git -f updatelink
42 $ $TESTDIR/readlink.py a
42 $ $TESTDIR/readlink.py a
43 a -> c
43 a -> c
44 $ hg qpop
44 $ hg qpop
45 popping updatelink
45 popping updatelink
46 now at: symlink.patch
46 now at: symlink.patch
47 $ hg qpush --debug
47 $ hg qpush --debug
48 applying updatelink
48 applying updatelink
49 patching file a
49 patching file a
50 a
50 a
51 now at: updatelink
51 now at: updatelink
52 $ $TESTDIR/readlink.py a
52 $ $TESTDIR/readlink.py a
53 a -> c
53 a -> c
54 $ hg st
54 $ hg st
55
55
56
56
57 test replacing a symlink with a file
57 test replacing a symlink with a file
58
58
59 $ ln -s c s
59 $ ln -s c s
60 $ hg add s
60 $ hg add s
61 $ hg qnew --git -f addlink
61 $ hg qnew --git -f addlink
62 $ rm s
62 $ rm s
63 $ echo sss > s
63 $ echo sss > s
64 $ hg qnew --git -f replacelinkwithfile
64 $ hg qnew --git -f replacelinkwithfile
65 $ hg qpop
65 $ hg qpop
66 popping replacelinkwithfile
66 popping replacelinkwithfile
67 now at: addlink
67 now at: addlink
68 $ hg qpush
68 $ hg qpush
69 applying replacelinkwithfile
69 applying replacelinkwithfile
70 now at: replacelinkwithfile
70 now at: replacelinkwithfile
71 $ cat s
71 $ cat s
72 sss
72 sss
73 $ hg st
73 $ hg st
74
74
75
75
76 test symlink removal
76 test symlink removal
77
77
78 $ hg qnew removesl.patch
78 $ hg qnew removesl.patch
79 $ hg rm a
79 $ hg rm a
80 $ hg qrefresh --git
80 $ hg qrefresh --git
81 $ hg qpop
81 $ hg qpop
82 popping removesl.patch
82 popping removesl.patch
83 now at: replacelinkwithfile
83 now at: replacelinkwithfile
84 $ hg qpush
84 $ hg qpush
85 applying removesl.patch
85 applying removesl.patch
86 now at: removesl.patch
86 now at: removesl.patch
87 $ hg st -c
87 $ hg st -c
88 C b
88 C b
89 C c
89 C c
90 C s
90 C s
91
91
92 replace broken symlink with another broken symlink
92 replace broken symlink with another broken symlink
93
93
94 $ ln -s linka linka
94 $ ln -s linka linka
95 $ hg add linka
95 $ hg add linka
96 $ hg qnew link
96 $ hg qnew link
97 $ hg mv linka linkb
97 $ hg mv linka linkb
98 $ rm linkb
98 $ rm linkb
99 $ ln -s linkb linkb
99 $ ln -s linkb linkb
100 $ hg qnew movelink
100 $ hg qnew movelink
101 $ hg qpop
101 $ hg qpop
102 popping movelink
102 popping movelink
103 now at: link
103 now at: link
104 $ hg qpush
104 $ hg qpush
105 applying movelink
105 applying movelink
106 now at: movelink
106 now at: movelink
107 $ $TESTDIR/readlink.py linkb
107 $ $TESTDIR/readlink.py linkb
108 linkb -> linkb
108 linkb -> linkb
109
109
110 check patch does not overwrite untracked symlinks
110 check patch does not overwrite untracked symlinks
111
111
112 $ hg qpop
112 $ hg qpop
113 popping movelink
113 popping movelink
114 now at: link
114 now at: link
115 $ ln -s linkbb linkb
115 $ ln -s linkbb linkb
116 $ hg qpush
116 $ hg qpush
117 applying movelink
117 applying movelink
118 cannot create linkb: destination already exists
119 1 out of 1 hunks FAILED -- saving rejects to file linkb.rej
118 patch failed, unable to continue (try -v)
120 patch failed, unable to continue (try -v)
119 patch failed, rejects left in working dir
121 patch failed, rejects left in working dir
120 errors during apply, please fix and refresh movelink
122 errors during apply, please fix and refresh movelink
121 [2]
123 [2]
General Comments 0
You need to be logged in to leave comments. Login now