##// END OF EJS Templates
scmutil: switch match users to supplying contexts...
Matt Mackall -
r14671:35c2cc32 default
parent child Browse files
Show More
@@ -1,166 +1,166 b''
1 # perf.py - performance test routines
1 # perf.py - performance test routines
2 '''helper extension to measure performance'''
2 '''helper extension to measure performance'''
3
3
4 from mercurial import cmdutil, scmutil, match, commands
4 from mercurial import cmdutil, scmutil, match, commands
5 import time, os, sys
5 import time, os, sys
6
6
7 def timer(func, title=None):
7 def timer(func, title=None):
8 results = []
8 results = []
9 begin = time.time()
9 begin = time.time()
10 count = 0
10 count = 0
11 while True:
11 while True:
12 ostart = os.times()
12 ostart = os.times()
13 cstart = time.time()
13 cstart = time.time()
14 r = func()
14 r = func()
15 cstop = time.time()
15 cstop = time.time()
16 ostop = os.times()
16 ostop = os.times()
17 count += 1
17 count += 1
18 a, b = ostart, ostop
18 a, b = ostart, ostop
19 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
19 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
20 if cstop - begin > 3 and count >= 100:
20 if cstop - begin > 3 and count >= 100:
21 break
21 break
22 if cstop - begin > 10 and count >= 3:
22 if cstop - begin > 10 and count >= 3:
23 break
23 break
24 if title:
24 if title:
25 sys.stderr.write("! %s\n" % title)
25 sys.stderr.write("! %s\n" % title)
26 if r:
26 if r:
27 sys.stderr.write("! result: %s\n" % r)
27 sys.stderr.write("! result: %s\n" % r)
28 m = min(results)
28 m = min(results)
29 sys.stderr.write("! wall %f comb %f user %f sys %f (best of %d)\n"
29 sys.stderr.write("! wall %f comb %f user %f sys %f (best of %d)\n"
30 % (m[0], m[1] + m[2], m[1], m[2], count))
30 % (m[0], m[1] + m[2], m[1], m[2], count))
31
31
32 def perfwalk(ui, repo, *pats):
32 def perfwalk(ui, repo, *pats):
33 try:
33 try:
34 m = scmutil.match(repo, pats, {})
34 m = scmutil.match(repo[None], pats, {})
35 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
35 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
36 except:
36 except:
37 try:
37 try:
38 m = scmutil.match(repo, pats, {})
38 m = scmutil.match(repo[None], pats, {})
39 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
39 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
40 except:
40 except:
41 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
41 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
42
42
43 def perfstatus(ui, repo, *pats):
43 def perfstatus(ui, repo, *pats):
44 #m = match.always(repo.root, repo.getcwd())
44 #m = match.always(repo.root, repo.getcwd())
45 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False, False))))
45 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False, False))))
46 timer(lambda: sum(map(len, repo.status())))
46 timer(lambda: sum(map(len, repo.status())))
47
47
48 def perfheads(ui, repo):
48 def perfheads(ui, repo):
49 timer(lambda: len(repo.changelog.heads()))
49 timer(lambda: len(repo.changelog.heads()))
50
50
51 def perftags(ui, repo):
51 def perftags(ui, repo):
52 import mercurial.changelog, mercurial.manifest
52 import mercurial.changelog, mercurial.manifest
53 def t():
53 def t():
54 repo.changelog = mercurial.changelog.changelog(repo.sopener)
54 repo.changelog = mercurial.changelog.changelog(repo.sopener)
55 repo.manifest = mercurial.manifest.manifest(repo.sopener)
55 repo.manifest = mercurial.manifest.manifest(repo.sopener)
56 repo._tags = None
56 repo._tags = None
57 return len(repo.tags())
57 return len(repo.tags())
58 timer(t)
58 timer(t)
59
59
60 def perfdirstate(ui, repo):
60 def perfdirstate(ui, repo):
61 "a" in repo.dirstate
61 "a" in repo.dirstate
62 def d():
62 def d():
63 repo.dirstate.invalidate()
63 repo.dirstate.invalidate()
64 "a" in repo.dirstate
64 "a" in repo.dirstate
65 timer(d)
65 timer(d)
66
66
67 def perfdirstatedirs(ui, repo):
67 def perfdirstatedirs(ui, repo):
68 "a" in repo.dirstate
68 "a" in repo.dirstate
69 def d():
69 def d():
70 "a" in repo.dirstate._dirs
70 "a" in repo.dirstate._dirs
71 del repo.dirstate._dirs
71 del repo.dirstate._dirs
72 timer(d)
72 timer(d)
73
73
74 def perfmanifest(ui, repo):
74 def perfmanifest(ui, repo):
75 def d():
75 def d():
76 t = repo.manifest.tip()
76 t = repo.manifest.tip()
77 m = repo.manifest.read(t)
77 m = repo.manifest.read(t)
78 repo.manifest.mapcache = None
78 repo.manifest.mapcache = None
79 repo.manifest._cache = None
79 repo.manifest._cache = None
80 timer(d)
80 timer(d)
81
81
82 def perfindex(ui, repo):
82 def perfindex(ui, repo):
83 import mercurial.revlog
83 import mercurial.revlog
84 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
84 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
85 n = repo["tip"].node()
85 n = repo["tip"].node()
86 def d():
86 def d():
87 repo.invalidate()
87 repo.invalidate()
88 repo[n]
88 repo[n]
89 timer(d)
89 timer(d)
90
90
91 def perfstartup(ui, repo):
91 def perfstartup(ui, repo):
92 cmd = sys.argv[0]
92 cmd = sys.argv[0]
93 def d():
93 def d():
94 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
94 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
95 timer(d)
95 timer(d)
96
96
97 def perfparents(ui, repo):
97 def perfparents(ui, repo):
98 nl = [repo.changelog.node(i) for i in xrange(1000)]
98 nl = [repo.changelog.node(i) for i in xrange(1000)]
99 def d():
99 def d():
100 for n in nl:
100 for n in nl:
101 repo.changelog.parents(n)
101 repo.changelog.parents(n)
102 timer(d)
102 timer(d)
103
103
104 def perflookup(ui, repo, rev):
104 def perflookup(ui, repo, rev):
105 timer(lambda: len(repo.lookup(rev)))
105 timer(lambda: len(repo.lookup(rev)))
106
106
107 def perflog(ui, repo, **opts):
107 def perflog(ui, repo, **opts):
108 ui.pushbuffer()
108 ui.pushbuffer()
109 timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
109 timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
110 copies=opts.get('rename')))
110 copies=opts.get('rename')))
111 ui.popbuffer()
111 ui.popbuffer()
112
112
113 def perftemplating(ui, repo):
113 def perftemplating(ui, repo):
114 ui.pushbuffer()
114 ui.pushbuffer()
115 timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
115 timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
116 template='{date|shortdate} [{rev}:{node|short}]'
116 template='{date|shortdate} [{rev}:{node|short}]'
117 ' {author|person}: {desc|firstline}\n'))
117 ' {author|person}: {desc|firstline}\n'))
118 ui.popbuffer()
118 ui.popbuffer()
119
119
120 def perfdiffwd(ui, repo):
120 def perfdiffwd(ui, repo):
121 """Profile diff of working directory changes"""
121 """Profile diff of working directory changes"""
122 options = {
122 options = {
123 'w': 'ignore_all_space',
123 'w': 'ignore_all_space',
124 'b': 'ignore_space_change',
124 'b': 'ignore_space_change',
125 'B': 'ignore_blank_lines',
125 'B': 'ignore_blank_lines',
126 }
126 }
127
127
128 for diffopt in ('', 'w', 'b', 'B', 'wB'):
128 for diffopt in ('', 'w', 'b', 'B', 'wB'):
129 opts = dict((options[c], '1') for c in diffopt)
129 opts = dict((options[c], '1') for c in diffopt)
130 def d():
130 def d():
131 ui.pushbuffer()
131 ui.pushbuffer()
132 commands.diff(ui, repo, **opts)
132 commands.diff(ui, repo, **opts)
133 ui.popbuffer()
133 ui.popbuffer()
134 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
134 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
135 timer(d, title)
135 timer(d, title)
136
136
137 def perfrevlog(ui, repo, file_, **opts):
137 def perfrevlog(ui, repo, file_, **opts):
138 from mercurial import revlog
138 from mercurial import revlog
139 dist = opts['dist']
139 dist = opts['dist']
140 def d():
140 def d():
141 r = revlog.revlog(lambda fn: open(fn, 'rb'), file_)
141 r = revlog.revlog(lambda fn: open(fn, 'rb'), file_)
142 for x in xrange(0, len(r), dist):
142 for x in xrange(0, len(r), dist):
143 r.revision(r.node(x))
143 r.revision(r.node(x))
144
144
145 timer(d)
145 timer(d)
146
146
147 cmdtable = {
147 cmdtable = {
148 'perflookup': (perflookup, []),
148 'perflookup': (perflookup, []),
149 'perfparents': (perfparents, []),
149 'perfparents': (perfparents, []),
150 'perfstartup': (perfstartup, []),
150 'perfstartup': (perfstartup, []),
151 'perfstatus': (perfstatus, []),
151 'perfstatus': (perfstatus, []),
152 'perfwalk': (perfwalk, []),
152 'perfwalk': (perfwalk, []),
153 'perfmanifest': (perfmanifest, []),
153 'perfmanifest': (perfmanifest, []),
154 'perfindex': (perfindex, []),
154 'perfindex': (perfindex, []),
155 'perfheads': (perfheads, []),
155 'perfheads': (perfheads, []),
156 'perftags': (perftags, []),
156 'perftags': (perftags, []),
157 'perfdirstate': (perfdirstate, []),
157 'perfdirstate': (perfdirstate, []),
158 'perfdirstatedirs': (perfdirstate, []),
158 'perfdirstatedirs': (perfdirstate, []),
159 'perflog': (perflog,
159 'perflog': (perflog,
160 [('', 'rename', False, 'ask log to follow renames')]),
160 [('', 'rename', False, 'ask log to follow renames')]),
161 'perftemplating': (perftemplating, []),
161 'perftemplating': (perftemplating, []),
162 'perfdiffwd': (perfdiffwd, []),
162 'perfdiffwd': (perfdiffwd, []),
163 'perfrevlog': (perfrevlog,
163 'perfrevlog': (perfrevlog,
164 [('d', 'dist', 100, 'distance between the revisions')],
164 [('d', 'dist', 100, 'distance between the revisions')],
165 "[INDEXFILE]"),
165 "[INDEXFILE]"),
166 }
166 }
@@ -1,197 +1,197 b''
1 # churn.py - create a graph of revisions count grouped by template
1 # churn.py - create a graph of revisions count grouped by template
2 #
2 #
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
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 '''command to display statistics about repository history'''
9 '''command to display statistics about repository history'''
10
10
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12 from mercurial import patch, cmdutil, scmutil, util, templater, commands
12 from mercurial import patch, cmdutil, scmutil, util, templater, commands
13 import os
13 import os
14 import time, datetime
14 import time, datetime
15
15
16 def maketemplater(ui, repo, tmpl):
16 def maketemplater(ui, repo, tmpl):
17 tmpl = templater.parsestring(tmpl, quoted=False)
17 tmpl = templater.parsestring(tmpl, quoted=False)
18 try:
18 try:
19 t = cmdutil.changeset_templater(ui, repo, False, None, None, False)
19 t = cmdutil.changeset_templater(ui, repo, False, None, None, False)
20 except SyntaxError, inst:
20 except SyntaxError, inst:
21 raise util.Abort(inst.args[0])
21 raise util.Abort(inst.args[0])
22 t.use_template(tmpl)
22 t.use_template(tmpl)
23 return t
23 return t
24
24
25 def changedlines(ui, repo, ctx1, ctx2, fns):
25 def changedlines(ui, repo, ctx1, ctx2, fns):
26 added, removed = 0, 0
26 added, removed = 0, 0
27 fmatch = scmutil.matchfiles(repo, fns)
27 fmatch = scmutil.matchfiles(repo, fns)
28 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
28 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
29 for l in diff.split('\n'):
29 for l in diff.split('\n'):
30 if l.startswith("+") and not l.startswith("+++ "):
30 if l.startswith("+") and not l.startswith("+++ "):
31 added += 1
31 added += 1
32 elif l.startswith("-") and not l.startswith("--- "):
32 elif l.startswith("-") and not l.startswith("--- "):
33 removed += 1
33 removed += 1
34 return (added, removed)
34 return (added, removed)
35
35
36 def countrate(ui, repo, amap, *pats, **opts):
36 def countrate(ui, repo, amap, *pats, **opts):
37 """Calculate stats"""
37 """Calculate stats"""
38 if opts.get('dateformat'):
38 if opts.get('dateformat'):
39 def getkey(ctx):
39 def getkey(ctx):
40 t, tz = ctx.date()
40 t, tz = ctx.date()
41 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
41 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
42 return date.strftime(opts['dateformat'])
42 return date.strftime(opts['dateformat'])
43 else:
43 else:
44 tmpl = opts.get('template', '{author|email}')
44 tmpl = opts.get('template', '{author|email}')
45 tmpl = maketemplater(ui, repo, tmpl)
45 tmpl = maketemplater(ui, repo, tmpl)
46 def getkey(ctx):
46 def getkey(ctx):
47 ui.pushbuffer()
47 ui.pushbuffer()
48 tmpl.show(ctx)
48 tmpl.show(ctx)
49 return ui.popbuffer()
49 return ui.popbuffer()
50
50
51 state = {'count': 0}
51 state = {'count': 0}
52 rate = {}
52 rate = {}
53 df = False
53 df = False
54 if opts.get('date'):
54 if opts.get('date'):
55 df = util.matchdate(opts['date'])
55 df = util.matchdate(opts['date'])
56
56
57 m = scmutil.match(repo, pats, opts)
57 m = scmutil.match(repo[None], pats, opts)
58 def prep(ctx, fns):
58 def prep(ctx, fns):
59 rev = ctx.rev()
59 rev = ctx.rev()
60 if df and not df(ctx.date()[0]): # doesn't match date format
60 if df and not df(ctx.date()[0]): # doesn't match date format
61 return
61 return
62
62
63 key = getkey(ctx).strip()
63 key = getkey(ctx).strip()
64 key = amap.get(key, key) # alias remap
64 key = amap.get(key, key) # alias remap
65 if opts.get('changesets'):
65 if opts.get('changesets'):
66 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
66 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
67 else:
67 else:
68 parents = ctx.parents()
68 parents = ctx.parents()
69 if len(parents) > 1:
69 if len(parents) > 1:
70 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
70 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
71 return
71 return
72
72
73 ctx1 = parents[0]
73 ctx1 = parents[0]
74 lines = changedlines(ui, repo, ctx1, ctx, fns)
74 lines = changedlines(ui, repo, ctx1, ctx, fns)
75 rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
75 rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
76
76
77 state['count'] += 1
77 state['count'] += 1
78 ui.progress(_('analyzing'), state['count'], total=len(repo))
78 ui.progress(_('analyzing'), state['count'], total=len(repo))
79
79
80 for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
80 for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
81 continue
81 continue
82
82
83 ui.progress(_('analyzing'), None)
83 ui.progress(_('analyzing'), None)
84
84
85 return rate
85 return rate
86
86
87
87
88 def churn(ui, repo, *pats, **opts):
88 def churn(ui, repo, *pats, **opts):
89 '''histogram of changes to the repository
89 '''histogram of changes to the repository
90
90
91 This command will display a histogram representing the number
91 This command will display a histogram representing the number
92 of changed lines or revisions, grouped according to the given
92 of changed lines or revisions, grouped according to the given
93 template. The default template will group changes by author.
93 template. The default template will group changes by author.
94 The --dateformat option may be used to group the results by
94 The --dateformat option may be used to group the results by
95 date instead.
95 date instead.
96
96
97 Statistics are based on the number of changed lines, or
97 Statistics are based on the number of changed lines, or
98 alternatively the number of matching revisions if the
98 alternatively the number of matching revisions if the
99 --changesets option is specified.
99 --changesets option is specified.
100
100
101 Examples::
101 Examples::
102
102
103 # display count of changed lines for every committer
103 # display count of changed lines for every committer
104 hg churn -t '{author|email}'
104 hg churn -t '{author|email}'
105
105
106 # display daily activity graph
106 # display daily activity graph
107 hg churn -f '%H' -s -c
107 hg churn -f '%H' -s -c
108
108
109 # display activity of developers by month
109 # display activity of developers by month
110 hg churn -f '%Y-%m' -s -c
110 hg churn -f '%Y-%m' -s -c
111
111
112 # display count of lines changed in every year
112 # display count of lines changed in every year
113 hg churn -f '%Y' -s
113 hg churn -f '%Y' -s
114
114
115 It is possible to map alternate email addresses to a main address
115 It is possible to map alternate email addresses to a main address
116 by providing a file using the following format::
116 by providing a file using the following format::
117
117
118 <alias email> = <actual email>
118 <alias email> = <actual email>
119
119
120 Such a file may be specified with the --aliases option, otherwise
120 Such a file may be specified with the --aliases option, otherwise
121 a .hgchurn file will be looked for in the working directory root.
121 a .hgchurn file will be looked for in the working directory root.
122 '''
122 '''
123 def pad(s, l):
123 def pad(s, l):
124 return (s + " " * l)[:l]
124 return (s + " " * l)[:l]
125
125
126 amap = {}
126 amap = {}
127 aliases = opts.get('aliases')
127 aliases = opts.get('aliases')
128 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
128 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
129 aliases = repo.wjoin('.hgchurn')
129 aliases = repo.wjoin('.hgchurn')
130 if aliases:
130 if aliases:
131 for l in open(aliases, "r"):
131 for l in open(aliases, "r"):
132 try:
132 try:
133 alias, actual = l.split('=' in l and '=' or None, 1)
133 alias, actual = l.split('=' in l and '=' or None, 1)
134 amap[alias.strip()] = actual.strip()
134 amap[alias.strip()] = actual.strip()
135 except ValueError:
135 except ValueError:
136 l = l.strip()
136 l = l.strip()
137 if l:
137 if l:
138 ui.warn(_("skipping malformed alias: %s\n" % l))
138 ui.warn(_("skipping malformed alias: %s\n" % l))
139 continue
139 continue
140
140
141 rate = countrate(ui, repo, amap, *pats, **opts).items()
141 rate = countrate(ui, repo, amap, *pats, **opts).items()
142 if not rate:
142 if not rate:
143 return
143 return
144
144
145 sortkey = ((not opts.get('sort')) and (lambda x: -sum(x[1])) or None)
145 sortkey = ((not opts.get('sort')) and (lambda x: -sum(x[1])) or None)
146 rate.sort(key=sortkey)
146 rate.sort(key=sortkey)
147
147
148 # Be careful not to have a zero maxcount (issue833)
148 # Be careful not to have a zero maxcount (issue833)
149 maxcount = float(max(sum(v) for k, v in rate)) or 1.0
149 maxcount = float(max(sum(v) for k, v in rate)) or 1.0
150 maxname = max(len(k) for k, v in rate)
150 maxname = max(len(k) for k, v in rate)
151
151
152 ttywidth = ui.termwidth()
152 ttywidth = ui.termwidth()
153 ui.debug("assuming %i character terminal\n" % ttywidth)
153 ui.debug("assuming %i character terminal\n" % ttywidth)
154 width = ttywidth - maxname - 2 - 2 - 2
154 width = ttywidth - maxname - 2 - 2 - 2
155
155
156 if opts.get('diffstat'):
156 if opts.get('diffstat'):
157 width -= 15
157 width -= 15
158 def format(name, diffstat):
158 def format(name, diffstat):
159 added, removed = diffstat
159 added, removed = diffstat
160 return "%s %15s %s%s\n" % (pad(name, maxname),
160 return "%s %15s %s%s\n" % (pad(name, maxname),
161 '+%d/-%d' % (added, removed),
161 '+%d/-%d' % (added, removed),
162 ui.label('+' * charnum(added),
162 ui.label('+' * charnum(added),
163 'diffstat.inserted'),
163 'diffstat.inserted'),
164 ui.label('-' * charnum(removed),
164 ui.label('-' * charnum(removed),
165 'diffstat.deleted'))
165 'diffstat.deleted'))
166 else:
166 else:
167 width -= 6
167 width -= 6
168 def format(name, count):
168 def format(name, count):
169 return "%s %6d %s\n" % (pad(name, maxname), sum(count),
169 return "%s %6d %s\n" % (pad(name, maxname), sum(count),
170 '*' * charnum(sum(count)))
170 '*' * charnum(sum(count)))
171
171
172 def charnum(count):
172 def charnum(count):
173 return int(round(count * width / maxcount))
173 return int(round(count * width / maxcount))
174
174
175 for name, count in rate:
175 for name, count in rate:
176 ui.write(format(name, count))
176 ui.write(format(name, count))
177
177
178
178
179 cmdtable = {
179 cmdtable = {
180 "churn":
180 "churn":
181 (churn,
181 (churn,
182 [('r', 'rev', [],
182 [('r', 'rev', [],
183 _('count rate for the specified revision or range'), _('REV')),
183 _('count rate for the specified revision or range'), _('REV')),
184 ('d', 'date', '',
184 ('d', 'date', '',
185 _('count rate for revisions matching date spec'), _('DATE')),
185 _('count rate for revisions matching date spec'), _('DATE')),
186 ('t', 'template', '{author|email}',
186 ('t', 'template', '{author|email}',
187 _('template to group changesets'), _('TEMPLATE')),
187 _('template to group changesets'), _('TEMPLATE')),
188 ('f', 'dateformat', '',
188 ('f', 'dateformat', '',
189 _('strftime-compatible format for grouping by date'), _('FORMAT')),
189 _('strftime-compatible format for grouping by date'), _('FORMAT')),
190 ('c', 'changesets', False, _('count rate by number of changesets')),
190 ('c', 'changesets', False, _('count rate by number of changesets')),
191 ('s', 'sort', False, _('sort by key (default: sort by count)')),
191 ('s', 'sort', False, _('sort by key (default: sort by count)')),
192 ('', 'diffstat', False, _('display added/removed lines separately')),
192 ('', 'diffstat', False, _('display added/removed lines separately')),
193 ('', 'aliases', '',
193 ('', 'aliases', '',
194 _('file with email aliases'), _('FILE')),
194 _('file with email aliases'), _('FILE')),
195 ] + commands.walkopts,
195 ] + commands.walkopts,
196 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]")),
196 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]")),
197 }
197 }
@@ -1,328 +1,328 b''
1 # extdiff.py - external diff program support for mercurial
1 # extdiff.py - external diff program support for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to allow external programs to compare revisions
8 '''command to allow external programs to compare revisions
9
9
10 The extdiff Mercurial extension allows you to use external programs
10 The extdiff Mercurial extension allows you to use external programs
11 to compare revisions, or revision with working directory. The external
11 to compare revisions, or revision with working directory. The external
12 diff programs are called with a configurable set of options and two
12 diff programs are called with a configurable set of options and two
13 non-option arguments: paths to directories containing snapshots of
13 non-option arguments: paths to directories containing snapshots of
14 files to compare.
14 files to compare.
15
15
16 The extdiff extension also allows you to configure new diff commands, so
16 The extdiff extension also allows you to configure new diff commands, so
17 you do not need to type :hg:`extdiff -p kdiff3` always. ::
17 you do not need to type :hg:`extdiff -p kdiff3` always. ::
18
18
19 [extdiff]
19 [extdiff]
20 # add new command that runs GNU diff(1) in 'context diff' mode
20 # add new command that runs GNU diff(1) in 'context diff' mode
21 cdiff = gdiff -Nprc5
21 cdiff = gdiff -Nprc5
22 ## or the old way:
22 ## or the old way:
23 #cmd.cdiff = gdiff
23 #cmd.cdiff = gdiff
24 #opts.cdiff = -Nprc5
24 #opts.cdiff = -Nprc5
25
25
26 # add new command called vdiff, runs kdiff3
26 # add new command called vdiff, runs kdiff3
27 vdiff = kdiff3
27 vdiff = kdiff3
28
28
29 # add new command called meld, runs meld (no need to name twice)
29 # add new command called meld, runs meld (no need to name twice)
30 meld =
30 meld =
31
31
32 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
32 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
33 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
33 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
34 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
34 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
35 # your .vimrc
35 # your .vimrc
36 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
36 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
37
37
38 Tool arguments can include variables that are expanded at runtime::
38 Tool arguments can include variables that are expanded at runtime::
39
39
40 $parent1, $plabel1 - filename, descriptive label of first parent
40 $parent1, $plabel1 - filename, descriptive label of first parent
41 $child, $clabel - filename, descriptive label of child revision
41 $child, $clabel - filename, descriptive label of child revision
42 $parent2, $plabel2 - filename, descriptive label of second parent
42 $parent2, $plabel2 - filename, descriptive label of second parent
43 $root - repository root
43 $root - repository root
44 $parent is an alias for $parent1.
44 $parent is an alias for $parent1.
45
45
46 The extdiff extension will look in your [diff-tools] and [merge-tools]
46 The extdiff extension will look in your [diff-tools] and [merge-tools]
47 sections for diff tool arguments, when none are specified in [extdiff].
47 sections for diff tool arguments, when none are specified in [extdiff].
48
48
49 ::
49 ::
50
50
51 [extdiff]
51 [extdiff]
52 kdiff3 =
52 kdiff3 =
53
53
54 [diff-tools]
54 [diff-tools]
55 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
55 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
56
56
57 You can use -I/-X and list of file or directory names like normal
57 You can use -I/-X and list of file or directory names like normal
58 :hg:`diff` command. The extdiff extension makes snapshots of only
58 :hg:`diff` command. The extdiff extension makes snapshots of only
59 needed files, so running the external diff program will actually be
59 needed files, so running the external diff program will actually be
60 pretty fast (at least faster than having to compare the entire tree).
60 pretty fast (at least faster than having to compare the entire tree).
61 '''
61 '''
62
62
63 from mercurial.i18n import _
63 from mercurial.i18n import _
64 from mercurial.node import short, nullid
64 from mercurial.node import short, nullid
65 from mercurial import scmutil, scmutil, util, commands, encoding
65 from mercurial import scmutil, scmutil, util, commands, encoding
66 import os, shlex, shutil, tempfile, re
66 import os, shlex, shutil, tempfile, re
67
67
68 def snapshot(ui, repo, files, node, tmproot):
68 def snapshot(ui, repo, files, node, tmproot):
69 '''snapshot files as of some revision
69 '''snapshot files as of some revision
70 if not using snapshot, -I/-X does not work and recursive diff
70 if not using snapshot, -I/-X does not work and recursive diff
71 in tools like kdiff3 and meld displays too many files.'''
71 in tools like kdiff3 and meld displays too many files.'''
72 dirname = os.path.basename(repo.root)
72 dirname = os.path.basename(repo.root)
73 if dirname == "":
73 if dirname == "":
74 dirname = "root"
74 dirname = "root"
75 if node is not None:
75 if node is not None:
76 dirname = '%s.%s' % (dirname, short(node))
76 dirname = '%s.%s' % (dirname, short(node))
77 base = os.path.join(tmproot, dirname)
77 base = os.path.join(tmproot, dirname)
78 os.mkdir(base)
78 os.mkdir(base)
79 if node is not None:
79 if node is not None:
80 ui.note(_('making snapshot of %d files from rev %s\n') %
80 ui.note(_('making snapshot of %d files from rev %s\n') %
81 (len(files), short(node)))
81 (len(files), short(node)))
82 else:
82 else:
83 ui.note(_('making snapshot of %d files from working directory\n') %
83 ui.note(_('making snapshot of %d files from working directory\n') %
84 (len(files)))
84 (len(files)))
85 wopener = scmutil.opener(base)
85 wopener = scmutil.opener(base)
86 fns_and_mtime = []
86 fns_and_mtime = []
87 ctx = repo[node]
87 ctx = repo[node]
88 for fn in files:
88 for fn in files:
89 wfn = util.pconvert(fn)
89 wfn = util.pconvert(fn)
90 if not wfn in ctx:
90 if not wfn in ctx:
91 # File doesn't exist; could be a bogus modify
91 # File doesn't exist; could be a bogus modify
92 continue
92 continue
93 ui.note(' %s\n' % wfn)
93 ui.note(' %s\n' % wfn)
94 dest = os.path.join(base, wfn)
94 dest = os.path.join(base, wfn)
95 fctx = ctx[wfn]
95 fctx = ctx[wfn]
96 data = repo.wwritedata(wfn, fctx.data())
96 data = repo.wwritedata(wfn, fctx.data())
97 if 'l' in fctx.flags():
97 if 'l' in fctx.flags():
98 wopener.symlink(data, wfn)
98 wopener.symlink(data, wfn)
99 else:
99 else:
100 wopener.write(wfn, data)
100 wopener.write(wfn, data)
101 if 'x' in fctx.flags():
101 if 'x' in fctx.flags():
102 util.setflags(dest, False, True)
102 util.setflags(dest, False, True)
103 if node is None:
103 if node is None:
104 fns_and_mtime.append((dest, repo.wjoin(fn),
104 fns_and_mtime.append((dest, repo.wjoin(fn),
105 os.lstat(dest).st_mtime))
105 os.lstat(dest).st_mtime))
106 return dirname, fns_and_mtime
106 return dirname, fns_and_mtime
107
107
108 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
108 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
109 '''Do the actuall diff:
109 '''Do the actuall diff:
110
110
111 - copy to a temp structure if diffing 2 internal revisions
111 - copy to a temp structure if diffing 2 internal revisions
112 - copy to a temp structure if diffing working revision with
112 - copy to a temp structure if diffing working revision with
113 another one and more than 1 file is changed
113 another one and more than 1 file is changed
114 - just invoke the diff for a single file in the working dir
114 - just invoke the diff for a single file in the working dir
115 '''
115 '''
116
116
117 revs = opts.get('rev')
117 revs = opts.get('rev')
118 change = opts.get('change')
118 change = opts.get('change')
119 args = ' '.join(diffopts)
119 args = ' '.join(diffopts)
120 do3way = '$parent2' in args
120 do3way = '$parent2' in args
121
121
122 if revs and change:
122 if revs and change:
123 msg = _('cannot specify --rev and --change at the same time')
123 msg = _('cannot specify --rev and --change at the same time')
124 raise util.Abort(msg)
124 raise util.Abort(msg)
125 elif change:
125 elif change:
126 node2 = scmutil.revsingle(repo, change, None).node()
126 node2 = scmutil.revsingle(repo, change, None).node()
127 node1a, node1b = repo.changelog.parents(node2)
127 node1a, node1b = repo.changelog.parents(node2)
128 else:
128 else:
129 node1a, node2 = scmutil.revpair(repo, revs)
129 node1a, node2 = scmutil.revpair(repo, revs)
130 if not revs:
130 if not revs:
131 node1b = repo.dirstate.p2()
131 node1b = repo.dirstate.p2()
132 else:
132 else:
133 node1b = nullid
133 node1b = nullid
134
134
135 # Disable 3-way merge if there is only one parent
135 # Disable 3-way merge if there is only one parent
136 if do3way:
136 if do3way:
137 if node1b == nullid:
137 if node1b == nullid:
138 do3way = False
138 do3way = False
139
139
140 matcher = scmutil.match(repo, pats, opts)
140 matcher = scmutil.match(repo[node2], pats, opts)
141 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
141 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
142 if do3way:
142 if do3way:
143 mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
143 mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
144 else:
144 else:
145 mod_b, add_b, rem_b = set(), set(), set()
145 mod_b, add_b, rem_b = set(), set(), set()
146 modadd = mod_a | add_a | mod_b | add_b
146 modadd = mod_a | add_a | mod_b | add_b
147 common = modadd | rem_a | rem_b
147 common = modadd | rem_a | rem_b
148 if not common:
148 if not common:
149 return 0
149 return 0
150
150
151 tmproot = tempfile.mkdtemp(prefix='extdiff.')
151 tmproot = tempfile.mkdtemp(prefix='extdiff.')
152 try:
152 try:
153 # Always make a copy of node1a (and node1b, if applicable)
153 # Always make a copy of node1a (and node1b, if applicable)
154 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
154 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
155 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
155 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
156 rev1a = '@%d' % repo[node1a].rev()
156 rev1a = '@%d' % repo[node1a].rev()
157 if do3way:
157 if do3way:
158 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
158 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
159 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
159 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
160 rev1b = '@%d' % repo[node1b].rev()
160 rev1b = '@%d' % repo[node1b].rev()
161 else:
161 else:
162 dir1b = None
162 dir1b = None
163 rev1b = ''
163 rev1b = ''
164
164
165 fns_and_mtime = []
165 fns_and_mtime = []
166
166
167 # If node2 in not the wc or there is >1 change, copy it
167 # If node2 in not the wc or there is >1 change, copy it
168 dir2root = ''
168 dir2root = ''
169 rev2 = ''
169 rev2 = ''
170 if node2:
170 if node2:
171 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
171 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
172 rev2 = '@%d' % repo[node2].rev()
172 rev2 = '@%d' % repo[node2].rev()
173 elif len(common) > 1:
173 elif len(common) > 1:
174 #we only actually need to get the files to copy back to
174 #we only actually need to get the files to copy back to
175 #the working dir in this case (because the other cases
175 #the working dir in this case (because the other cases
176 #are: diffing 2 revisions or single file -- in which case
176 #are: diffing 2 revisions or single file -- in which case
177 #the file is already directly passed to the diff tool).
177 #the file is already directly passed to the diff tool).
178 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
178 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
179 else:
179 else:
180 # This lets the diff tool open the changed file directly
180 # This lets the diff tool open the changed file directly
181 dir2 = ''
181 dir2 = ''
182 dir2root = repo.root
182 dir2root = repo.root
183
183
184 label1a = rev1a
184 label1a = rev1a
185 label1b = rev1b
185 label1b = rev1b
186 label2 = rev2
186 label2 = rev2
187
187
188 # If only one change, diff the files instead of the directories
188 # If only one change, diff the files instead of the directories
189 # Handle bogus modifies correctly by checking if the files exist
189 # Handle bogus modifies correctly by checking if the files exist
190 if len(common) == 1:
190 if len(common) == 1:
191 common_file = util.localpath(common.pop())
191 common_file = util.localpath(common.pop())
192 dir1a = os.path.join(tmproot, dir1a, common_file)
192 dir1a = os.path.join(tmproot, dir1a, common_file)
193 label1a = common_file + rev1a
193 label1a = common_file + rev1a
194 if not os.path.isfile(dir1a):
194 if not os.path.isfile(dir1a):
195 dir1a = os.devnull
195 dir1a = os.devnull
196 if do3way:
196 if do3way:
197 dir1b = os.path.join(tmproot, dir1b, common_file)
197 dir1b = os.path.join(tmproot, dir1b, common_file)
198 label1b = common_file + rev1b
198 label1b = common_file + rev1b
199 if not os.path.isfile(dir1b):
199 if not os.path.isfile(dir1b):
200 dir1b = os.devnull
200 dir1b = os.devnull
201 dir2 = os.path.join(dir2root, dir2, common_file)
201 dir2 = os.path.join(dir2root, dir2, common_file)
202 label2 = common_file + rev2
202 label2 = common_file + rev2
203
203
204 # Function to quote file/dir names in the argument string.
204 # Function to quote file/dir names in the argument string.
205 # When not operating in 3-way mode, an empty string is
205 # When not operating in 3-way mode, an empty string is
206 # returned for parent2
206 # returned for parent2
207 replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b,
207 replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b,
208 plabel1=label1a, plabel2=label1b,
208 plabel1=label1a, plabel2=label1b,
209 clabel=label2, child=dir2,
209 clabel=label2, child=dir2,
210 root=repo.root)
210 root=repo.root)
211 def quote(match):
211 def quote(match):
212 key = match.group()[1:]
212 key = match.group()[1:]
213 if not do3way and key == 'parent2':
213 if not do3way and key == 'parent2':
214 return ''
214 return ''
215 return util.shellquote(replace[key])
215 return util.shellquote(replace[key])
216
216
217 # Match parent2 first, so 'parent1?' will match both parent1 and parent
217 # Match parent2 first, so 'parent1?' will match both parent1 and parent
218 regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)'
218 regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)'
219 if not do3way and not re.search(regex, args):
219 if not do3way and not re.search(regex, args):
220 args += ' $parent1 $child'
220 args += ' $parent1 $child'
221 args = re.sub(regex, quote, args)
221 args = re.sub(regex, quote, args)
222 cmdline = util.shellquote(diffcmd) + ' ' + args
222 cmdline = util.shellquote(diffcmd) + ' ' + args
223
223
224 ui.debug('running %r in %s\n' % (cmdline, tmproot))
224 ui.debug('running %r in %s\n' % (cmdline, tmproot))
225 util.system(cmdline, cwd=tmproot)
225 util.system(cmdline, cwd=tmproot)
226
226
227 for copy_fn, working_fn, mtime in fns_and_mtime:
227 for copy_fn, working_fn, mtime in fns_and_mtime:
228 if os.lstat(copy_fn).st_mtime != mtime:
228 if os.lstat(copy_fn).st_mtime != mtime:
229 ui.debug('file changed while diffing. '
229 ui.debug('file changed while diffing. '
230 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
230 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
231 util.copyfile(copy_fn, working_fn)
231 util.copyfile(copy_fn, working_fn)
232
232
233 return 1
233 return 1
234 finally:
234 finally:
235 ui.note(_('cleaning up temp directory\n'))
235 ui.note(_('cleaning up temp directory\n'))
236 shutil.rmtree(tmproot)
236 shutil.rmtree(tmproot)
237
237
238 def extdiff(ui, repo, *pats, **opts):
238 def extdiff(ui, repo, *pats, **opts):
239 '''use external program to diff repository (or selected files)
239 '''use external program to diff repository (or selected files)
240
240
241 Show differences between revisions for the specified files, using
241 Show differences between revisions for the specified files, using
242 an external program. The default program used is diff, with
242 an external program. The default program used is diff, with
243 default options "-Npru".
243 default options "-Npru".
244
244
245 To select a different program, use the -p/--program option. The
245 To select a different program, use the -p/--program option. The
246 program will be passed the names of two directories to compare. To
246 program will be passed the names of two directories to compare. To
247 pass additional options to the program, use -o/--option. These
247 pass additional options to the program, use -o/--option. These
248 will be passed before the names of the directories to compare.
248 will be passed before the names of the directories to compare.
249
249
250 When two revision arguments are given, then changes are shown
250 When two revision arguments are given, then changes are shown
251 between those revisions. If only one revision is specified then
251 between those revisions. If only one revision is specified then
252 that revision is compared to the working directory, and, when no
252 that revision is compared to the working directory, and, when no
253 revisions are specified, the working directory files are compared
253 revisions are specified, the working directory files are compared
254 to its parent.'''
254 to its parent.'''
255 program = opts.get('program')
255 program = opts.get('program')
256 option = opts.get('option')
256 option = opts.get('option')
257 if not program:
257 if not program:
258 program = 'diff'
258 program = 'diff'
259 option = option or ['-Npru']
259 option = option or ['-Npru']
260 return dodiff(ui, repo, program, option, pats, opts)
260 return dodiff(ui, repo, program, option, pats, opts)
261
261
262 cmdtable = {
262 cmdtable = {
263 "extdiff":
263 "extdiff":
264 (extdiff,
264 (extdiff,
265 [('p', 'program', '',
265 [('p', 'program', '',
266 _('comparison program to run'), _('CMD')),
266 _('comparison program to run'), _('CMD')),
267 ('o', 'option', [],
267 ('o', 'option', [],
268 _('pass option to comparison program'), _('OPT')),
268 _('pass option to comparison program'), _('OPT')),
269 ('r', 'rev', [],
269 ('r', 'rev', [],
270 _('revision'), _('REV')),
270 _('revision'), _('REV')),
271 ('c', 'change', '',
271 ('c', 'change', '',
272 _('change made by revision'), _('REV')),
272 _('change made by revision'), _('REV')),
273 ] + commands.walkopts,
273 ] + commands.walkopts,
274 _('hg extdiff [OPT]... [FILE]...')),
274 _('hg extdiff [OPT]... [FILE]...')),
275 }
275 }
276
276
277 def uisetup(ui):
277 def uisetup(ui):
278 for cmd, path in ui.configitems('extdiff'):
278 for cmd, path in ui.configitems('extdiff'):
279 if cmd.startswith('cmd.'):
279 if cmd.startswith('cmd.'):
280 cmd = cmd[4:]
280 cmd = cmd[4:]
281 if not path:
281 if not path:
282 path = cmd
282 path = cmd
283 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
283 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
284 diffopts = diffopts and [diffopts] or []
284 diffopts = diffopts and [diffopts] or []
285 elif cmd.startswith('opts.'):
285 elif cmd.startswith('opts.'):
286 continue
286 continue
287 else:
287 else:
288 # command = path opts
288 # command = path opts
289 if path:
289 if path:
290 diffopts = shlex.split(path)
290 diffopts = shlex.split(path)
291 path = diffopts.pop(0)
291 path = diffopts.pop(0)
292 else:
292 else:
293 path, diffopts = cmd, []
293 path, diffopts = cmd, []
294 # look for diff arguments in [diff-tools] then [merge-tools]
294 # look for diff arguments in [diff-tools] then [merge-tools]
295 if diffopts == []:
295 if diffopts == []:
296 args = ui.config('diff-tools', cmd+'.diffargs') or \
296 args = ui.config('diff-tools', cmd+'.diffargs') or \
297 ui.config('merge-tools', cmd+'.diffargs')
297 ui.config('merge-tools', cmd+'.diffargs')
298 if args:
298 if args:
299 diffopts = shlex.split(args)
299 diffopts = shlex.split(args)
300 def save(cmd, path, diffopts):
300 def save(cmd, path, diffopts):
301 '''use closure to save diff command to use'''
301 '''use closure to save diff command to use'''
302 def mydiff(ui, repo, *pats, **opts):
302 def mydiff(ui, repo, *pats, **opts):
303 return dodiff(ui, repo, path, diffopts + opts['option'],
303 return dodiff(ui, repo, path, diffopts + opts['option'],
304 pats, opts)
304 pats, opts)
305 doc = _('''\
305 doc = _('''\
306 use %(path)s to diff repository (or selected files)
306 use %(path)s to diff repository (or selected files)
307
307
308 Show differences between revisions for the specified files, using
308 Show differences between revisions for the specified files, using
309 the %(path)s program.
309 the %(path)s program.
310
310
311 When two revision arguments are given, then changes are shown
311 When two revision arguments are given, then changes are shown
312 between those revisions. If only one revision is specified then
312 between those revisions. If only one revision is specified then
313 that revision is compared to the working directory, and, when no
313 that revision is compared to the working directory, and, when no
314 revisions are specified, the working directory files are compared
314 revisions are specified, the working directory files are compared
315 to its parent.\
315 to its parent.\
316 ''') % dict(path=util.uirepr(path))
316 ''') % dict(path=util.uirepr(path))
317
317
318 # We must translate the docstring right away since it is
318 # We must translate the docstring right away since it is
319 # used as a format string. The string will unfortunately
319 # used as a format string. The string will unfortunately
320 # be translated again in commands.helpcmd and this will
320 # be translated again in commands.helpcmd and this will
321 # fail when the docstring contains non-ASCII characters.
321 # fail when the docstring contains non-ASCII characters.
322 # Decoding the string to a Unicode string here (using the
322 # Decoding the string to a Unicode string here (using the
323 # right encoding) prevents that.
323 # right encoding) prevents that.
324 mydiff.__doc__ = doc.decode(encoding.encoding)
324 mydiff.__doc__ = doc.decode(encoding.encoding)
325 return mydiff
325 return mydiff
326 cmdtable[cmd] = (save(cmd, path, diffopts),
326 cmdtable[cmd] = (save(cmd, path, diffopts),
327 cmdtable['extdiff'][1][1:],
327 cmdtable['extdiff'][1][1:],
328 _('hg %s [OPTION]... [FILE]...') % cmd)
328 _('hg %s [OPTION]... [FILE]...') % cmd)
@@ -1,348 +1,348 b''
1 # Minimal support for git commands on an hg repository
1 # Minimal support for git commands on an hg repository
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms 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 '''browse the repository in a graphical way
8 '''browse the repository in a graphical way
9
9
10 The hgk extension allows browsing the history of a repository in a
10 The hgk extension allows browsing the history of a repository in a
11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
12 distributed with Mercurial.)
12 distributed with Mercurial.)
13
13
14 hgk consists of two parts: a Tcl script that does the displaying and
14 hgk consists of two parts: a Tcl script that does the displaying and
15 querying of information, and an extension to Mercurial named hgk.py,
15 querying of information, and an extension to Mercurial named hgk.py,
16 which provides hooks for hgk to get information. hgk can be found in
16 which provides hooks for hgk to get information. hgk can be found in
17 the contrib directory, and the extension is shipped in the hgext
17 the contrib directory, and the extension is shipped in the hgext
18 repository, and needs to be enabled.
18 repository, and needs to be enabled.
19
19
20 The :hg:`view` command will launch the hgk Tcl script. For this command
20 The :hg:`view` command will launch the hgk Tcl script. For this command
21 to work, hgk must be in your search path. Alternately, you can specify
21 to work, hgk must be in your search path. Alternately, you can specify
22 the path to hgk in your configuration file::
22 the path to hgk in your configuration file::
23
23
24 [hgk]
24 [hgk]
25 path=/location/of/hgk
25 path=/location/of/hgk
26
26
27 hgk can make use of the extdiff extension to visualize revisions.
27 hgk can make use of the extdiff extension to visualize revisions.
28 Assuming you had already configured extdiff vdiff command, just add::
28 Assuming you had already configured extdiff vdiff command, just add::
29
29
30 [hgk]
30 [hgk]
31 vdiff=vdiff
31 vdiff=vdiff
32
32
33 Revisions context menu will now display additional entries to fire
33 Revisions context menu will now display additional entries to fire
34 vdiff on hovered and selected revisions.
34 vdiff on hovered and selected revisions.
35 '''
35 '''
36
36
37 import os
37 import os
38 from mercurial import commands, util, patch, revlog, scmutil
38 from mercurial import commands, util, patch, revlog, scmutil
39 from mercurial.node import nullid, nullrev, short
39 from mercurial.node import nullid, nullrev, short
40 from mercurial.i18n import _
40 from mercurial.i18n import _
41
41
42 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
42 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
43 """diff trees from two commits"""
43 """diff trees from two commits"""
44 def __difftree(repo, node1, node2, files=[]):
44 def __difftree(repo, node1, node2, files=[]):
45 assert node2 is not None
45 assert node2 is not None
46 mmap = repo[node1].manifest()
46 mmap = repo[node1].manifest()
47 mmap2 = repo[node2].manifest()
47 mmap2 = repo[node2].manifest()
48 m = scmutil.match(repo, files)
48 m = scmutil.match(repo[node1], files)
49 modified, added, removed = repo.status(node1, node2, m)[:3]
49 modified, added, removed = repo.status(node1, node2, m)[:3]
50 empty = short(nullid)
50 empty = short(nullid)
51
51
52 for f in modified:
52 for f in modified:
53 # TODO get file permissions
53 # TODO get file permissions
54 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
54 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
55 (short(mmap[f]), short(mmap2[f]), f, f))
55 (short(mmap[f]), short(mmap2[f]), f, f))
56 for f in added:
56 for f in added:
57 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
57 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
58 (empty, short(mmap2[f]), f, f))
58 (empty, short(mmap2[f]), f, f))
59 for f in removed:
59 for f in removed:
60 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
60 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
61 (short(mmap[f]), empty, f, f))
61 (short(mmap[f]), empty, f, f))
62 ##
62 ##
63
63
64 while True:
64 while True:
65 if opts['stdin']:
65 if opts['stdin']:
66 try:
66 try:
67 line = raw_input().split(' ')
67 line = raw_input().split(' ')
68 node1 = line[0]
68 node1 = line[0]
69 if len(line) > 1:
69 if len(line) > 1:
70 node2 = line[1]
70 node2 = line[1]
71 else:
71 else:
72 node2 = None
72 node2 = None
73 except EOFError:
73 except EOFError:
74 break
74 break
75 node1 = repo.lookup(node1)
75 node1 = repo.lookup(node1)
76 if node2:
76 if node2:
77 node2 = repo.lookup(node2)
77 node2 = repo.lookup(node2)
78 else:
78 else:
79 node2 = node1
79 node2 = node1
80 node1 = repo.changelog.parents(node1)[0]
80 node1 = repo.changelog.parents(node1)[0]
81 if opts['patch']:
81 if opts['patch']:
82 if opts['pretty']:
82 if opts['pretty']:
83 catcommit(ui, repo, node2, "")
83 catcommit(ui, repo, node2, "")
84 m = scmutil.match(repo, files)
84 m = scmutil.match(repo[node1], files)
85 chunks = patch.diff(repo, node1, node2, match=m,
85 chunks = patch.diff(repo, node1, node2, match=m,
86 opts=patch.diffopts(ui, {'git': True}))
86 opts=patch.diffopts(ui, {'git': True}))
87 for chunk in chunks:
87 for chunk in chunks:
88 ui.write(chunk)
88 ui.write(chunk)
89 else:
89 else:
90 __difftree(repo, node1, node2, files=files)
90 __difftree(repo, node1, node2, files=files)
91 if not opts['stdin']:
91 if not opts['stdin']:
92 break
92 break
93
93
94 def catcommit(ui, repo, n, prefix, ctx=None):
94 def catcommit(ui, repo, n, prefix, ctx=None):
95 nlprefix = '\n' + prefix
95 nlprefix = '\n' + prefix
96 if ctx is None:
96 if ctx is None:
97 ctx = repo[n]
97 ctx = repo[n]
98 ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ??
98 ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ??
99 for p in ctx.parents():
99 for p in ctx.parents():
100 ui.write("parent %s\n" % p)
100 ui.write("parent %s\n" % p)
101
101
102 date = ctx.date()
102 date = ctx.date()
103 description = ctx.description().replace("\0", "")
103 description = ctx.description().replace("\0", "")
104 lines = description.splitlines()
104 lines = description.splitlines()
105 if lines and lines[-1].startswith('committer:'):
105 if lines and lines[-1].startswith('committer:'):
106 committer = lines[-1].split(': ')[1].rstrip()
106 committer = lines[-1].split(': ')[1].rstrip()
107 else:
107 else:
108 committer = ctx.user()
108 committer = ctx.user()
109
109
110 ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
110 ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
111 ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
111 ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
112 ui.write("revision %d\n" % ctx.rev())
112 ui.write("revision %d\n" % ctx.rev())
113 ui.write("branch %s\n\n" % ctx.branch())
113 ui.write("branch %s\n\n" % ctx.branch())
114
114
115 if prefix != "":
115 if prefix != "":
116 ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
116 ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
117 else:
117 else:
118 ui.write(description + "\n")
118 ui.write(description + "\n")
119 if prefix:
119 if prefix:
120 ui.write('\0')
120 ui.write('\0')
121
121
122 def base(ui, repo, node1, node2):
122 def base(ui, repo, node1, node2):
123 """output common ancestor information"""
123 """output common ancestor information"""
124 node1 = repo.lookup(node1)
124 node1 = repo.lookup(node1)
125 node2 = repo.lookup(node2)
125 node2 = repo.lookup(node2)
126 n = repo.changelog.ancestor(node1, node2)
126 n = repo.changelog.ancestor(node1, node2)
127 ui.write(short(n) + "\n")
127 ui.write(short(n) + "\n")
128
128
129 def catfile(ui, repo, type=None, r=None, **opts):
129 def catfile(ui, repo, type=None, r=None, **opts):
130 """cat a specific revision"""
130 """cat a specific revision"""
131 # in stdin mode, every line except the commit is prefixed with two
131 # in stdin mode, every line except the commit is prefixed with two
132 # spaces. This way the our caller can find the commit without magic
132 # spaces. This way the our caller can find the commit without magic
133 # strings
133 # strings
134 #
134 #
135 prefix = ""
135 prefix = ""
136 if opts['stdin']:
136 if opts['stdin']:
137 try:
137 try:
138 (type, r) = raw_input().split(' ')
138 (type, r) = raw_input().split(' ')
139 prefix = " "
139 prefix = " "
140 except EOFError:
140 except EOFError:
141 return
141 return
142
142
143 else:
143 else:
144 if not type or not r:
144 if not type or not r:
145 ui.warn(_("cat-file: type or revision not supplied\n"))
145 ui.warn(_("cat-file: type or revision not supplied\n"))
146 commands.help_(ui, 'cat-file')
146 commands.help_(ui, 'cat-file')
147
147
148 while r:
148 while r:
149 if type != "commit":
149 if type != "commit":
150 ui.warn(_("aborting hg cat-file only understands commits\n"))
150 ui.warn(_("aborting hg cat-file only understands commits\n"))
151 return 1
151 return 1
152 n = repo.lookup(r)
152 n = repo.lookup(r)
153 catcommit(ui, repo, n, prefix)
153 catcommit(ui, repo, n, prefix)
154 if opts['stdin']:
154 if opts['stdin']:
155 try:
155 try:
156 (type, r) = raw_input().split(' ')
156 (type, r) = raw_input().split(' ')
157 except EOFError:
157 except EOFError:
158 break
158 break
159 else:
159 else:
160 break
160 break
161
161
162 # git rev-tree is a confusing thing. You can supply a number of
162 # git rev-tree is a confusing thing. You can supply a number of
163 # commit sha1s on the command line, and it walks the commit history
163 # commit sha1s on the command line, and it walks the commit history
164 # telling you which commits are reachable from the supplied ones via
164 # telling you which commits are reachable from the supplied ones via
165 # a bitmask based on arg position.
165 # a bitmask based on arg position.
166 # you can specify a commit to stop at by starting the sha1 with ^
166 # you can specify a commit to stop at by starting the sha1 with ^
167 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
167 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
168 def chlogwalk():
168 def chlogwalk():
169 count = len(repo)
169 count = len(repo)
170 i = count
170 i = count
171 l = [0] * 100
171 l = [0] * 100
172 chunk = 100
172 chunk = 100
173 while True:
173 while True:
174 if chunk > i:
174 if chunk > i:
175 chunk = i
175 chunk = i
176 i = 0
176 i = 0
177 else:
177 else:
178 i -= chunk
178 i -= chunk
179
179
180 for x in xrange(chunk):
180 for x in xrange(chunk):
181 if i + x >= count:
181 if i + x >= count:
182 l[chunk - x:] = [0] * (chunk - x)
182 l[chunk - x:] = [0] * (chunk - x)
183 break
183 break
184 if full is not None:
184 if full is not None:
185 l[x] = repo[i + x]
185 l[x] = repo[i + x]
186 l[x].changeset() # force reading
186 l[x].changeset() # force reading
187 else:
187 else:
188 l[x] = 1
188 l[x] = 1
189 for x in xrange(chunk - 1, -1, -1):
189 for x in xrange(chunk - 1, -1, -1):
190 if l[x] != 0:
190 if l[x] != 0:
191 yield (i + x, full is not None and l[x] or None)
191 yield (i + x, full is not None and l[x] or None)
192 if i == 0:
192 if i == 0:
193 break
193 break
194
194
195 # calculate and return the reachability bitmask for sha
195 # calculate and return the reachability bitmask for sha
196 def is_reachable(ar, reachable, sha):
196 def is_reachable(ar, reachable, sha):
197 if len(ar) == 0:
197 if len(ar) == 0:
198 return 1
198 return 1
199 mask = 0
199 mask = 0
200 for i in xrange(len(ar)):
200 for i in xrange(len(ar)):
201 if sha in reachable[i]:
201 if sha in reachable[i]:
202 mask |= 1 << i
202 mask |= 1 << i
203
203
204 return mask
204 return mask
205
205
206 reachable = []
206 reachable = []
207 stop_sha1 = []
207 stop_sha1 = []
208 want_sha1 = []
208 want_sha1 = []
209 count = 0
209 count = 0
210
210
211 # figure out which commits they are asking for and which ones they
211 # figure out which commits they are asking for and which ones they
212 # want us to stop on
212 # want us to stop on
213 for i, arg in enumerate(args):
213 for i, arg in enumerate(args):
214 if arg.startswith('^'):
214 if arg.startswith('^'):
215 s = repo.lookup(arg[1:])
215 s = repo.lookup(arg[1:])
216 stop_sha1.append(s)
216 stop_sha1.append(s)
217 want_sha1.append(s)
217 want_sha1.append(s)
218 elif arg != 'HEAD':
218 elif arg != 'HEAD':
219 want_sha1.append(repo.lookup(arg))
219 want_sha1.append(repo.lookup(arg))
220
220
221 # calculate the graph for the supplied commits
221 # calculate the graph for the supplied commits
222 for i, n in enumerate(want_sha1):
222 for i, n in enumerate(want_sha1):
223 reachable.append(set())
223 reachable.append(set())
224 visit = [n]
224 visit = [n]
225 reachable[i].add(n)
225 reachable[i].add(n)
226 while visit:
226 while visit:
227 n = visit.pop(0)
227 n = visit.pop(0)
228 if n in stop_sha1:
228 if n in stop_sha1:
229 continue
229 continue
230 for p in repo.changelog.parents(n):
230 for p in repo.changelog.parents(n):
231 if p not in reachable[i]:
231 if p not in reachable[i]:
232 reachable[i].add(p)
232 reachable[i].add(p)
233 visit.append(p)
233 visit.append(p)
234 if p in stop_sha1:
234 if p in stop_sha1:
235 continue
235 continue
236
236
237 # walk the repository looking for commits that are in our
237 # walk the repository looking for commits that are in our
238 # reachability graph
238 # reachability graph
239 for i, ctx in chlogwalk():
239 for i, ctx in chlogwalk():
240 n = repo.changelog.node(i)
240 n = repo.changelog.node(i)
241 mask = is_reachable(want_sha1, reachable, n)
241 mask = is_reachable(want_sha1, reachable, n)
242 if mask:
242 if mask:
243 parentstr = ""
243 parentstr = ""
244 if parents:
244 if parents:
245 pp = repo.changelog.parents(n)
245 pp = repo.changelog.parents(n)
246 if pp[0] != nullid:
246 if pp[0] != nullid:
247 parentstr += " " + short(pp[0])
247 parentstr += " " + short(pp[0])
248 if pp[1] != nullid:
248 if pp[1] != nullid:
249 parentstr += " " + short(pp[1])
249 parentstr += " " + short(pp[1])
250 if not full:
250 if not full:
251 ui.write("%s%s\n" % (short(n), parentstr))
251 ui.write("%s%s\n" % (short(n), parentstr))
252 elif full == "commit":
252 elif full == "commit":
253 ui.write("%s%s\n" % (short(n), parentstr))
253 ui.write("%s%s\n" % (short(n), parentstr))
254 catcommit(ui, repo, n, ' ', ctx)
254 catcommit(ui, repo, n, ' ', ctx)
255 else:
255 else:
256 (p1, p2) = repo.changelog.parents(n)
256 (p1, p2) = repo.changelog.parents(n)
257 (h, h1, h2) = map(short, (n, p1, p2))
257 (h, h1, h2) = map(short, (n, p1, p2))
258 (i1, i2) = map(repo.changelog.rev, (p1, p2))
258 (i1, i2) = map(repo.changelog.rev, (p1, p2))
259
259
260 date = ctx.date()[0]
260 date = ctx.date()[0]
261 ui.write("%s %s:%s" % (date, h, mask))
261 ui.write("%s %s:%s" % (date, h, mask))
262 mask = is_reachable(want_sha1, reachable, p1)
262 mask = is_reachable(want_sha1, reachable, p1)
263 if i1 != nullrev and mask > 0:
263 if i1 != nullrev and mask > 0:
264 ui.write("%s:%s " % (h1, mask)),
264 ui.write("%s:%s " % (h1, mask)),
265 mask = is_reachable(want_sha1, reachable, p2)
265 mask = is_reachable(want_sha1, reachable, p2)
266 if i2 != nullrev and mask > 0:
266 if i2 != nullrev and mask > 0:
267 ui.write("%s:%s " % (h2, mask))
267 ui.write("%s:%s " % (h2, mask))
268 ui.write("\n")
268 ui.write("\n")
269 if maxnr and count >= maxnr:
269 if maxnr and count >= maxnr:
270 break
270 break
271 count += 1
271 count += 1
272
272
273 def revparse(ui, repo, *revs, **opts):
273 def revparse(ui, repo, *revs, **opts):
274 """parse given revisions"""
274 """parse given revisions"""
275 def revstr(rev):
275 def revstr(rev):
276 if rev == 'HEAD':
276 if rev == 'HEAD':
277 rev = 'tip'
277 rev = 'tip'
278 return revlog.hex(repo.lookup(rev))
278 return revlog.hex(repo.lookup(rev))
279
279
280 for r in revs:
280 for r in revs:
281 revrange = r.split(':', 1)
281 revrange = r.split(':', 1)
282 ui.write('%s\n' % revstr(revrange[0]))
282 ui.write('%s\n' % revstr(revrange[0]))
283 if len(revrange) == 2:
283 if len(revrange) == 2:
284 ui.write('^%s\n' % revstr(revrange[1]))
284 ui.write('^%s\n' % revstr(revrange[1]))
285
285
286 # git rev-list tries to order things by date, and has the ability to stop
286 # git rev-list tries to order things by date, and has the ability to stop
287 # at a given commit without walking the whole repo. TODO add the stop
287 # at a given commit without walking the whole repo. TODO add the stop
288 # parameter
288 # parameter
289 def revlist(ui, repo, *revs, **opts):
289 def revlist(ui, repo, *revs, **opts):
290 """print revisions"""
290 """print revisions"""
291 if opts['header']:
291 if opts['header']:
292 full = "commit"
292 full = "commit"
293 else:
293 else:
294 full = None
294 full = None
295 copy = [x for x in revs]
295 copy = [x for x in revs]
296 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
296 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
297
297
298 def config(ui, repo, **opts):
298 def config(ui, repo, **opts):
299 """print extension options"""
299 """print extension options"""
300 def writeopt(name, value):
300 def writeopt(name, value):
301 ui.write('k=%s\nv=%s\n' % (name, value))
301 ui.write('k=%s\nv=%s\n' % (name, value))
302
302
303 writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
303 writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
304
304
305
305
306 def view(ui, repo, *etc, **opts):
306 def view(ui, repo, *etc, **opts):
307 "start interactive history viewer"
307 "start interactive history viewer"
308 os.chdir(repo.root)
308 os.chdir(repo.root)
309 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
309 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
310 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
310 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
311 ui.debug("running %s\n" % cmd)
311 ui.debug("running %s\n" % cmd)
312 util.system(cmd)
312 util.system(cmd)
313
313
314 cmdtable = {
314 cmdtable = {
315 "^view":
315 "^view":
316 (view,
316 (view,
317 [('l', 'limit', '',
317 [('l', 'limit', '',
318 _('limit number of changes displayed'), _('NUM'))],
318 _('limit number of changes displayed'), _('NUM'))],
319 _('hg view [-l LIMIT] [REVRANGE]')),
319 _('hg view [-l LIMIT] [REVRANGE]')),
320 "debug-diff-tree":
320 "debug-diff-tree":
321 (difftree,
321 (difftree,
322 [('p', 'patch', None, _('generate patch')),
322 [('p', 'patch', None, _('generate patch')),
323 ('r', 'recursive', None, _('recursive')),
323 ('r', 'recursive', None, _('recursive')),
324 ('P', 'pretty', None, _('pretty')),
324 ('P', 'pretty', None, _('pretty')),
325 ('s', 'stdin', None, _('stdin')),
325 ('s', 'stdin', None, _('stdin')),
326 ('C', 'copy', None, _('detect copies')),
326 ('C', 'copy', None, _('detect copies')),
327 ('S', 'search', "", _('search'))],
327 ('S', 'search', "", _('search'))],
328 _('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...')),
328 _('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...')),
329 "debug-cat-file":
329 "debug-cat-file":
330 (catfile,
330 (catfile,
331 [('s', 'stdin', None, _('stdin'))],
331 [('s', 'stdin', None, _('stdin'))],
332 _('hg debug-cat-file [OPTION]... TYPE FILE')),
332 _('hg debug-cat-file [OPTION]... TYPE FILE')),
333 "debug-config":
333 "debug-config":
334 (config, [], _('hg debug-config')),
334 (config, [], _('hg debug-config')),
335 "debug-merge-base":
335 "debug-merge-base":
336 (base, [], _('hg debug-merge-base REV REV')),
336 (base, [], _('hg debug-merge-base REV REV')),
337 "debug-rev-parse":
337 "debug-rev-parse":
338 (revparse,
338 (revparse,
339 [('', 'default', '', _('ignored'))],
339 [('', 'default', '', _('ignored'))],
340 _('hg debug-rev-parse REV')),
340 _('hg debug-rev-parse REV')),
341 "debug-rev-list":
341 "debug-rev-list":
342 (revlist,
342 (revlist,
343 [('H', 'header', None, _('header')),
343 [('H', 'header', None, _('header')),
344 ('t', 'topo-order', None, _('topo-order')),
344 ('t', 'topo-order', None, _('topo-order')),
345 ('p', 'parents', None, _('parents')),
345 ('p', 'parents', None, _('parents')),
346 ('n', 'max-count', 0, _('max-count'))],
346 ('n', 'max-count', 0, _('max-count'))],
347 _('hg debug-rev-list [OPTION]... REV...')),
347 _('hg debug-rev-list [OPTION]... REV...')),
348 }
348 }
@@ -1,690 +1,690 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[None], 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, gp, backend, store, eolmode=None):
598 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
599 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
599 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
600 rejects or conflicts due to expanded keywords in working dir.'''
600 rejects or conflicts due to expanded keywords in working dir.'''
601 orig(self, ui, gp, backend, store, eolmode)
601 orig(self, ui, gp, backend, store, eolmode)
602 # shrink keywords read from working dir
602 # shrink keywords read from working dir
603 self.lines = kwt.shrinklines(self.fname, self.lines)
603 self.lines = kwt.shrinklines(self.fname, self.lines)
604
604
605 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
605 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
606 opts=None, prefix=''):
606 opts=None, prefix=''):
607 '''Monkeypatch patch.diff to avoid expansion.'''
607 '''Monkeypatch patch.diff to avoid expansion.'''
608 kwt.restrict = True
608 kwt.restrict = True
609 return orig(repo, node1, node2, match, changes, opts, prefix)
609 return orig(repo, node1, node2, match, changes, opts, prefix)
610
610
611 def kwweb_skip(orig, web, req, tmpl):
611 def kwweb_skip(orig, web, req, tmpl):
612 '''Wraps webcommands.x turning off keyword expansion.'''
612 '''Wraps webcommands.x turning off keyword expansion.'''
613 kwt.match = util.never
613 kwt.match = util.never
614 return orig(web, req, tmpl)
614 return orig(web, req, tmpl)
615
615
616 def kw_copy(orig, ui, repo, pats, opts, rename=False):
616 def kw_copy(orig, ui, repo, pats, opts, rename=False):
617 '''Wraps cmdutil.copy so that copy/rename destinations do not
617 '''Wraps cmdutil.copy so that copy/rename destinations do not
618 contain expanded keywords.
618 contain expanded keywords.
619 Note that the source of a regular file destination may also be a
619 Note that the source of a regular file destination may also be a
620 symlink:
620 symlink:
621 hg cp sym x -> x is symlink
621 hg cp sym x -> x is symlink
622 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
622 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
623 For the latter we have to follow the symlink to find out whether its
623 For the latter we have to follow the symlink to find out whether its
624 target is configured for expansion and we therefore must unexpand the
624 target is configured for expansion and we therefore must unexpand the
625 keywords in the destination.'''
625 keywords in the destination.'''
626 orig(ui, repo, pats, opts, rename)
626 orig(ui, repo, pats, opts, rename)
627 if opts.get('dry_run'):
627 if opts.get('dry_run'):
628 return
628 return
629 wctx = repo[None]
629 wctx = repo[None]
630 cwd = repo.getcwd()
630 cwd = repo.getcwd()
631
631
632 def haskwsource(dest):
632 def haskwsource(dest):
633 '''Returns true if dest is a regular file and configured for
633 '''Returns true if dest is a regular file and configured for
634 expansion or a symlink which points to a file configured for
634 expansion or a symlink which points to a file configured for
635 expansion. '''
635 expansion. '''
636 source = repo.dirstate.copied(dest)
636 source = repo.dirstate.copied(dest)
637 if 'l' in wctx.flags(source):
637 if 'l' in wctx.flags(source):
638 source = scmutil.canonpath(repo.root, cwd,
638 source = scmutil.canonpath(repo.root, cwd,
639 os.path.realpath(source))
639 os.path.realpath(source))
640 return kwt.match(source)
640 return kwt.match(source)
641
641
642 candidates = [f for f in repo.dirstate.copies() if
642 candidates = [f for f in repo.dirstate.copies() if
643 not 'l' in wctx.flags(f) and haskwsource(f)]
643 not 'l' in wctx.flags(f) and haskwsource(f)]
644 kwt.overwrite(wctx, candidates, False, False)
644 kwt.overwrite(wctx, candidates, False, False)
645
645
646 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
646 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
647 '''Wraps record.dorecord expanding keywords after recording.'''
647 '''Wraps record.dorecord expanding keywords after recording.'''
648 wlock = repo.wlock()
648 wlock = repo.wlock()
649 try:
649 try:
650 # record returns 0 even when nothing has changed
650 # record returns 0 even when nothing has changed
651 # therefore compare nodes before and after
651 # therefore compare nodes before and after
652 kwt.record = True
652 kwt.record = True
653 ctx = repo['.']
653 ctx = repo['.']
654 wstatus = repo[None].status()
654 wstatus = repo[None].status()
655 ret = orig(ui, repo, commitfunc, *pats, **opts)
655 ret = orig(ui, repo, commitfunc, *pats, **opts)
656 recctx = repo['.']
656 recctx = repo['.']
657 if ctx != recctx:
657 if ctx != recctx:
658 modified, added = _preselect(wstatus, recctx.files())
658 modified, added = _preselect(wstatus, recctx.files())
659 kwt.restrict = False
659 kwt.restrict = False
660 kwt.overwrite(recctx, modified, False, True)
660 kwt.overwrite(recctx, modified, False, True)
661 kwt.overwrite(recctx, added, False, True, True)
661 kwt.overwrite(recctx, added, False, True, True)
662 kwt.restrict = True
662 kwt.restrict = True
663 return ret
663 return ret
664 finally:
664 finally:
665 wlock.release()
665 wlock.release()
666
666
667 def kwfilectx_cmp(orig, self, fctx):
667 def kwfilectx_cmp(orig, self, fctx):
668 # keyword affects data size, comparing wdir and filelog size does
668 # keyword affects data size, comparing wdir and filelog size does
669 # not make sense
669 # not make sense
670 if (fctx._filerev is None and
670 if (fctx._filerev is None and
671 (self._repo._encodefilterpats or
671 (self._repo._encodefilterpats or
672 kwt.match(fctx.path()) and not 'l' in fctx.flags()) or
672 kwt.match(fctx.path()) and not 'l' in fctx.flags()) or
673 self.size() == fctx.size()):
673 self.size() == fctx.size()):
674 return self._filelog.cmp(self._filenode, fctx.data())
674 return self._filelog.cmp(self._filenode, fctx.data())
675 return True
675 return True
676
676
677 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
677 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
678 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
678 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
679 extensions.wrapfunction(patch, 'diff', kw_diff)
679 extensions.wrapfunction(patch, 'diff', kw_diff)
680 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
680 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
681 for c in 'annotate changeset rev filediff diff'.split():
681 for c in 'annotate changeset rev filediff diff'.split():
682 extensions.wrapfunction(webcommands, c, kwweb_skip)
682 extensions.wrapfunction(webcommands, c, kwweb_skip)
683 for name in recordextensions.split():
683 for name in recordextensions.split():
684 try:
684 try:
685 record = extensions.find(name)
685 record = extensions.find(name)
686 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
686 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
687 except KeyError:
687 except KeyError:
688 pass
688 pass
689
689
690 repo.__class__ = kwrepo
690 repo.__class__ = kwrepo
@@ -1,3301 +1,3301 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use :hg:`help command` for more details)::
17 Common tasks (use :hg:`help command` for more details)::
18
18
19 create new patch qnew
19 create new patch qnew
20 import existing patch qimport
20 import existing patch qimport
21
21
22 print patch series qseries
22 print patch series qseries
23 print applied patches qapplied
23 print applied patches qapplied
24
24
25 add known patch to applied stack qpush
25 add known patch to applied stack qpush
26 remove patch from applied stack qpop
26 remove patch from applied stack qpop
27 refresh contents of top applied patch qrefresh
27 refresh contents of top applied patch qrefresh
28
28
29 By default, mq will automatically use git patches when required to
29 By default, mq will automatically use git patches when required to
30 avoid losing file mode changes, copy records, binary files or empty
30 avoid losing file mode changes, copy records, binary files or empty
31 files creations or deletions. This behaviour can be configured with::
31 files creations or deletions. This behaviour can be configured with::
32
32
33 [mq]
33 [mq]
34 git = auto/keep/yes/no
34 git = auto/keep/yes/no
35
35
36 If set to 'keep', mq will obey the [diff] section configuration while
36 If set to 'keep', mq will obey the [diff] section configuration while
37 preserving existing git patches upon qrefresh. If set to 'yes' or
37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 'no', mq will override the [diff] section and always generate git or
38 'no', mq will override the [diff] section and always generate git or
39 regular patches, possibly losing data in the second case.
39 regular patches, possibly losing data in the second case.
40
40
41 You will by default be managing a patch queue named "patches". You can
41 You will by default be managing a patch queue named "patches". You can
42 create other, independent patch queues with the :hg:`qqueue` command.
42 create other, independent patch queues with the :hg:`qqueue` command.
43 '''
43 '''
44
44
45 from mercurial.i18n import _
45 from mercurial.i18n import _
46 from mercurial.node import bin, hex, short, nullid, nullrev
46 from mercurial.node import bin, hex, short, nullid, nullrev
47 from mercurial.lock import release
47 from mercurial.lock import release
48 from mercurial import commands, cmdutil, hg, scmutil, util, revset
48 from mercurial import commands, cmdutil, hg, scmutil, util, revset
49 from mercurial import repair, extensions, url, error
49 from mercurial import repair, extensions, url, error
50 from mercurial import patch as patchmod
50 from mercurial import patch as patchmod
51 import os, re, errno, shutil
51 import os, re, errno, shutil
52
52
53 commands.norepo += " qclone"
53 commands.norepo += " qclone"
54
54
55 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
55 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
56
56
57 cmdtable = {}
57 cmdtable = {}
58 command = cmdutil.command(cmdtable)
58 command = cmdutil.command(cmdtable)
59
59
60 # Patch names looks like unix-file names.
60 # Patch names looks like unix-file names.
61 # They must be joinable with queue directory and result in the patch path.
61 # They must be joinable with queue directory and result in the patch path.
62 normname = util.normpath
62 normname = util.normpath
63
63
64 class statusentry(object):
64 class statusentry(object):
65 def __init__(self, node, name):
65 def __init__(self, node, name):
66 self.node, self.name = node, name
66 self.node, self.name = node, name
67 def __repr__(self):
67 def __repr__(self):
68 return hex(self.node) + ':' + self.name
68 return hex(self.node) + ':' + self.name
69
69
70 class patchheader(object):
70 class patchheader(object):
71 def __init__(self, pf, plainmode=False):
71 def __init__(self, pf, plainmode=False):
72 def eatdiff(lines):
72 def eatdiff(lines):
73 while lines:
73 while lines:
74 l = lines[-1]
74 l = lines[-1]
75 if (l.startswith("diff -") or
75 if (l.startswith("diff -") or
76 l.startswith("Index:") or
76 l.startswith("Index:") or
77 l.startswith("===========")):
77 l.startswith("===========")):
78 del lines[-1]
78 del lines[-1]
79 else:
79 else:
80 break
80 break
81 def eatempty(lines):
81 def eatempty(lines):
82 while lines:
82 while lines:
83 if not lines[-1].strip():
83 if not lines[-1].strip():
84 del lines[-1]
84 del lines[-1]
85 else:
85 else:
86 break
86 break
87
87
88 message = []
88 message = []
89 comments = []
89 comments = []
90 user = None
90 user = None
91 date = None
91 date = None
92 parent = None
92 parent = None
93 format = None
93 format = None
94 subject = None
94 subject = None
95 branch = None
95 branch = None
96 nodeid = None
96 nodeid = None
97 diffstart = 0
97 diffstart = 0
98
98
99 for line in file(pf):
99 for line in file(pf):
100 line = line.rstrip()
100 line = line.rstrip()
101 if (line.startswith('diff --git')
101 if (line.startswith('diff --git')
102 or (diffstart and line.startswith('+++ '))):
102 or (diffstart and line.startswith('+++ '))):
103 diffstart = 2
103 diffstart = 2
104 break
104 break
105 diffstart = 0 # reset
105 diffstart = 0 # reset
106 if line.startswith("--- "):
106 if line.startswith("--- "):
107 diffstart = 1
107 diffstart = 1
108 continue
108 continue
109 elif format == "hgpatch":
109 elif format == "hgpatch":
110 # parse values when importing the result of an hg export
110 # parse values when importing the result of an hg export
111 if line.startswith("# User "):
111 if line.startswith("# User "):
112 user = line[7:]
112 user = line[7:]
113 elif line.startswith("# Date "):
113 elif line.startswith("# Date "):
114 date = line[7:]
114 date = line[7:]
115 elif line.startswith("# Parent "):
115 elif line.startswith("# Parent "):
116 parent = line[9:].lstrip()
116 parent = line[9:].lstrip()
117 elif line.startswith("# Branch "):
117 elif line.startswith("# Branch "):
118 branch = line[9:]
118 branch = line[9:]
119 elif line.startswith("# Node ID "):
119 elif line.startswith("# Node ID "):
120 nodeid = line[10:]
120 nodeid = line[10:]
121 elif not line.startswith("# ") and line:
121 elif not line.startswith("# ") and line:
122 message.append(line)
122 message.append(line)
123 format = None
123 format = None
124 elif line == '# HG changeset patch':
124 elif line == '# HG changeset patch':
125 message = []
125 message = []
126 format = "hgpatch"
126 format = "hgpatch"
127 elif (format != "tagdone" and (line.startswith("Subject: ") or
127 elif (format != "tagdone" and (line.startswith("Subject: ") or
128 line.startswith("subject: "))):
128 line.startswith("subject: "))):
129 subject = line[9:]
129 subject = line[9:]
130 format = "tag"
130 format = "tag"
131 elif (format != "tagdone" and (line.startswith("From: ") or
131 elif (format != "tagdone" and (line.startswith("From: ") or
132 line.startswith("from: "))):
132 line.startswith("from: "))):
133 user = line[6:]
133 user = line[6:]
134 format = "tag"
134 format = "tag"
135 elif (format != "tagdone" and (line.startswith("Date: ") or
135 elif (format != "tagdone" and (line.startswith("Date: ") or
136 line.startswith("date: "))):
136 line.startswith("date: "))):
137 date = line[6:]
137 date = line[6:]
138 format = "tag"
138 format = "tag"
139 elif format == "tag" and line == "":
139 elif format == "tag" and line == "":
140 # when looking for tags (subject: from: etc) they
140 # when looking for tags (subject: from: etc) they
141 # end once you find a blank line in the source
141 # end once you find a blank line in the source
142 format = "tagdone"
142 format = "tagdone"
143 elif message or line:
143 elif message or line:
144 message.append(line)
144 message.append(line)
145 comments.append(line)
145 comments.append(line)
146
146
147 eatdiff(message)
147 eatdiff(message)
148 eatdiff(comments)
148 eatdiff(comments)
149 # Remember the exact starting line of the patch diffs before consuming
149 # Remember the exact starting line of the patch diffs before consuming
150 # empty lines, for external use by TortoiseHg and others
150 # empty lines, for external use by TortoiseHg and others
151 self.diffstartline = len(comments)
151 self.diffstartline = len(comments)
152 eatempty(message)
152 eatempty(message)
153 eatempty(comments)
153 eatempty(comments)
154
154
155 # make sure message isn't empty
155 # make sure message isn't empty
156 if format and format.startswith("tag") and subject:
156 if format and format.startswith("tag") and subject:
157 message.insert(0, "")
157 message.insert(0, "")
158 message.insert(0, subject)
158 message.insert(0, subject)
159
159
160 self.message = message
160 self.message = message
161 self.comments = comments
161 self.comments = comments
162 self.user = user
162 self.user = user
163 self.date = date
163 self.date = date
164 self.parent = parent
164 self.parent = parent
165 # nodeid and branch are for external use by TortoiseHg and others
165 # nodeid and branch are for external use by TortoiseHg and others
166 self.nodeid = nodeid
166 self.nodeid = nodeid
167 self.branch = branch
167 self.branch = branch
168 self.haspatch = diffstart > 1
168 self.haspatch = diffstart > 1
169 self.plainmode = plainmode
169 self.plainmode = plainmode
170
170
171 def setuser(self, user):
171 def setuser(self, user):
172 if not self.updateheader(['From: ', '# User '], user):
172 if not self.updateheader(['From: ', '# User '], user):
173 try:
173 try:
174 patchheaderat = self.comments.index('# HG changeset patch')
174 patchheaderat = self.comments.index('# HG changeset patch')
175 self.comments.insert(patchheaderat + 1, '# User ' + user)
175 self.comments.insert(patchheaderat + 1, '# User ' + user)
176 except ValueError:
176 except ValueError:
177 if self.plainmode or self._hasheader(['Date: ']):
177 if self.plainmode or self._hasheader(['Date: ']):
178 self.comments = ['From: ' + user] + self.comments
178 self.comments = ['From: ' + user] + self.comments
179 else:
179 else:
180 tmp = ['# HG changeset patch', '# User ' + user, '']
180 tmp = ['# HG changeset patch', '# User ' + user, '']
181 self.comments = tmp + self.comments
181 self.comments = tmp + self.comments
182 self.user = user
182 self.user = user
183
183
184 def setdate(self, date):
184 def setdate(self, date):
185 if not self.updateheader(['Date: ', '# Date '], date):
185 if not self.updateheader(['Date: ', '# Date '], date):
186 try:
186 try:
187 patchheaderat = self.comments.index('# HG changeset patch')
187 patchheaderat = self.comments.index('# HG changeset patch')
188 self.comments.insert(patchheaderat + 1, '# Date ' + date)
188 self.comments.insert(patchheaderat + 1, '# Date ' + date)
189 except ValueError:
189 except ValueError:
190 if self.plainmode or self._hasheader(['From: ']):
190 if self.plainmode or self._hasheader(['From: ']):
191 self.comments = ['Date: ' + date] + self.comments
191 self.comments = ['Date: ' + date] + self.comments
192 else:
192 else:
193 tmp = ['# HG changeset patch', '# Date ' + date, '']
193 tmp = ['# HG changeset patch', '# Date ' + date, '']
194 self.comments = tmp + self.comments
194 self.comments = tmp + self.comments
195 self.date = date
195 self.date = date
196
196
197 def setparent(self, parent):
197 def setparent(self, parent):
198 if not self.updateheader(['# Parent '], parent):
198 if not self.updateheader(['# Parent '], parent):
199 try:
199 try:
200 patchheaderat = self.comments.index('# HG changeset patch')
200 patchheaderat = self.comments.index('# HG changeset patch')
201 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
201 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
202 except ValueError:
202 except ValueError:
203 pass
203 pass
204 self.parent = parent
204 self.parent = parent
205
205
206 def setmessage(self, message):
206 def setmessage(self, message):
207 if self.comments:
207 if self.comments:
208 self._delmsg()
208 self._delmsg()
209 self.message = [message]
209 self.message = [message]
210 self.comments += self.message
210 self.comments += self.message
211
211
212 def updateheader(self, prefixes, new):
212 def updateheader(self, prefixes, new):
213 '''Update all references to a field in the patch header.
213 '''Update all references to a field in the patch header.
214 Return whether the field is present.'''
214 Return whether the field is present.'''
215 res = False
215 res = False
216 for prefix in prefixes:
216 for prefix in prefixes:
217 for i in xrange(len(self.comments)):
217 for i in xrange(len(self.comments)):
218 if self.comments[i].startswith(prefix):
218 if self.comments[i].startswith(prefix):
219 self.comments[i] = prefix + new
219 self.comments[i] = prefix + new
220 res = True
220 res = True
221 break
221 break
222 return res
222 return res
223
223
224 def _hasheader(self, prefixes):
224 def _hasheader(self, prefixes):
225 '''Check if a header starts with any of the given prefixes.'''
225 '''Check if a header starts with any of the given prefixes.'''
226 for prefix in prefixes:
226 for prefix in prefixes:
227 for comment in self.comments:
227 for comment in self.comments:
228 if comment.startswith(prefix):
228 if comment.startswith(prefix):
229 return True
229 return True
230 return False
230 return False
231
231
232 def __str__(self):
232 def __str__(self):
233 if not self.comments:
233 if not self.comments:
234 return ''
234 return ''
235 return '\n'.join(self.comments) + '\n\n'
235 return '\n'.join(self.comments) + '\n\n'
236
236
237 def _delmsg(self):
237 def _delmsg(self):
238 '''Remove existing message, keeping the rest of the comments fields.
238 '''Remove existing message, keeping the rest of the comments fields.
239 If comments contains 'subject: ', message will prepend
239 If comments contains 'subject: ', message will prepend
240 the field and a blank line.'''
240 the field and a blank line.'''
241 if self.message:
241 if self.message:
242 subj = 'subject: ' + self.message[0].lower()
242 subj = 'subject: ' + self.message[0].lower()
243 for i in xrange(len(self.comments)):
243 for i in xrange(len(self.comments)):
244 if subj == self.comments[i].lower():
244 if subj == self.comments[i].lower():
245 del self.comments[i]
245 del self.comments[i]
246 self.message = self.message[2:]
246 self.message = self.message[2:]
247 break
247 break
248 ci = 0
248 ci = 0
249 for mi in self.message:
249 for mi in self.message:
250 while mi != self.comments[ci]:
250 while mi != self.comments[ci]:
251 ci += 1
251 ci += 1
252 del self.comments[ci]
252 del self.comments[ci]
253
253
254 class queue(object):
254 class queue(object):
255 def __init__(self, ui, path, patchdir=None):
255 def __init__(self, ui, path, patchdir=None):
256 self.basepath = path
256 self.basepath = path
257 try:
257 try:
258 fh = open(os.path.join(path, 'patches.queue'))
258 fh = open(os.path.join(path, 'patches.queue'))
259 cur = fh.read().rstrip()
259 cur = fh.read().rstrip()
260 fh.close()
260 fh.close()
261 if not cur:
261 if not cur:
262 curpath = os.path.join(path, 'patches')
262 curpath = os.path.join(path, 'patches')
263 else:
263 else:
264 curpath = os.path.join(path, 'patches-' + cur)
264 curpath = os.path.join(path, 'patches-' + cur)
265 except IOError:
265 except IOError:
266 curpath = os.path.join(path, 'patches')
266 curpath = os.path.join(path, 'patches')
267 self.path = patchdir or curpath
267 self.path = patchdir or curpath
268 self.opener = scmutil.opener(self.path)
268 self.opener = scmutil.opener(self.path)
269 self.ui = ui
269 self.ui = ui
270 self.applieddirty = 0
270 self.applieddirty = 0
271 self.seriesdirty = 0
271 self.seriesdirty = 0
272 self.added = []
272 self.added = []
273 self.seriespath = "series"
273 self.seriespath = "series"
274 self.statuspath = "status"
274 self.statuspath = "status"
275 self.guardspath = "guards"
275 self.guardspath = "guards"
276 self.activeguards = None
276 self.activeguards = None
277 self.guardsdirty = False
277 self.guardsdirty = False
278 # Handle mq.git as a bool with extended values
278 # Handle mq.git as a bool with extended values
279 try:
279 try:
280 gitmode = ui.configbool('mq', 'git', None)
280 gitmode = ui.configbool('mq', 'git', None)
281 if gitmode is None:
281 if gitmode is None:
282 raise error.ConfigError()
282 raise error.ConfigError()
283 self.gitmode = gitmode and 'yes' or 'no'
283 self.gitmode = gitmode and 'yes' or 'no'
284 except error.ConfigError:
284 except error.ConfigError:
285 self.gitmode = ui.config('mq', 'git', 'auto').lower()
285 self.gitmode = ui.config('mq', 'git', 'auto').lower()
286 self.plainmode = ui.configbool('mq', 'plain', False)
286 self.plainmode = ui.configbool('mq', 'plain', False)
287
287
288 @util.propertycache
288 @util.propertycache
289 def applied(self):
289 def applied(self):
290 if os.path.exists(self.join(self.statuspath)):
290 if os.path.exists(self.join(self.statuspath)):
291 def parselines(lines):
291 def parselines(lines):
292 for l in lines:
292 for l in lines:
293 entry = l.split(':', 1)
293 entry = l.split(':', 1)
294 if len(entry) > 1:
294 if len(entry) > 1:
295 n, name = entry
295 n, name = entry
296 yield statusentry(bin(n), name)
296 yield statusentry(bin(n), name)
297 elif l.strip():
297 elif l.strip():
298 self.ui.warn(_('malformated mq status line: %s\n') % entry)
298 self.ui.warn(_('malformated mq status line: %s\n') % entry)
299 # else we ignore empty lines
299 # else we ignore empty lines
300 lines = self.opener.read(self.statuspath).splitlines()
300 lines = self.opener.read(self.statuspath).splitlines()
301 return list(parselines(lines))
301 return list(parselines(lines))
302 return []
302 return []
303
303
304 @util.propertycache
304 @util.propertycache
305 def fullseries(self):
305 def fullseries(self):
306 if os.path.exists(self.join(self.seriespath)):
306 if os.path.exists(self.join(self.seriespath)):
307 return self.opener.read(self.seriespath).splitlines()
307 return self.opener.read(self.seriespath).splitlines()
308 return []
308 return []
309
309
310 @util.propertycache
310 @util.propertycache
311 def series(self):
311 def series(self):
312 self.parseseries()
312 self.parseseries()
313 return self.series
313 return self.series
314
314
315 @util.propertycache
315 @util.propertycache
316 def seriesguards(self):
316 def seriesguards(self):
317 self.parseseries()
317 self.parseseries()
318 return self.seriesguards
318 return self.seriesguards
319
319
320 def invalidate(self):
320 def invalidate(self):
321 for a in 'applied fullseries series seriesguards'.split():
321 for a in 'applied fullseries series seriesguards'.split():
322 if a in self.__dict__:
322 if a in self.__dict__:
323 delattr(self, a)
323 delattr(self, a)
324 self.applieddirty = 0
324 self.applieddirty = 0
325 self.seriesdirty = 0
325 self.seriesdirty = 0
326 self.guardsdirty = False
326 self.guardsdirty = False
327 self.activeguards = None
327 self.activeguards = None
328
328
329 def diffopts(self, opts={}, patchfn=None):
329 def diffopts(self, opts={}, patchfn=None):
330 diffopts = patchmod.diffopts(self.ui, opts)
330 diffopts = patchmod.diffopts(self.ui, opts)
331 if self.gitmode == 'auto':
331 if self.gitmode == 'auto':
332 diffopts.upgrade = True
332 diffopts.upgrade = True
333 elif self.gitmode == 'keep':
333 elif self.gitmode == 'keep':
334 pass
334 pass
335 elif self.gitmode in ('yes', 'no'):
335 elif self.gitmode in ('yes', 'no'):
336 diffopts.git = self.gitmode == 'yes'
336 diffopts.git = self.gitmode == 'yes'
337 else:
337 else:
338 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
338 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
339 ' got %s') % self.gitmode)
339 ' got %s') % self.gitmode)
340 if patchfn:
340 if patchfn:
341 diffopts = self.patchopts(diffopts, patchfn)
341 diffopts = self.patchopts(diffopts, patchfn)
342 return diffopts
342 return diffopts
343
343
344 def patchopts(self, diffopts, *patches):
344 def patchopts(self, diffopts, *patches):
345 """Return a copy of input diff options with git set to true if
345 """Return a copy of input diff options with git set to true if
346 referenced patch is a git patch and should be preserved as such.
346 referenced patch is a git patch and should be preserved as such.
347 """
347 """
348 diffopts = diffopts.copy()
348 diffopts = diffopts.copy()
349 if not diffopts.git and self.gitmode == 'keep':
349 if not diffopts.git and self.gitmode == 'keep':
350 for patchfn in patches:
350 for patchfn in patches:
351 patchf = self.opener(patchfn, 'r')
351 patchf = self.opener(patchfn, 'r')
352 # if the patch was a git patch, refresh it as a git patch
352 # if the patch was a git patch, refresh it as a git patch
353 for line in patchf:
353 for line in patchf:
354 if line.startswith('diff --git'):
354 if line.startswith('diff --git'):
355 diffopts.git = True
355 diffopts.git = True
356 break
356 break
357 patchf.close()
357 patchf.close()
358 return diffopts
358 return diffopts
359
359
360 def join(self, *p):
360 def join(self, *p):
361 return os.path.join(self.path, *p)
361 return os.path.join(self.path, *p)
362
362
363 def findseries(self, patch):
363 def findseries(self, patch):
364 def matchpatch(l):
364 def matchpatch(l):
365 l = l.split('#', 1)[0]
365 l = l.split('#', 1)[0]
366 return l.strip() == patch
366 return l.strip() == patch
367 for index, l in enumerate(self.fullseries):
367 for index, l in enumerate(self.fullseries):
368 if matchpatch(l):
368 if matchpatch(l):
369 return index
369 return index
370 return None
370 return None
371
371
372 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
372 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
373
373
374 def parseseries(self):
374 def parseseries(self):
375 self.series = []
375 self.series = []
376 self.seriesguards = []
376 self.seriesguards = []
377 for l in self.fullseries:
377 for l in self.fullseries:
378 h = l.find('#')
378 h = l.find('#')
379 if h == -1:
379 if h == -1:
380 patch = l
380 patch = l
381 comment = ''
381 comment = ''
382 elif h == 0:
382 elif h == 0:
383 continue
383 continue
384 else:
384 else:
385 patch = l[:h]
385 patch = l[:h]
386 comment = l[h:]
386 comment = l[h:]
387 patch = patch.strip()
387 patch = patch.strip()
388 if patch:
388 if patch:
389 if patch in self.series:
389 if patch in self.series:
390 raise util.Abort(_('%s appears more than once in %s') %
390 raise util.Abort(_('%s appears more than once in %s') %
391 (patch, self.join(self.seriespath)))
391 (patch, self.join(self.seriespath)))
392 self.series.append(patch)
392 self.series.append(patch)
393 self.seriesguards.append(self.guard_re.findall(comment))
393 self.seriesguards.append(self.guard_re.findall(comment))
394
394
395 def checkguard(self, guard):
395 def checkguard(self, guard):
396 if not guard:
396 if not guard:
397 return _('guard cannot be an empty string')
397 return _('guard cannot be an empty string')
398 bad_chars = '# \t\r\n\f'
398 bad_chars = '# \t\r\n\f'
399 first = guard[0]
399 first = guard[0]
400 if first in '-+':
400 if first in '-+':
401 return (_('guard %r starts with invalid character: %r') %
401 return (_('guard %r starts with invalid character: %r') %
402 (guard, first))
402 (guard, first))
403 for c in bad_chars:
403 for c in bad_chars:
404 if c in guard:
404 if c in guard:
405 return _('invalid character in guard %r: %r') % (guard, c)
405 return _('invalid character in guard %r: %r') % (guard, c)
406
406
407 def setactive(self, guards):
407 def setactive(self, guards):
408 for guard in guards:
408 for guard in guards:
409 bad = self.checkguard(guard)
409 bad = self.checkguard(guard)
410 if bad:
410 if bad:
411 raise util.Abort(bad)
411 raise util.Abort(bad)
412 guards = sorted(set(guards))
412 guards = sorted(set(guards))
413 self.ui.debug('active guards: %s\n' % ' '.join(guards))
413 self.ui.debug('active guards: %s\n' % ' '.join(guards))
414 self.activeguards = guards
414 self.activeguards = guards
415 self.guardsdirty = True
415 self.guardsdirty = True
416
416
417 def active(self):
417 def active(self):
418 if self.activeguards is None:
418 if self.activeguards is None:
419 self.activeguards = []
419 self.activeguards = []
420 try:
420 try:
421 guards = self.opener.read(self.guardspath).split()
421 guards = self.opener.read(self.guardspath).split()
422 except IOError, err:
422 except IOError, err:
423 if err.errno != errno.ENOENT:
423 if err.errno != errno.ENOENT:
424 raise
424 raise
425 guards = []
425 guards = []
426 for i, guard in enumerate(guards):
426 for i, guard in enumerate(guards):
427 bad = self.checkguard(guard)
427 bad = self.checkguard(guard)
428 if bad:
428 if bad:
429 self.ui.warn('%s:%d: %s\n' %
429 self.ui.warn('%s:%d: %s\n' %
430 (self.join(self.guardspath), i + 1, bad))
430 (self.join(self.guardspath), i + 1, bad))
431 else:
431 else:
432 self.activeguards.append(guard)
432 self.activeguards.append(guard)
433 return self.activeguards
433 return self.activeguards
434
434
435 def setguards(self, idx, guards):
435 def setguards(self, idx, guards):
436 for g in guards:
436 for g in guards:
437 if len(g) < 2:
437 if len(g) < 2:
438 raise util.Abort(_('guard %r too short') % g)
438 raise util.Abort(_('guard %r too short') % g)
439 if g[0] not in '-+':
439 if g[0] not in '-+':
440 raise util.Abort(_('guard %r starts with invalid char') % g)
440 raise util.Abort(_('guard %r starts with invalid char') % g)
441 bad = self.checkguard(g[1:])
441 bad = self.checkguard(g[1:])
442 if bad:
442 if bad:
443 raise util.Abort(bad)
443 raise util.Abort(bad)
444 drop = self.guard_re.sub('', self.fullseries[idx])
444 drop = self.guard_re.sub('', self.fullseries[idx])
445 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
445 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
446 self.parseseries()
446 self.parseseries()
447 self.seriesdirty = True
447 self.seriesdirty = True
448
448
449 def pushable(self, idx):
449 def pushable(self, idx):
450 if isinstance(idx, str):
450 if isinstance(idx, str):
451 idx = self.series.index(idx)
451 idx = self.series.index(idx)
452 patchguards = self.seriesguards[idx]
452 patchguards = self.seriesguards[idx]
453 if not patchguards:
453 if not patchguards:
454 return True, None
454 return True, None
455 guards = self.active()
455 guards = self.active()
456 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
456 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
457 if exactneg:
457 if exactneg:
458 return False, repr(exactneg[0])
458 return False, repr(exactneg[0])
459 pos = [g for g in patchguards if g[0] == '+']
459 pos = [g for g in patchguards if g[0] == '+']
460 exactpos = [g for g in pos if g[1:] in guards]
460 exactpos = [g for g in pos if g[1:] in guards]
461 if pos:
461 if pos:
462 if exactpos:
462 if exactpos:
463 return True, repr(exactpos[0])
463 return True, repr(exactpos[0])
464 return False, ' '.join(map(repr, pos))
464 return False, ' '.join(map(repr, pos))
465 return True, ''
465 return True, ''
466
466
467 def explainpushable(self, idx, all_patches=False):
467 def explainpushable(self, idx, all_patches=False):
468 write = all_patches and self.ui.write or self.ui.warn
468 write = all_patches and self.ui.write or self.ui.warn
469 if all_patches or self.ui.verbose:
469 if all_patches or self.ui.verbose:
470 if isinstance(idx, str):
470 if isinstance(idx, str):
471 idx = self.series.index(idx)
471 idx = self.series.index(idx)
472 pushable, why = self.pushable(idx)
472 pushable, why = self.pushable(idx)
473 if all_patches and pushable:
473 if all_patches and pushable:
474 if why is None:
474 if why is None:
475 write(_('allowing %s - no guards in effect\n') %
475 write(_('allowing %s - no guards in effect\n') %
476 self.series[idx])
476 self.series[idx])
477 else:
477 else:
478 if not why:
478 if not why:
479 write(_('allowing %s - no matching negative guards\n') %
479 write(_('allowing %s - no matching negative guards\n') %
480 self.series[idx])
480 self.series[idx])
481 else:
481 else:
482 write(_('allowing %s - guarded by %s\n') %
482 write(_('allowing %s - guarded by %s\n') %
483 (self.series[idx], why))
483 (self.series[idx], why))
484 if not pushable:
484 if not pushable:
485 if why:
485 if why:
486 write(_('skipping %s - guarded by %s\n') %
486 write(_('skipping %s - guarded by %s\n') %
487 (self.series[idx], why))
487 (self.series[idx], why))
488 else:
488 else:
489 write(_('skipping %s - no matching guards\n') %
489 write(_('skipping %s - no matching guards\n') %
490 self.series[idx])
490 self.series[idx])
491
491
492 def savedirty(self):
492 def savedirty(self):
493 def writelist(items, path):
493 def writelist(items, path):
494 fp = self.opener(path, 'w')
494 fp = self.opener(path, 'w')
495 for i in items:
495 for i in items:
496 fp.write("%s\n" % i)
496 fp.write("%s\n" % i)
497 fp.close()
497 fp.close()
498 if self.applieddirty:
498 if self.applieddirty:
499 writelist(map(str, self.applied), self.statuspath)
499 writelist(map(str, self.applied), self.statuspath)
500 if self.seriesdirty:
500 if self.seriesdirty:
501 writelist(self.fullseries, self.seriespath)
501 writelist(self.fullseries, self.seriespath)
502 if self.guardsdirty:
502 if self.guardsdirty:
503 writelist(self.activeguards, self.guardspath)
503 writelist(self.activeguards, self.guardspath)
504 if self.added:
504 if self.added:
505 qrepo = self.qrepo()
505 qrepo = self.qrepo()
506 if qrepo:
506 if qrepo:
507 qrepo[None].add(f for f in self.added if f not in qrepo[None])
507 qrepo[None].add(f for f in self.added if f not in qrepo[None])
508 self.added = []
508 self.added = []
509
509
510 def removeundo(self, repo):
510 def removeundo(self, repo):
511 undo = repo.sjoin('undo')
511 undo = repo.sjoin('undo')
512 if not os.path.exists(undo):
512 if not os.path.exists(undo):
513 return
513 return
514 try:
514 try:
515 os.unlink(undo)
515 os.unlink(undo)
516 except OSError, inst:
516 except OSError, inst:
517 self.ui.warn(_('error removing undo: %s\n') % str(inst))
517 self.ui.warn(_('error removing undo: %s\n') % str(inst))
518
518
519 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
519 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
520 fp=None, changes=None, opts={}):
520 fp=None, changes=None, opts={}):
521 stat = opts.get('stat')
521 stat = opts.get('stat')
522 m = scmutil.match(repo, files, opts)
522 m = scmutil.match(repo[node1], files, opts)
523 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
523 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
524 changes, stat, fp)
524 changes, stat, fp)
525
525
526 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
526 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
527 # first try just applying the patch
527 # first try just applying the patch
528 (err, n) = self.apply(repo, [patch], update_status=False,
528 (err, n) = self.apply(repo, [patch], update_status=False,
529 strict=True, merge=rev)
529 strict=True, merge=rev)
530
530
531 if err == 0:
531 if err == 0:
532 return (err, n)
532 return (err, n)
533
533
534 if n is None:
534 if n is None:
535 raise util.Abort(_("apply failed for patch %s") % patch)
535 raise util.Abort(_("apply failed for patch %s") % patch)
536
536
537 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
537 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
538
538
539 # apply failed, strip away that rev and merge.
539 # apply failed, strip away that rev and merge.
540 hg.clean(repo, head)
540 hg.clean(repo, head)
541 self.strip(repo, [n], update=False, backup='strip')
541 self.strip(repo, [n], update=False, backup='strip')
542
542
543 ctx = repo[rev]
543 ctx = repo[rev]
544 ret = hg.merge(repo, rev)
544 ret = hg.merge(repo, rev)
545 if ret:
545 if ret:
546 raise util.Abort(_("update returned %d") % ret)
546 raise util.Abort(_("update returned %d") % ret)
547 n = repo.commit(ctx.description(), ctx.user(), force=True)
547 n = repo.commit(ctx.description(), ctx.user(), force=True)
548 if n is None:
548 if n is None:
549 raise util.Abort(_("repo commit failed"))
549 raise util.Abort(_("repo commit failed"))
550 try:
550 try:
551 ph = patchheader(mergeq.join(patch), self.plainmode)
551 ph = patchheader(mergeq.join(patch), self.plainmode)
552 except:
552 except:
553 raise util.Abort(_("unable to read %s") % patch)
553 raise util.Abort(_("unable to read %s") % patch)
554
554
555 diffopts = self.patchopts(diffopts, patch)
555 diffopts = self.patchopts(diffopts, patch)
556 patchf = self.opener(patch, "w")
556 patchf = self.opener(patch, "w")
557 comments = str(ph)
557 comments = str(ph)
558 if comments:
558 if comments:
559 patchf.write(comments)
559 patchf.write(comments)
560 self.printdiff(repo, diffopts, head, n, fp=patchf)
560 self.printdiff(repo, diffopts, head, n, fp=patchf)
561 patchf.close()
561 patchf.close()
562 self.removeundo(repo)
562 self.removeundo(repo)
563 return (0, n)
563 return (0, n)
564
564
565 def qparents(self, repo, rev=None):
565 def qparents(self, repo, rev=None):
566 if rev is None:
566 if rev is None:
567 (p1, p2) = repo.dirstate.parents()
567 (p1, p2) = repo.dirstate.parents()
568 if p2 == nullid:
568 if p2 == nullid:
569 return p1
569 return p1
570 if not self.applied:
570 if not self.applied:
571 return None
571 return None
572 return self.applied[-1].node
572 return self.applied[-1].node
573 p1, p2 = repo.changelog.parents(rev)
573 p1, p2 = repo.changelog.parents(rev)
574 if p2 != nullid and p2 in [x.node for x in self.applied]:
574 if p2 != nullid and p2 in [x.node for x in self.applied]:
575 return p2
575 return p2
576 return p1
576 return p1
577
577
578 def mergepatch(self, repo, mergeq, series, diffopts):
578 def mergepatch(self, repo, mergeq, series, diffopts):
579 if not self.applied:
579 if not self.applied:
580 # each of the patches merged in will have two parents. This
580 # each of the patches merged in will have two parents. This
581 # can confuse the qrefresh, qdiff, and strip code because it
581 # can confuse the qrefresh, qdiff, and strip code because it
582 # needs to know which parent is actually in the patch queue.
582 # needs to know which parent is actually in the patch queue.
583 # so, we insert a merge marker with only one parent. This way
583 # so, we insert a merge marker with only one parent. This way
584 # the first patch in the queue is never a merge patch
584 # the first patch in the queue is never a merge patch
585 #
585 #
586 pname = ".hg.patches.merge.marker"
586 pname = ".hg.patches.merge.marker"
587 n = repo.commit('[mq]: merge marker', force=True)
587 n = repo.commit('[mq]: merge marker', force=True)
588 self.removeundo(repo)
588 self.removeundo(repo)
589 self.applied.append(statusentry(n, pname))
589 self.applied.append(statusentry(n, pname))
590 self.applieddirty = 1
590 self.applieddirty = 1
591
591
592 head = self.qparents(repo)
592 head = self.qparents(repo)
593
593
594 for patch in series:
594 for patch in series:
595 patch = mergeq.lookup(patch, strict=True)
595 patch = mergeq.lookup(patch, strict=True)
596 if not patch:
596 if not patch:
597 self.ui.warn(_("patch %s does not exist\n") % patch)
597 self.ui.warn(_("patch %s does not exist\n") % patch)
598 return (1, None)
598 return (1, None)
599 pushable, reason = self.pushable(patch)
599 pushable, reason = self.pushable(patch)
600 if not pushable:
600 if not pushable:
601 self.explainpushable(patch, all_patches=True)
601 self.explainpushable(patch, all_patches=True)
602 continue
602 continue
603 info = mergeq.isapplied(patch)
603 info = mergeq.isapplied(patch)
604 if not info:
604 if not info:
605 self.ui.warn(_("patch %s is not applied\n") % patch)
605 self.ui.warn(_("patch %s is not applied\n") % patch)
606 return (1, None)
606 return (1, None)
607 rev = info[1]
607 rev = info[1]
608 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
608 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
609 if head:
609 if head:
610 self.applied.append(statusentry(head, patch))
610 self.applied.append(statusentry(head, patch))
611 self.applieddirty = 1
611 self.applieddirty = 1
612 if err:
612 if err:
613 return (err, head)
613 return (err, head)
614 self.savedirty()
614 self.savedirty()
615 return (0, head)
615 return (0, head)
616
616
617 def patch(self, repo, patchfile):
617 def patch(self, repo, patchfile):
618 '''Apply patchfile to the working directory.
618 '''Apply patchfile to the working directory.
619 patchfile: name of patch file'''
619 patchfile: name of patch file'''
620 files = set()
620 files = set()
621 try:
621 try:
622 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
622 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
623 files=files, eolmode=None)
623 files=files, eolmode=None)
624 return (True, list(files), fuzz)
624 return (True, list(files), fuzz)
625 except Exception, inst:
625 except Exception, inst:
626 self.ui.note(str(inst) + '\n')
626 self.ui.note(str(inst) + '\n')
627 if not self.ui.verbose:
627 if not self.ui.verbose:
628 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
628 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
629 return (False, list(files), False)
629 return (False, list(files), False)
630
630
631 def apply(self, repo, series, list=False, update_status=True,
631 def apply(self, repo, series, list=False, update_status=True,
632 strict=False, patchdir=None, merge=None, all_files=None):
632 strict=False, patchdir=None, merge=None, all_files=None):
633 wlock = lock = tr = None
633 wlock = lock = tr = None
634 try:
634 try:
635 wlock = repo.wlock()
635 wlock = repo.wlock()
636 lock = repo.lock()
636 lock = repo.lock()
637 tr = repo.transaction("qpush")
637 tr = repo.transaction("qpush")
638 try:
638 try:
639 ret = self._apply(repo, series, list, update_status,
639 ret = self._apply(repo, series, list, update_status,
640 strict, patchdir, merge, all_files=all_files)
640 strict, patchdir, merge, all_files=all_files)
641 tr.close()
641 tr.close()
642 self.savedirty()
642 self.savedirty()
643 return ret
643 return ret
644 except:
644 except:
645 try:
645 try:
646 tr.abort()
646 tr.abort()
647 finally:
647 finally:
648 repo.invalidate()
648 repo.invalidate()
649 repo.dirstate.invalidate()
649 repo.dirstate.invalidate()
650 raise
650 raise
651 finally:
651 finally:
652 release(tr, lock, wlock)
652 release(tr, lock, wlock)
653 self.removeundo(repo)
653 self.removeundo(repo)
654
654
655 def _apply(self, repo, series, list=False, update_status=True,
655 def _apply(self, repo, series, list=False, update_status=True,
656 strict=False, patchdir=None, merge=None, all_files=None):
656 strict=False, patchdir=None, merge=None, all_files=None):
657 '''returns (error, hash)
657 '''returns (error, hash)
658 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
658 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
659 # TODO unify with commands.py
659 # TODO unify with commands.py
660 if not patchdir:
660 if not patchdir:
661 patchdir = self.path
661 patchdir = self.path
662 err = 0
662 err = 0
663 n = None
663 n = None
664 for patchname in series:
664 for patchname in series:
665 pushable, reason = self.pushable(patchname)
665 pushable, reason = self.pushable(patchname)
666 if not pushable:
666 if not pushable:
667 self.explainpushable(patchname, all_patches=True)
667 self.explainpushable(patchname, all_patches=True)
668 continue
668 continue
669 self.ui.status(_("applying %s\n") % patchname)
669 self.ui.status(_("applying %s\n") % patchname)
670 pf = os.path.join(patchdir, patchname)
670 pf = os.path.join(patchdir, patchname)
671
671
672 try:
672 try:
673 ph = patchheader(self.join(patchname), self.plainmode)
673 ph = patchheader(self.join(patchname), self.plainmode)
674 except IOError:
674 except IOError:
675 self.ui.warn(_("unable to read %s\n") % patchname)
675 self.ui.warn(_("unable to read %s\n") % patchname)
676 err = 1
676 err = 1
677 break
677 break
678
678
679 message = ph.message
679 message = ph.message
680 if not message:
680 if not message:
681 # The commit message should not be translated
681 # The commit message should not be translated
682 message = "imported patch %s\n" % patchname
682 message = "imported patch %s\n" % patchname
683 else:
683 else:
684 if list:
684 if list:
685 # The commit message should not be translated
685 # The commit message should not be translated
686 message.append("\nimported patch %s" % patchname)
686 message.append("\nimported patch %s" % patchname)
687 message = '\n'.join(message)
687 message = '\n'.join(message)
688
688
689 if ph.haspatch:
689 if ph.haspatch:
690 (patcherr, files, fuzz) = self.patch(repo, pf)
690 (patcherr, files, fuzz) = self.patch(repo, pf)
691 if all_files is not None:
691 if all_files is not None:
692 all_files.update(files)
692 all_files.update(files)
693 patcherr = not patcherr
693 patcherr = not patcherr
694 else:
694 else:
695 self.ui.warn(_("patch %s is empty\n") % patchname)
695 self.ui.warn(_("patch %s is empty\n") % patchname)
696 patcherr, files, fuzz = 0, [], 0
696 patcherr, files, fuzz = 0, [], 0
697
697
698 if merge and files:
698 if merge and files:
699 # Mark as removed/merged and update dirstate parent info
699 # Mark as removed/merged and update dirstate parent info
700 removed = []
700 removed = []
701 merged = []
701 merged = []
702 for f in files:
702 for f in files:
703 if os.path.lexists(repo.wjoin(f)):
703 if os.path.lexists(repo.wjoin(f)):
704 merged.append(f)
704 merged.append(f)
705 else:
705 else:
706 removed.append(f)
706 removed.append(f)
707 for f in removed:
707 for f in removed:
708 repo.dirstate.remove(f)
708 repo.dirstate.remove(f)
709 for f in merged:
709 for f in merged:
710 repo.dirstate.merge(f)
710 repo.dirstate.merge(f)
711 p1, p2 = repo.dirstate.parents()
711 p1, p2 = repo.dirstate.parents()
712 repo.dirstate.setparents(p1, merge)
712 repo.dirstate.setparents(p1, merge)
713
713
714 match = scmutil.matchfiles(repo, files or [])
714 match = scmutil.matchfiles(repo, files or [])
715 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
715 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
716
716
717 if n is None:
717 if n is None:
718 raise util.Abort(_("repository commit failed"))
718 raise util.Abort(_("repository commit failed"))
719
719
720 if update_status:
720 if update_status:
721 self.applied.append(statusentry(n, patchname))
721 self.applied.append(statusentry(n, patchname))
722
722
723 if patcherr:
723 if patcherr:
724 self.ui.warn(_("patch failed, rejects left in working dir\n"))
724 self.ui.warn(_("patch failed, rejects left in working dir\n"))
725 err = 2
725 err = 2
726 break
726 break
727
727
728 if fuzz and strict:
728 if fuzz and strict:
729 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
729 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
730 err = 3
730 err = 3
731 break
731 break
732 return (err, n)
732 return (err, n)
733
733
734 def _cleanup(self, patches, numrevs, keep=False):
734 def _cleanup(self, patches, numrevs, keep=False):
735 if not keep:
735 if not keep:
736 r = self.qrepo()
736 r = self.qrepo()
737 if r:
737 if r:
738 r[None].forget(patches)
738 r[None].forget(patches)
739 for p in patches:
739 for p in patches:
740 os.unlink(self.join(p))
740 os.unlink(self.join(p))
741
741
742 if numrevs:
742 if numrevs:
743 qfinished = self.applied[:numrevs]
743 qfinished = self.applied[:numrevs]
744 del self.applied[:numrevs]
744 del self.applied[:numrevs]
745 self.applieddirty = 1
745 self.applieddirty = 1
746
746
747 unknown = []
747 unknown = []
748
748
749 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
749 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
750 reverse=True):
750 reverse=True):
751 if i is not None:
751 if i is not None:
752 del self.fullseries[i]
752 del self.fullseries[i]
753 else:
753 else:
754 unknown.append(p)
754 unknown.append(p)
755
755
756 if unknown:
756 if unknown:
757 if numrevs:
757 if numrevs:
758 rev = dict((entry.name, entry.node) for entry in qfinished)
758 rev = dict((entry.name, entry.node) for entry in qfinished)
759 for p in unknown:
759 for p in unknown:
760 msg = _('revision %s refers to unknown patches: %s\n')
760 msg = _('revision %s refers to unknown patches: %s\n')
761 self.ui.warn(msg % (short(rev[p]), p))
761 self.ui.warn(msg % (short(rev[p]), p))
762 else:
762 else:
763 msg = _('unknown patches: %s\n')
763 msg = _('unknown patches: %s\n')
764 raise util.Abort(''.join(msg % p for p in unknown))
764 raise util.Abort(''.join(msg % p for p in unknown))
765
765
766 self.parseseries()
766 self.parseseries()
767 self.seriesdirty = 1
767 self.seriesdirty = 1
768
768
769 def _revpatches(self, repo, revs):
769 def _revpatches(self, repo, revs):
770 firstrev = repo[self.applied[0].node].rev()
770 firstrev = repo[self.applied[0].node].rev()
771 patches = []
771 patches = []
772 for i, rev in enumerate(revs):
772 for i, rev in enumerate(revs):
773
773
774 if rev < firstrev:
774 if rev < firstrev:
775 raise util.Abort(_('revision %d is not managed') % rev)
775 raise util.Abort(_('revision %d is not managed') % rev)
776
776
777 ctx = repo[rev]
777 ctx = repo[rev]
778 base = self.applied[i].node
778 base = self.applied[i].node
779 if ctx.node() != base:
779 if ctx.node() != base:
780 msg = _('cannot delete revision %d above applied patches')
780 msg = _('cannot delete revision %d above applied patches')
781 raise util.Abort(msg % rev)
781 raise util.Abort(msg % rev)
782
782
783 patch = self.applied[i].name
783 patch = self.applied[i].name
784 for fmt in ('[mq]: %s', 'imported patch %s'):
784 for fmt in ('[mq]: %s', 'imported patch %s'):
785 if ctx.description() == fmt % patch:
785 if ctx.description() == fmt % patch:
786 msg = _('patch %s finalized without changeset message\n')
786 msg = _('patch %s finalized without changeset message\n')
787 repo.ui.status(msg % patch)
787 repo.ui.status(msg % patch)
788 break
788 break
789
789
790 patches.append(patch)
790 patches.append(patch)
791 return patches
791 return patches
792
792
793 def finish(self, repo, revs):
793 def finish(self, repo, revs):
794 patches = self._revpatches(repo, sorted(revs))
794 patches = self._revpatches(repo, sorted(revs))
795 self._cleanup(patches, len(patches))
795 self._cleanup(patches, len(patches))
796
796
797 def delete(self, repo, patches, opts):
797 def delete(self, repo, patches, opts):
798 if not patches and not opts.get('rev'):
798 if not patches and not opts.get('rev'):
799 raise util.Abort(_('qdelete requires at least one revision or '
799 raise util.Abort(_('qdelete requires at least one revision or '
800 'patch name'))
800 'patch name'))
801
801
802 realpatches = []
802 realpatches = []
803 for patch in patches:
803 for patch in patches:
804 patch = self.lookup(patch, strict=True)
804 patch = self.lookup(patch, strict=True)
805 info = self.isapplied(patch)
805 info = self.isapplied(patch)
806 if info:
806 if info:
807 raise util.Abort(_("cannot delete applied patch %s") % patch)
807 raise util.Abort(_("cannot delete applied patch %s") % patch)
808 if patch not in self.series:
808 if patch not in self.series:
809 raise util.Abort(_("patch %s not in series file") % patch)
809 raise util.Abort(_("patch %s not in series file") % patch)
810 if patch not in realpatches:
810 if patch not in realpatches:
811 realpatches.append(patch)
811 realpatches.append(patch)
812
812
813 numrevs = 0
813 numrevs = 0
814 if opts.get('rev'):
814 if opts.get('rev'):
815 if not self.applied:
815 if not self.applied:
816 raise util.Abort(_('no patches applied'))
816 raise util.Abort(_('no patches applied'))
817 revs = scmutil.revrange(repo, opts.get('rev'))
817 revs = scmutil.revrange(repo, opts.get('rev'))
818 if len(revs) > 1 and revs[0] > revs[1]:
818 if len(revs) > 1 and revs[0] > revs[1]:
819 revs.reverse()
819 revs.reverse()
820 revpatches = self._revpatches(repo, revs)
820 revpatches = self._revpatches(repo, revs)
821 realpatches += revpatches
821 realpatches += revpatches
822 numrevs = len(revpatches)
822 numrevs = len(revpatches)
823
823
824 self._cleanup(realpatches, numrevs, opts.get('keep'))
824 self._cleanup(realpatches, numrevs, opts.get('keep'))
825
825
826 def checktoppatch(self, repo):
826 def checktoppatch(self, repo):
827 if self.applied:
827 if self.applied:
828 top = self.applied[-1].node
828 top = self.applied[-1].node
829 patch = self.applied[-1].name
829 patch = self.applied[-1].name
830 pp = repo.dirstate.parents()
830 pp = repo.dirstate.parents()
831 if top not in pp:
831 if top not in pp:
832 raise util.Abort(_("working directory revision is not qtip"))
832 raise util.Abort(_("working directory revision is not qtip"))
833 return top, patch
833 return top, patch
834 return None, None
834 return None, None
835
835
836 def checksubstate(self, repo):
836 def checksubstate(self, repo):
837 '''return list of subrepos at a different revision than substate.
837 '''return list of subrepos at a different revision than substate.
838 Abort if any subrepos have uncommitted changes.'''
838 Abort if any subrepos have uncommitted changes.'''
839 inclsubs = []
839 inclsubs = []
840 wctx = repo[None]
840 wctx = repo[None]
841 for s in wctx.substate:
841 for s in wctx.substate:
842 if wctx.sub(s).dirty(True):
842 if wctx.sub(s).dirty(True):
843 raise util.Abort(
843 raise util.Abort(
844 _("uncommitted changes in subrepository %s") % s)
844 _("uncommitted changes in subrepository %s") % s)
845 elif wctx.sub(s).dirty():
845 elif wctx.sub(s).dirty():
846 inclsubs.append(s)
846 inclsubs.append(s)
847 return inclsubs
847 return inclsubs
848
848
849 def localchangesfound(self, refresh=True):
849 def localchangesfound(self, refresh=True):
850 if refresh:
850 if refresh:
851 raise util.Abort(_("local changes found, refresh first"))
851 raise util.Abort(_("local changes found, refresh first"))
852 else:
852 else:
853 raise util.Abort(_("local changes found"))
853 raise util.Abort(_("local changes found"))
854
854
855 def checklocalchanges(self, repo, force=False, refresh=True):
855 def checklocalchanges(self, repo, force=False, refresh=True):
856 m, a, r, d = repo.status()[:4]
856 m, a, r, d = repo.status()[:4]
857 if (m or a or r or d) and not force:
857 if (m or a or r or d) and not force:
858 self.localchangesfound(refresh)
858 self.localchangesfound(refresh)
859 return m, a, r, d
859 return m, a, r, d
860
860
861 _reserved = ('series', 'status', 'guards', '.', '..')
861 _reserved = ('series', 'status', 'guards', '.', '..')
862 def checkreservedname(self, name):
862 def checkreservedname(self, name):
863 if name in self._reserved:
863 if name in self._reserved:
864 raise util.Abort(_('"%s" cannot be used as the name of a patch')
864 raise util.Abort(_('"%s" cannot be used as the name of a patch')
865 % name)
865 % name)
866 for prefix in ('.hg', '.mq'):
866 for prefix in ('.hg', '.mq'):
867 if name.startswith(prefix):
867 if name.startswith(prefix):
868 raise util.Abort(_('patch name cannot begin with "%s"')
868 raise util.Abort(_('patch name cannot begin with "%s"')
869 % prefix)
869 % prefix)
870 for c in ('#', ':'):
870 for c in ('#', ':'):
871 if c in name:
871 if c in name:
872 raise util.Abort(_('"%s" cannot be used in the name of a patch')
872 raise util.Abort(_('"%s" cannot be used in the name of a patch')
873 % c)
873 % c)
874
874
875 def checkpatchname(self, name, force=False):
875 def checkpatchname(self, name, force=False):
876 self.checkreservedname(name)
876 self.checkreservedname(name)
877 if not force and os.path.exists(self.join(name)):
877 if not force and os.path.exists(self.join(name)):
878 if os.path.isdir(self.join(name)):
878 if os.path.isdir(self.join(name)):
879 raise util.Abort(_('"%s" already exists as a directory')
879 raise util.Abort(_('"%s" already exists as a directory')
880 % name)
880 % name)
881 else:
881 else:
882 raise util.Abort(_('patch "%s" already exists') % name)
882 raise util.Abort(_('patch "%s" already exists') % name)
883
883
884 def new(self, repo, patchfn, *pats, **opts):
884 def new(self, repo, patchfn, *pats, **opts):
885 """options:
885 """options:
886 msg: a string or a no-argument function returning a string
886 msg: a string or a no-argument function returning a string
887 """
887 """
888 msg = opts.get('msg')
888 msg = opts.get('msg')
889 user = opts.get('user')
889 user = opts.get('user')
890 date = opts.get('date')
890 date = opts.get('date')
891 if date:
891 if date:
892 date = util.parsedate(date)
892 date = util.parsedate(date)
893 diffopts = self.diffopts({'git': opts.get('git')})
893 diffopts = self.diffopts({'git': opts.get('git')})
894 if opts.get('checkname', True):
894 if opts.get('checkname', True):
895 self.checkpatchname(patchfn)
895 self.checkpatchname(patchfn)
896 inclsubs = self.checksubstate(repo)
896 inclsubs = self.checksubstate(repo)
897 if inclsubs:
897 if inclsubs:
898 inclsubs.append('.hgsubstate')
898 inclsubs.append('.hgsubstate')
899 if opts.get('include') or opts.get('exclude') or pats:
899 if opts.get('include') or opts.get('exclude') or pats:
900 if inclsubs:
900 if inclsubs:
901 pats = list(pats or []) + inclsubs
901 pats = list(pats or []) + inclsubs
902 match = scmutil.match(repo, pats, opts)
902 match = scmutil.match(repo[None], pats, opts)
903 # detect missing files in pats
903 # detect missing files in pats
904 def badfn(f, msg):
904 def badfn(f, msg):
905 if f != '.hgsubstate': # .hgsubstate is auto-created
905 if f != '.hgsubstate': # .hgsubstate is auto-created
906 raise util.Abort('%s: %s' % (f, msg))
906 raise util.Abort('%s: %s' % (f, msg))
907 match.bad = badfn
907 match.bad = badfn
908 m, a, r, d = repo.status(match=match)[:4]
908 m, a, r, d = repo.status(match=match)[:4]
909 else:
909 else:
910 m, a, r, d = self.checklocalchanges(repo, force=True)
910 m, a, r, d = self.checklocalchanges(repo, force=True)
911 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
911 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
912 if len(repo[None].parents()) > 1:
912 if len(repo[None].parents()) > 1:
913 raise util.Abort(_('cannot manage merge changesets'))
913 raise util.Abort(_('cannot manage merge changesets'))
914 commitfiles = m + a + r
914 commitfiles = m + a + r
915 self.checktoppatch(repo)
915 self.checktoppatch(repo)
916 insert = self.fullseriesend()
916 insert = self.fullseriesend()
917 wlock = repo.wlock()
917 wlock = repo.wlock()
918 try:
918 try:
919 try:
919 try:
920 # if patch file write fails, abort early
920 # if patch file write fails, abort early
921 p = self.opener(patchfn, "w")
921 p = self.opener(patchfn, "w")
922 except IOError, e:
922 except IOError, e:
923 raise util.Abort(_('cannot write patch "%s": %s')
923 raise util.Abort(_('cannot write patch "%s": %s')
924 % (patchfn, e.strerror))
924 % (patchfn, e.strerror))
925 try:
925 try:
926 if self.plainmode:
926 if self.plainmode:
927 if user:
927 if user:
928 p.write("From: " + user + "\n")
928 p.write("From: " + user + "\n")
929 if not date:
929 if not date:
930 p.write("\n")
930 p.write("\n")
931 if date:
931 if date:
932 p.write("Date: %d %d\n\n" % date)
932 p.write("Date: %d %d\n\n" % date)
933 else:
933 else:
934 p.write("# HG changeset patch\n")
934 p.write("# HG changeset patch\n")
935 p.write("# Parent "
935 p.write("# Parent "
936 + hex(repo[None].p1().node()) + "\n")
936 + hex(repo[None].p1().node()) + "\n")
937 if user:
937 if user:
938 p.write("# User " + user + "\n")
938 p.write("# User " + user + "\n")
939 if date:
939 if date:
940 p.write("# Date %s %s\n\n" % date)
940 p.write("# Date %s %s\n\n" % date)
941 if hasattr(msg, '__call__'):
941 if hasattr(msg, '__call__'):
942 msg = msg()
942 msg = msg()
943 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
943 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
944 n = repo.commit(commitmsg, user, date, match=match, force=True)
944 n = repo.commit(commitmsg, user, date, match=match, force=True)
945 if n is None:
945 if n is None:
946 raise util.Abort(_("repo commit failed"))
946 raise util.Abort(_("repo commit failed"))
947 try:
947 try:
948 self.fullseries[insert:insert] = [patchfn]
948 self.fullseries[insert:insert] = [patchfn]
949 self.applied.append(statusentry(n, patchfn))
949 self.applied.append(statusentry(n, patchfn))
950 self.parseseries()
950 self.parseseries()
951 self.seriesdirty = 1
951 self.seriesdirty = 1
952 self.applieddirty = 1
952 self.applieddirty = 1
953 if msg:
953 if msg:
954 msg = msg + "\n\n"
954 msg = msg + "\n\n"
955 p.write(msg)
955 p.write(msg)
956 if commitfiles:
956 if commitfiles:
957 parent = self.qparents(repo, n)
957 parent = self.qparents(repo, n)
958 chunks = patchmod.diff(repo, node1=parent, node2=n,
958 chunks = patchmod.diff(repo, node1=parent, node2=n,
959 match=match, opts=diffopts)
959 match=match, opts=diffopts)
960 for chunk in chunks:
960 for chunk in chunks:
961 p.write(chunk)
961 p.write(chunk)
962 p.close()
962 p.close()
963 wlock.release()
963 wlock.release()
964 wlock = None
964 wlock = None
965 r = self.qrepo()
965 r = self.qrepo()
966 if r:
966 if r:
967 r[None].add([patchfn])
967 r[None].add([patchfn])
968 except:
968 except:
969 repo.rollback()
969 repo.rollback()
970 raise
970 raise
971 except Exception:
971 except Exception:
972 patchpath = self.join(patchfn)
972 patchpath = self.join(patchfn)
973 try:
973 try:
974 os.unlink(patchpath)
974 os.unlink(patchpath)
975 except:
975 except:
976 self.ui.warn(_('error unlinking %s\n') % patchpath)
976 self.ui.warn(_('error unlinking %s\n') % patchpath)
977 raise
977 raise
978 self.removeundo(repo)
978 self.removeundo(repo)
979 finally:
979 finally:
980 release(wlock)
980 release(wlock)
981
981
982 def strip(self, repo, revs, update=True, backup="all", force=None):
982 def strip(self, repo, revs, update=True, backup="all", force=None):
983 wlock = lock = None
983 wlock = lock = None
984 try:
984 try:
985 wlock = repo.wlock()
985 wlock = repo.wlock()
986 lock = repo.lock()
986 lock = repo.lock()
987
987
988 if update:
988 if update:
989 self.checklocalchanges(repo, force=force, refresh=False)
989 self.checklocalchanges(repo, force=force, refresh=False)
990 urev = self.qparents(repo, revs[0])
990 urev = self.qparents(repo, revs[0])
991 hg.clean(repo, urev)
991 hg.clean(repo, urev)
992 repo.dirstate.write()
992 repo.dirstate.write()
993
993
994 self.removeundo(repo)
994 self.removeundo(repo)
995 for rev in revs:
995 for rev in revs:
996 repair.strip(self.ui, repo, rev, backup)
996 repair.strip(self.ui, repo, rev, backup)
997 # strip may have unbundled a set of backed up revisions after
997 # strip may have unbundled a set of backed up revisions after
998 # the actual strip
998 # the actual strip
999 self.removeundo(repo)
999 self.removeundo(repo)
1000 finally:
1000 finally:
1001 release(lock, wlock)
1001 release(lock, wlock)
1002
1002
1003 def isapplied(self, patch):
1003 def isapplied(self, patch):
1004 """returns (index, rev, patch)"""
1004 """returns (index, rev, patch)"""
1005 for i, a in enumerate(self.applied):
1005 for i, a in enumerate(self.applied):
1006 if a.name == patch:
1006 if a.name == patch:
1007 return (i, a.node, a.name)
1007 return (i, a.node, a.name)
1008 return None
1008 return None
1009
1009
1010 # if the exact patch name does not exist, we try a few
1010 # if the exact patch name does not exist, we try a few
1011 # variations. If strict is passed, we try only #1
1011 # variations. If strict is passed, we try only #1
1012 #
1012 #
1013 # 1) a number to indicate an offset in the series file
1013 # 1) a number to indicate an offset in the series file
1014 # 2) a unique substring of the patch name was given
1014 # 2) a unique substring of the patch name was given
1015 # 3) patchname[-+]num to indicate an offset in the series file
1015 # 3) patchname[-+]num to indicate an offset in the series file
1016 def lookup(self, patch, strict=False):
1016 def lookup(self, patch, strict=False):
1017 patch = patch and str(patch)
1017 patch = patch and str(patch)
1018
1018
1019 def partialname(s):
1019 def partialname(s):
1020 if s in self.series:
1020 if s in self.series:
1021 return s
1021 return s
1022 matches = [x for x in self.series if s in x]
1022 matches = [x for x in self.series if s in x]
1023 if len(matches) > 1:
1023 if len(matches) > 1:
1024 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1024 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1025 for m in matches:
1025 for m in matches:
1026 self.ui.warn(' %s\n' % m)
1026 self.ui.warn(' %s\n' % m)
1027 return None
1027 return None
1028 if matches:
1028 if matches:
1029 return matches[0]
1029 return matches[0]
1030 if self.series and self.applied:
1030 if self.series and self.applied:
1031 if s == 'qtip':
1031 if s == 'qtip':
1032 return self.series[self.seriesend(True)-1]
1032 return self.series[self.seriesend(True)-1]
1033 if s == 'qbase':
1033 if s == 'qbase':
1034 return self.series[0]
1034 return self.series[0]
1035 return None
1035 return None
1036
1036
1037 if patch is None:
1037 if patch is None:
1038 return None
1038 return None
1039 if patch in self.series:
1039 if patch in self.series:
1040 return patch
1040 return patch
1041
1041
1042 if not os.path.isfile(self.join(patch)):
1042 if not os.path.isfile(self.join(patch)):
1043 try:
1043 try:
1044 sno = int(patch)
1044 sno = int(patch)
1045 except (ValueError, OverflowError):
1045 except (ValueError, OverflowError):
1046 pass
1046 pass
1047 else:
1047 else:
1048 if -len(self.series) <= sno < len(self.series):
1048 if -len(self.series) <= sno < len(self.series):
1049 return self.series[sno]
1049 return self.series[sno]
1050
1050
1051 if not strict:
1051 if not strict:
1052 res = partialname(patch)
1052 res = partialname(patch)
1053 if res:
1053 if res:
1054 return res
1054 return res
1055 minus = patch.rfind('-')
1055 minus = patch.rfind('-')
1056 if minus >= 0:
1056 if minus >= 0:
1057 res = partialname(patch[:minus])
1057 res = partialname(patch[:minus])
1058 if res:
1058 if res:
1059 i = self.series.index(res)
1059 i = self.series.index(res)
1060 try:
1060 try:
1061 off = int(patch[minus + 1:] or 1)
1061 off = int(patch[minus + 1:] or 1)
1062 except (ValueError, OverflowError):
1062 except (ValueError, OverflowError):
1063 pass
1063 pass
1064 else:
1064 else:
1065 if i - off >= 0:
1065 if i - off >= 0:
1066 return self.series[i - off]
1066 return self.series[i - off]
1067 plus = patch.rfind('+')
1067 plus = patch.rfind('+')
1068 if plus >= 0:
1068 if plus >= 0:
1069 res = partialname(patch[:plus])
1069 res = partialname(patch[:plus])
1070 if res:
1070 if res:
1071 i = self.series.index(res)
1071 i = self.series.index(res)
1072 try:
1072 try:
1073 off = int(patch[plus + 1:] or 1)
1073 off = int(patch[plus + 1:] or 1)
1074 except (ValueError, OverflowError):
1074 except (ValueError, OverflowError):
1075 pass
1075 pass
1076 else:
1076 else:
1077 if i + off < len(self.series):
1077 if i + off < len(self.series):
1078 return self.series[i + off]
1078 return self.series[i + off]
1079 raise util.Abort(_("patch %s not in series") % patch)
1079 raise util.Abort(_("patch %s not in series") % patch)
1080
1080
1081 def push(self, repo, patch=None, force=False, list=False,
1081 def push(self, repo, patch=None, force=False, list=False,
1082 mergeq=None, all=False, move=False, exact=False):
1082 mergeq=None, all=False, move=False, exact=False):
1083 diffopts = self.diffopts()
1083 diffopts = self.diffopts()
1084 wlock = repo.wlock()
1084 wlock = repo.wlock()
1085 try:
1085 try:
1086 heads = []
1086 heads = []
1087 for b, ls in repo.branchmap().iteritems():
1087 for b, ls in repo.branchmap().iteritems():
1088 heads += ls
1088 heads += ls
1089 if not heads:
1089 if not heads:
1090 heads = [nullid]
1090 heads = [nullid]
1091 if repo.dirstate.p1() not in heads and not exact:
1091 if repo.dirstate.p1() not in heads and not exact:
1092 self.ui.status(_("(working directory not at a head)\n"))
1092 self.ui.status(_("(working directory not at a head)\n"))
1093
1093
1094 if not self.series:
1094 if not self.series:
1095 self.ui.warn(_('no patches in series\n'))
1095 self.ui.warn(_('no patches in series\n'))
1096 return 0
1096 return 0
1097
1097
1098 patch = self.lookup(patch)
1098 patch = self.lookup(patch)
1099 # Suppose our series file is: A B C and the current 'top'
1099 # Suppose our series file is: A B C and the current 'top'
1100 # patch is B. qpush C should be performed (moving forward)
1100 # patch is B. qpush C should be performed (moving forward)
1101 # qpush B is a NOP (no change) qpush A is an error (can't
1101 # qpush B is a NOP (no change) qpush A is an error (can't
1102 # go backwards with qpush)
1102 # go backwards with qpush)
1103 if patch:
1103 if patch:
1104 info = self.isapplied(patch)
1104 info = self.isapplied(patch)
1105 if info and info[0] >= len(self.applied) - 1:
1105 if info and info[0] >= len(self.applied) - 1:
1106 self.ui.warn(
1106 self.ui.warn(
1107 _('qpush: %s is already at the top\n') % patch)
1107 _('qpush: %s is already at the top\n') % patch)
1108 return 0
1108 return 0
1109
1109
1110 pushable, reason = self.pushable(patch)
1110 pushable, reason = self.pushable(patch)
1111 if pushable:
1111 if pushable:
1112 if self.series.index(patch) < self.seriesend():
1112 if self.series.index(patch) < self.seriesend():
1113 raise util.Abort(
1113 raise util.Abort(
1114 _("cannot push to a previous patch: %s") % patch)
1114 _("cannot push to a previous patch: %s") % patch)
1115 else:
1115 else:
1116 if reason:
1116 if reason:
1117 reason = _('guarded by %s') % reason
1117 reason = _('guarded by %s') % reason
1118 else:
1118 else:
1119 reason = _('no matching guards')
1119 reason = _('no matching guards')
1120 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1120 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1121 return 1
1121 return 1
1122 elif all:
1122 elif all:
1123 patch = self.series[-1]
1123 patch = self.series[-1]
1124 if self.isapplied(patch):
1124 if self.isapplied(patch):
1125 self.ui.warn(_('all patches are currently applied\n'))
1125 self.ui.warn(_('all patches are currently applied\n'))
1126 return 0
1126 return 0
1127
1127
1128 # Following the above example, starting at 'top' of B:
1128 # Following the above example, starting at 'top' of B:
1129 # qpush should be performed (pushes C), but a subsequent
1129 # qpush should be performed (pushes C), but a subsequent
1130 # qpush without an argument is an error (nothing to
1130 # qpush without an argument is an error (nothing to
1131 # apply). This allows a loop of "...while hg qpush..." to
1131 # apply). This allows a loop of "...while hg qpush..." to
1132 # work as it detects an error when done
1132 # work as it detects an error when done
1133 start = self.seriesend()
1133 start = self.seriesend()
1134 if start == len(self.series):
1134 if start == len(self.series):
1135 self.ui.warn(_('patch series already fully applied\n'))
1135 self.ui.warn(_('patch series already fully applied\n'))
1136 return 1
1136 return 1
1137
1137
1138 if exact:
1138 if exact:
1139 if move:
1139 if move:
1140 raise util.Abort(_("cannot use --exact and --move together"))
1140 raise util.Abort(_("cannot use --exact and --move together"))
1141 if self.applied:
1141 if self.applied:
1142 raise util.Abort(_("cannot push --exact with applied patches"))
1142 raise util.Abort(_("cannot push --exact with applied patches"))
1143 root = self.series[start]
1143 root = self.series[start]
1144 target = patchheader(self.join(root), self.plainmode).parent
1144 target = patchheader(self.join(root), self.plainmode).parent
1145 if not target:
1145 if not target:
1146 raise util.Abort(_("%s does not have a parent recorded" % root))
1146 raise util.Abort(_("%s does not have a parent recorded" % root))
1147 if not repo[target] == repo['.']:
1147 if not repo[target] == repo['.']:
1148 hg.update(repo, target)
1148 hg.update(repo, target)
1149
1149
1150 if move:
1150 if move:
1151 if not patch:
1151 if not patch:
1152 raise util.Abort(_("please specify the patch to move"))
1152 raise util.Abort(_("please specify the patch to move"))
1153 for i, rpn in enumerate(self.fullseries[start:]):
1153 for i, rpn in enumerate(self.fullseries[start:]):
1154 # strip markers for patch guards
1154 # strip markers for patch guards
1155 if self.guard_re.split(rpn, 1)[0] == patch:
1155 if self.guard_re.split(rpn, 1)[0] == patch:
1156 break
1156 break
1157 index = start + i
1157 index = start + i
1158 assert index < len(self.fullseries)
1158 assert index < len(self.fullseries)
1159 fullpatch = self.fullseries[index]
1159 fullpatch = self.fullseries[index]
1160 del self.fullseries[index]
1160 del self.fullseries[index]
1161 self.fullseries.insert(start, fullpatch)
1161 self.fullseries.insert(start, fullpatch)
1162 self.parseseries()
1162 self.parseseries()
1163 self.seriesdirty = 1
1163 self.seriesdirty = 1
1164
1164
1165 self.applieddirty = 1
1165 self.applieddirty = 1
1166 if start > 0:
1166 if start > 0:
1167 self.checktoppatch(repo)
1167 self.checktoppatch(repo)
1168 if not patch:
1168 if not patch:
1169 patch = self.series[start]
1169 patch = self.series[start]
1170 end = start + 1
1170 end = start + 1
1171 else:
1171 else:
1172 end = self.series.index(patch, start) + 1
1172 end = self.series.index(patch, start) + 1
1173
1173
1174 s = self.series[start:end]
1174 s = self.series[start:end]
1175
1175
1176 if not force:
1176 if not force:
1177 mm, aa, rr, dd = repo.status()[:4]
1177 mm, aa, rr, dd = repo.status()[:4]
1178 wcfiles = set(mm + aa + rr + dd)
1178 wcfiles = set(mm + aa + rr + dd)
1179 if wcfiles:
1179 if wcfiles:
1180 for patchname in s:
1180 for patchname in s:
1181 pf = os.path.join(self.path, patchname)
1181 pf = os.path.join(self.path, patchname)
1182 patchfiles = patchmod.changedfiles(self.ui, repo, pf)
1182 patchfiles = patchmod.changedfiles(self.ui, repo, pf)
1183 if wcfiles.intersection(patchfiles):
1183 if wcfiles.intersection(patchfiles):
1184 self.localchangesfound(self.applied)
1184 self.localchangesfound(self.applied)
1185 elif mergeq:
1185 elif mergeq:
1186 self.checklocalchanges(refresh=self.applied)
1186 self.checklocalchanges(refresh=self.applied)
1187
1187
1188 all_files = set()
1188 all_files = set()
1189 try:
1189 try:
1190 if mergeq:
1190 if mergeq:
1191 ret = self.mergepatch(repo, mergeq, s, diffopts)
1191 ret = self.mergepatch(repo, mergeq, s, diffopts)
1192 else:
1192 else:
1193 ret = self.apply(repo, s, list, all_files=all_files)
1193 ret = self.apply(repo, s, list, all_files=all_files)
1194 except:
1194 except:
1195 self.ui.warn(_('cleaning up working directory...'))
1195 self.ui.warn(_('cleaning up working directory...'))
1196 node = repo.dirstate.p1()
1196 node = repo.dirstate.p1()
1197 hg.revert(repo, node, None)
1197 hg.revert(repo, node, None)
1198 # only remove unknown files that we know we touched or
1198 # only remove unknown files that we know we touched or
1199 # created while patching
1199 # created while patching
1200 for f in all_files:
1200 for f in all_files:
1201 if f not in repo.dirstate:
1201 if f not in repo.dirstate:
1202 try:
1202 try:
1203 util.unlinkpath(repo.wjoin(f))
1203 util.unlinkpath(repo.wjoin(f))
1204 except OSError, inst:
1204 except OSError, inst:
1205 if inst.errno != errno.ENOENT:
1205 if inst.errno != errno.ENOENT:
1206 raise
1206 raise
1207 self.ui.warn(_('done\n'))
1207 self.ui.warn(_('done\n'))
1208 raise
1208 raise
1209
1209
1210 if not self.applied:
1210 if not self.applied:
1211 return ret[0]
1211 return ret[0]
1212 top = self.applied[-1].name
1212 top = self.applied[-1].name
1213 if ret[0] and ret[0] > 1:
1213 if ret[0] and ret[0] > 1:
1214 msg = _("errors during apply, please fix and refresh %s\n")
1214 msg = _("errors during apply, please fix and refresh %s\n")
1215 self.ui.write(msg % top)
1215 self.ui.write(msg % top)
1216 else:
1216 else:
1217 self.ui.write(_("now at: %s\n") % top)
1217 self.ui.write(_("now at: %s\n") % top)
1218 return ret[0]
1218 return ret[0]
1219
1219
1220 finally:
1220 finally:
1221 wlock.release()
1221 wlock.release()
1222
1222
1223 def pop(self, repo, patch=None, force=False, update=True, all=False):
1223 def pop(self, repo, patch=None, force=False, update=True, all=False):
1224 wlock = repo.wlock()
1224 wlock = repo.wlock()
1225 try:
1225 try:
1226 if patch:
1226 if patch:
1227 # index, rev, patch
1227 # index, rev, patch
1228 info = self.isapplied(patch)
1228 info = self.isapplied(patch)
1229 if not info:
1229 if not info:
1230 patch = self.lookup(patch)
1230 patch = self.lookup(patch)
1231 info = self.isapplied(patch)
1231 info = self.isapplied(patch)
1232 if not info:
1232 if not info:
1233 raise util.Abort(_("patch %s is not applied") % patch)
1233 raise util.Abort(_("patch %s is not applied") % patch)
1234
1234
1235 if not self.applied:
1235 if not self.applied:
1236 # Allow qpop -a to work repeatedly,
1236 # Allow qpop -a to work repeatedly,
1237 # but not qpop without an argument
1237 # but not qpop without an argument
1238 self.ui.warn(_("no patches applied\n"))
1238 self.ui.warn(_("no patches applied\n"))
1239 return not all
1239 return not all
1240
1240
1241 if all:
1241 if all:
1242 start = 0
1242 start = 0
1243 elif patch:
1243 elif patch:
1244 start = info[0] + 1
1244 start = info[0] + 1
1245 else:
1245 else:
1246 start = len(self.applied) - 1
1246 start = len(self.applied) - 1
1247
1247
1248 if start >= len(self.applied):
1248 if start >= len(self.applied):
1249 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1249 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1250 return
1250 return
1251
1251
1252 if not update:
1252 if not update:
1253 parents = repo.dirstate.parents()
1253 parents = repo.dirstate.parents()
1254 rr = [x.node for x in self.applied]
1254 rr = [x.node for x in self.applied]
1255 for p in parents:
1255 for p in parents:
1256 if p in rr:
1256 if p in rr:
1257 self.ui.warn(_("qpop: forcing dirstate update\n"))
1257 self.ui.warn(_("qpop: forcing dirstate update\n"))
1258 update = True
1258 update = True
1259 else:
1259 else:
1260 parents = [p.node() for p in repo[None].parents()]
1260 parents = [p.node() for p in repo[None].parents()]
1261 needupdate = False
1261 needupdate = False
1262 for entry in self.applied[start:]:
1262 for entry in self.applied[start:]:
1263 if entry.node in parents:
1263 if entry.node in parents:
1264 needupdate = True
1264 needupdate = True
1265 break
1265 break
1266 update = needupdate
1266 update = needupdate
1267
1267
1268 self.applieddirty = 1
1268 self.applieddirty = 1
1269 end = len(self.applied)
1269 end = len(self.applied)
1270 rev = self.applied[start].node
1270 rev = self.applied[start].node
1271 if update:
1271 if update:
1272 top = self.checktoppatch(repo)[0]
1272 top = self.checktoppatch(repo)[0]
1273
1273
1274 try:
1274 try:
1275 heads = repo.changelog.heads(rev)
1275 heads = repo.changelog.heads(rev)
1276 except error.LookupError:
1276 except error.LookupError:
1277 node = short(rev)
1277 node = short(rev)
1278 raise util.Abort(_('trying to pop unknown node %s') % node)
1278 raise util.Abort(_('trying to pop unknown node %s') % node)
1279
1279
1280 if heads != [self.applied[-1].node]:
1280 if heads != [self.applied[-1].node]:
1281 raise util.Abort(_("popping would remove a revision not "
1281 raise util.Abort(_("popping would remove a revision not "
1282 "managed by this patch queue"))
1282 "managed by this patch queue"))
1283
1283
1284 # we know there are no local changes, so we can make a simplified
1284 # we know there are no local changes, so we can make a simplified
1285 # form of hg.update.
1285 # form of hg.update.
1286 if update:
1286 if update:
1287 qp = self.qparents(repo, rev)
1287 qp = self.qparents(repo, rev)
1288 ctx = repo[qp]
1288 ctx = repo[qp]
1289 m, a, r, d = repo.status(qp, top)[:4]
1289 m, a, r, d = repo.status(qp, top)[:4]
1290 parentfiles = set(m + a + r + d)
1290 parentfiles = set(m + a + r + d)
1291 if not force and parentfiles:
1291 if not force and parentfiles:
1292 mm, aa, rr, dd = repo.status()[:4]
1292 mm, aa, rr, dd = repo.status()[:4]
1293 wcfiles = set(mm + aa + rr + dd)
1293 wcfiles = set(mm + aa + rr + dd)
1294 if wcfiles.intersection(parentfiles):
1294 if wcfiles.intersection(parentfiles):
1295 self.localchangesfound()
1295 self.localchangesfound()
1296 if d:
1296 if d:
1297 raise util.Abort(_("deletions found between repo revs"))
1297 raise util.Abort(_("deletions found between repo revs"))
1298 for f in a:
1298 for f in a:
1299 try:
1299 try:
1300 util.unlinkpath(repo.wjoin(f))
1300 util.unlinkpath(repo.wjoin(f))
1301 except OSError, e:
1301 except OSError, e:
1302 if e.errno != errno.ENOENT:
1302 if e.errno != errno.ENOENT:
1303 raise
1303 raise
1304 repo.dirstate.drop(f)
1304 repo.dirstate.drop(f)
1305 for f in m + r:
1305 for f in m + r:
1306 fctx = ctx[f]
1306 fctx = ctx[f]
1307 repo.wwrite(f, fctx.data(), fctx.flags())
1307 repo.wwrite(f, fctx.data(), fctx.flags())
1308 repo.dirstate.normal(f)
1308 repo.dirstate.normal(f)
1309 repo.dirstate.setparents(qp, nullid)
1309 repo.dirstate.setparents(qp, nullid)
1310 for patch in reversed(self.applied[start:end]):
1310 for patch in reversed(self.applied[start:end]):
1311 self.ui.status(_("popping %s\n") % patch.name)
1311 self.ui.status(_("popping %s\n") % patch.name)
1312 del self.applied[start:end]
1312 del self.applied[start:end]
1313 self.strip(repo, [rev], update=False, backup='strip')
1313 self.strip(repo, [rev], update=False, backup='strip')
1314 if self.applied:
1314 if self.applied:
1315 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1315 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1316 else:
1316 else:
1317 self.ui.write(_("patch queue now empty\n"))
1317 self.ui.write(_("patch queue now empty\n"))
1318 finally:
1318 finally:
1319 wlock.release()
1319 wlock.release()
1320
1320
1321 def diff(self, repo, pats, opts):
1321 def diff(self, repo, pats, opts):
1322 top, patch = self.checktoppatch(repo)
1322 top, patch = self.checktoppatch(repo)
1323 if not top:
1323 if not top:
1324 self.ui.write(_("no patches applied\n"))
1324 self.ui.write(_("no patches applied\n"))
1325 return
1325 return
1326 qp = self.qparents(repo, top)
1326 qp = self.qparents(repo, top)
1327 if opts.get('reverse'):
1327 if opts.get('reverse'):
1328 node1, node2 = None, qp
1328 node1, node2 = None, qp
1329 else:
1329 else:
1330 node1, node2 = qp, None
1330 node1, node2 = qp, None
1331 diffopts = self.diffopts(opts, patch)
1331 diffopts = self.diffopts(opts, patch)
1332 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1332 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1333
1333
1334 def refresh(self, repo, pats=None, **opts):
1334 def refresh(self, repo, pats=None, **opts):
1335 if not self.applied:
1335 if not self.applied:
1336 self.ui.write(_("no patches applied\n"))
1336 self.ui.write(_("no patches applied\n"))
1337 return 1
1337 return 1
1338 msg = opts.get('msg', '').rstrip()
1338 msg = opts.get('msg', '').rstrip()
1339 newuser = opts.get('user')
1339 newuser = opts.get('user')
1340 newdate = opts.get('date')
1340 newdate = opts.get('date')
1341 if newdate:
1341 if newdate:
1342 newdate = '%d %d' % util.parsedate(newdate)
1342 newdate = '%d %d' % util.parsedate(newdate)
1343 wlock = repo.wlock()
1343 wlock = repo.wlock()
1344
1344
1345 try:
1345 try:
1346 self.checktoppatch(repo)
1346 self.checktoppatch(repo)
1347 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1347 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1348 if repo.changelog.heads(top) != [top]:
1348 if repo.changelog.heads(top) != [top]:
1349 raise util.Abort(_("cannot refresh a revision with children"))
1349 raise util.Abort(_("cannot refresh a revision with children"))
1350
1350
1351 inclsubs = self.checksubstate(repo)
1351 inclsubs = self.checksubstate(repo)
1352
1352
1353 cparents = repo.changelog.parents(top)
1353 cparents = repo.changelog.parents(top)
1354 patchparent = self.qparents(repo, top)
1354 patchparent = self.qparents(repo, top)
1355 ph = patchheader(self.join(patchfn), self.plainmode)
1355 ph = patchheader(self.join(patchfn), self.plainmode)
1356 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1356 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1357 if msg:
1357 if msg:
1358 ph.setmessage(msg)
1358 ph.setmessage(msg)
1359 if newuser:
1359 if newuser:
1360 ph.setuser(newuser)
1360 ph.setuser(newuser)
1361 if newdate:
1361 if newdate:
1362 ph.setdate(newdate)
1362 ph.setdate(newdate)
1363 ph.setparent(hex(patchparent))
1363 ph.setparent(hex(patchparent))
1364
1364
1365 # only commit new patch when write is complete
1365 # only commit new patch when write is complete
1366 patchf = self.opener(patchfn, 'w', atomictemp=True)
1366 patchf = self.opener(patchfn, 'w', atomictemp=True)
1367
1367
1368 comments = str(ph)
1368 comments = str(ph)
1369 if comments:
1369 if comments:
1370 patchf.write(comments)
1370 patchf.write(comments)
1371
1371
1372 # update the dirstate in place, strip off the qtip commit
1372 # update the dirstate in place, strip off the qtip commit
1373 # and then commit.
1373 # and then commit.
1374 #
1374 #
1375 # this should really read:
1375 # this should really read:
1376 # mm, dd, aa = repo.status(top, patchparent)[:3]
1376 # mm, dd, aa = repo.status(top, patchparent)[:3]
1377 # but we do it backwards to take advantage of manifest/chlog
1377 # but we do it backwards to take advantage of manifest/chlog
1378 # caching against the next repo.status call
1378 # caching against the next repo.status call
1379 mm, aa, dd = repo.status(patchparent, top)[:3]
1379 mm, aa, dd = repo.status(patchparent, top)[:3]
1380 changes = repo.changelog.read(top)
1380 changes = repo.changelog.read(top)
1381 man = repo.manifest.read(changes[0])
1381 man = repo.manifest.read(changes[0])
1382 aaa = aa[:]
1382 aaa = aa[:]
1383 matchfn = scmutil.match(repo, pats, opts)
1383 matchfn = scmutil.match(repo[None], pats, opts)
1384 # in short mode, we only diff the files included in the
1384 # in short mode, we only diff the files included in the
1385 # patch already plus specified files
1385 # patch already plus specified files
1386 if opts.get('short'):
1386 if opts.get('short'):
1387 # if amending a patch, we start with existing
1387 # if amending a patch, we start with existing
1388 # files plus specified files - unfiltered
1388 # files plus specified files - unfiltered
1389 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1389 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1390 # filter with inc/exl options
1390 # filter with inc/exl options
1391 matchfn = scmutil.match(repo, opts=opts)
1391 matchfn = scmutil.match(repo[None], opts=opts)
1392 else:
1392 else:
1393 match = scmutil.matchall(repo)
1393 match = scmutil.matchall(repo)
1394 m, a, r, d = repo.status(match=match)[:4]
1394 m, a, r, d = repo.status(match=match)[:4]
1395 mm = set(mm)
1395 mm = set(mm)
1396 aa = set(aa)
1396 aa = set(aa)
1397 dd = set(dd)
1397 dd = set(dd)
1398
1398
1399 # we might end up with files that were added between
1399 # we might end up with files that were added between
1400 # qtip and the dirstate parent, but then changed in the
1400 # qtip and the dirstate parent, but then changed in the
1401 # local dirstate. in this case, we want them to only
1401 # local dirstate. in this case, we want them to only
1402 # show up in the added section
1402 # show up in the added section
1403 for x in m:
1403 for x in m:
1404 if x not in aa:
1404 if x not in aa:
1405 mm.add(x)
1405 mm.add(x)
1406 # we might end up with files added by the local dirstate that
1406 # we might end up with files added by the local dirstate that
1407 # were deleted by the patch. In this case, they should only
1407 # were deleted by the patch. In this case, they should only
1408 # show up in the changed section.
1408 # show up in the changed section.
1409 for x in a:
1409 for x in a:
1410 if x in dd:
1410 if x in dd:
1411 dd.remove(x)
1411 dd.remove(x)
1412 mm.add(x)
1412 mm.add(x)
1413 else:
1413 else:
1414 aa.add(x)
1414 aa.add(x)
1415 # make sure any files deleted in the local dirstate
1415 # make sure any files deleted in the local dirstate
1416 # are not in the add or change column of the patch
1416 # are not in the add or change column of the patch
1417 forget = []
1417 forget = []
1418 for x in d + r:
1418 for x in d + r:
1419 if x in aa:
1419 if x in aa:
1420 aa.remove(x)
1420 aa.remove(x)
1421 forget.append(x)
1421 forget.append(x)
1422 continue
1422 continue
1423 else:
1423 else:
1424 mm.discard(x)
1424 mm.discard(x)
1425 dd.add(x)
1425 dd.add(x)
1426
1426
1427 m = list(mm)
1427 m = list(mm)
1428 r = list(dd)
1428 r = list(dd)
1429 a = list(aa)
1429 a = list(aa)
1430 c = [filter(matchfn, l) for l in (m, a, r)]
1430 c = [filter(matchfn, l) for l in (m, a, r)]
1431 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1431 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1432 chunks = patchmod.diff(repo, patchparent, match=match,
1432 chunks = patchmod.diff(repo, patchparent, match=match,
1433 changes=c, opts=diffopts)
1433 changes=c, opts=diffopts)
1434 for chunk in chunks:
1434 for chunk in chunks:
1435 patchf.write(chunk)
1435 patchf.write(chunk)
1436
1436
1437 try:
1437 try:
1438 if diffopts.git or diffopts.upgrade:
1438 if diffopts.git or diffopts.upgrade:
1439 copies = {}
1439 copies = {}
1440 for dst in a:
1440 for dst in a:
1441 src = repo.dirstate.copied(dst)
1441 src = repo.dirstate.copied(dst)
1442 # during qfold, the source file for copies may
1442 # during qfold, the source file for copies may
1443 # be removed. Treat this as a simple add.
1443 # be removed. Treat this as a simple add.
1444 if src is not None and src in repo.dirstate:
1444 if src is not None and src in repo.dirstate:
1445 copies.setdefault(src, []).append(dst)
1445 copies.setdefault(src, []).append(dst)
1446 repo.dirstate.add(dst)
1446 repo.dirstate.add(dst)
1447 # remember the copies between patchparent and qtip
1447 # remember the copies between patchparent and qtip
1448 for dst in aaa:
1448 for dst in aaa:
1449 f = repo.file(dst)
1449 f = repo.file(dst)
1450 src = f.renamed(man[dst])
1450 src = f.renamed(man[dst])
1451 if src:
1451 if src:
1452 copies.setdefault(src[0], []).extend(
1452 copies.setdefault(src[0], []).extend(
1453 copies.get(dst, []))
1453 copies.get(dst, []))
1454 if dst in a:
1454 if dst in a:
1455 copies[src[0]].append(dst)
1455 copies[src[0]].append(dst)
1456 # we can't copy a file created by the patch itself
1456 # we can't copy a file created by the patch itself
1457 if dst in copies:
1457 if dst in copies:
1458 del copies[dst]
1458 del copies[dst]
1459 for src, dsts in copies.iteritems():
1459 for src, dsts in copies.iteritems():
1460 for dst in dsts:
1460 for dst in dsts:
1461 repo.dirstate.copy(src, dst)
1461 repo.dirstate.copy(src, dst)
1462 else:
1462 else:
1463 for dst in a:
1463 for dst in a:
1464 repo.dirstate.add(dst)
1464 repo.dirstate.add(dst)
1465 # Drop useless copy information
1465 # Drop useless copy information
1466 for f in list(repo.dirstate.copies()):
1466 for f in list(repo.dirstate.copies()):
1467 repo.dirstate.copy(None, f)
1467 repo.dirstate.copy(None, f)
1468 for f in r:
1468 for f in r:
1469 repo.dirstate.remove(f)
1469 repo.dirstate.remove(f)
1470 # if the patch excludes a modified file, mark that
1470 # if the patch excludes a modified file, mark that
1471 # file with mtime=0 so status can see it.
1471 # file with mtime=0 so status can see it.
1472 mm = []
1472 mm = []
1473 for i in xrange(len(m)-1, -1, -1):
1473 for i in xrange(len(m)-1, -1, -1):
1474 if not matchfn(m[i]):
1474 if not matchfn(m[i]):
1475 mm.append(m[i])
1475 mm.append(m[i])
1476 del m[i]
1476 del m[i]
1477 for f in m:
1477 for f in m:
1478 repo.dirstate.normal(f)
1478 repo.dirstate.normal(f)
1479 for f in mm:
1479 for f in mm:
1480 repo.dirstate.normallookup(f)
1480 repo.dirstate.normallookup(f)
1481 for f in forget:
1481 for f in forget:
1482 repo.dirstate.drop(f)
1482 repo.dirstate.drop(f)
1483
1483
1484 if not msg:
1484 if not msg:
1485 if not ph.message:
1485 if not ph.message:
1486 message = "[mq]: %s\n" % patchfn
1486 message = "[mq]: %s\n" % patchfn
1487 else:
1487 else:
1488 message = "\n".join(ph.message)
1488 message = "\n".join(ph.message)
1489 else:
1489 else:
1490 message = msg
1490 message = msg
1491
1491
1492 user = ph.user or changes[1]
1492 user = ph.user or changes[1]
1493
1493
1494 # assumes strip can roll itself back if interrupted
1494 # assumes strip can roll itself back if interrupted
1495 repo.dirstate.setparents(*cparents)
1495 repo.dirstate.setparents(*cparents)
1496 self.applied.pop()
1496 self.applied.pop()
1497 self.applieddirty = 1
1497 self.applieddirty = 1
1498 self.strip(repo, [top], update=False,
1498 self.strip(repo, [top], update=False,
1499 backup='strip')
1499 backup='strip')
1500 except:
1500 except:
1501 repo.dirstate.invalidate()
1501 repo.dirstate.invalidate()
1502 raise
1502 raise
1503
1503
1504 try:
1504 try:
1505 # might be nice to attempt to roll back strip after this
1505 # might be nice to attempt to roll back strip after this
1506 n = repo.commit(message, user, ph.date, match=match,
1506 n = repo.commit(message, user, ph.date, match=match,
1507 force=True)
1507 force=True)
1508 # only write patch after a successful commit
1508 # only write patch after a successful commit
1509 patchf.rename()
1509 patchf.rename()
1510 self.applied.append(statusentry(n, patchfn))
1510 self.applied.append(statusentry(n, patchfn))
1511 except:
1511 except:
1512 ctx = repo[cparents[0]]
1512 ctx = repo[cparents[0]]
1513 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1513 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1514 self.savedirty()
1514 self.savedirty()
1515 self.ui.warn(_('refresh interrupted while patch was popped! '
1515 self.ui.warn(_('refresh interrupted while patch was popped! '
1516 '(revert --all, qpush to recover)\n'))
1516 '(revert --all, qpush to recover)\n'))
1517 raise
1517 raise
1518 finally:
1518 finally:
1519 wlock.release()
1519 wlock.release()
1520 self.removeundo(repo)
1520 self.removeundo(repo)
1521
1521
1522 def init(self, repo, create=False):
1522 def init(self, repo, create=False):
1523 if not create and os.path.isdir(self.path):
1523 if not create and os.path.isdir(self.path):
1524 raise util.Abort(_("patch queue directory already exists"))
1524 raise util.Abort(_("patch queue directory already exists"))
1525 try:
1525 try:
1526 os.mkdir(self.path)
1526 os.mkdir(self.path)
1527 except OSError, inst:
1527 except OSError, inst:
1528 if inst.errno != errno.EEXIST or not create:
1528 if inst.errno != errno.EEXIST or not create:
1529 raise
1529 raise
1530 if create:
1530 if create:
1531 return self.qrepo(create=True)
1531 return self.qrepo(create=True)
1532
1532
1533 def unapplied(self, repo, patch=None):
1533 def unapplied(self, repo, patch=None):
1534 if patch and patch not in self.series:
1534 if patch and patch not in self.series:
1535 raise util.Abort(_("patch %s is not in series file") % patch)
1535 raise util.Abort(_("patch %s is not in series file") % patch)
1536 if not patch:
1536 if not patch:
1537 start = self.seriesend()
1537 start = self.seriesend()
1538 else:
1538 else:
1539 start = self.series.index(patch) + 1
1539 start = self.series.index(patch) + 1
1540 unapplied = []
1540 unapplied = []
1541 for i in xrange(start, len(self.series)):
1541 for i in xrange(start, len(self.series)):
1542 pushable, reason = self.pushable(i)
1542 pushable, reason = self.pushable(i)
1543 if pushable:
1543 if pushable:
1544 unapplied.append((i, self.series[i]))
1544 unapplied.append((i, self.series[i]))
1545 self.explainpushable(i)
1545 self.explainpushable(i)
1546 return unapplied
1546 return unapplied
1547
1547
1548 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1548 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1549 summary=False):
1549 summary=False):
1550 def displayname(pfx, patchname, state):
1550 def displayname(pfx, patchname, state):
1551 if pfx:
1551 if pfx:
1552 self.ui.write(pfx)
1552 self.ui.write(pfx)
1553 if summary:
1553 if summary:
1554 ph = patchheader(self.join(patchname), self.plainmode)
1554 ph = patchheader(self.join(patchname), self.plainmode)
1555 msg = ph.message and ph.message[0] or ''
1555 msg = ph.message and ph.message[0] or ''
1556 if self.ui.formatted():
1556 if self.ui.formatted():
1557 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1557 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1558 if width > 0:
1558 if width > 0:
1559 msg = util.ellipsis(msg, width)
1559 msg = util.ellipsis(msg, width)
1560 else:
1560 else:
1561 msg = ''
1561 msg = ''
1562 self.ui.write(patchname, label='qseries.' + state)
1562 self.ui.write(patchname, label='qseries.' + state)
1563 self.ui.write(': ')
1563 self.ui.write(': ')
1564 self.ui.write(msg, label='qseries.message.' + state)
1564 self.ui.write(msg, label='qseries.message.' + state)
1565 else:
1565 else:
1566 self.ui.write(patchname, label='qseries.' + state)
1566 self.ui.write(patchname, label='qseries.' + state)
1567 self.ui.write('\n')
1567 self.ui.write('\n')
1568
1568
1569 applied = set([p.name for p in self.applied])
1569 applied = set([p.name for p in self.applied])
1570 if length is None:
1570 if length is None:
1571 length = len(self.series) - start
1571 length = len(self.series) - start
1572 if not missing:
1572 if not missing:
1573 if self.ui.verbose:
1573 if self.ui.verbose:
1574 idxwidth = len(str(start + length - 1))
1574 idxwidth = len(str(start + length - 1))
1575 for i in xrange(start, start + length):
1575 for i in xrange(start, start + length):
1576 patch = self.series[i]
1576 patch = self.series[i]
1577 if patch in applied:
1577 if patch in applied:
1578 char, state = 'A', 'applied'
1578 char, state = 'A', 'applied'
1579 elif self.pushable(i)[0]:
1579 elif self.pushable(i)[0]:
1580 char, state = 'U', 'unapplied'
1580 char, state = 'U', 'unapplied'
1581 else:
1581 else:
1582 char, state = 'G', 'guarded'
1582 char, state = 'G', 'guarded'
1583 pfx = ''
1583 pfx = ''
1584 if self.ui.verbose:
1584 if self.ui.verbose:
1585 pfx = '%*d %s ' % (idxwidth, i, char)
1585 pfx = '%*d %s ' % (idxwidth, i, char)
1586 elif status and status != char:
1586 elif status and status != char:
1587 continue
1587 continue
1588 displayname(pfx, patch, state)
1588 displayname(pfx, patch, state)
1589 else:
1589 else:
1590 msng_list = []
1590 msng_list = []
1591 for root, dirs, files in os.walk(self.path):
1591 for root, dirs, files in os.walk(self.path):
1592 d = root[len(self.path) + 1:]
1592 d = root[len(self.path) + 1:]
1593 for f in files:
1593 for f in files:
1594 fl = os.path.join(d, f)
1594 fl = os.path.join(d, f)
1595 if (fl not in self.series and
1595 if (fl not in self.series and
1596 fl not in (self.statuspath, self.seriespath,
1596 fl not in (self.statuspath, self.seriespath,
1597 self.guardspath)
1597 self.guardspath)
1598 and not fl.startswith('.')):
1598 and not fl.startswith('.')):
1599 msng_list.append(fl)
1599 msng_list.append(fl)
1600 for x in sorted(msng_list):
1600 for x in sorted(msng_list):
1601 pfx = self.ui.verbose and ('D ') or ''
1601 pfx = self.ui.verbose and ('D ') or ''
1602 displayname(pfx, x, 'missing')
1602 displayname(pfx, x, 'missing')
1603
1603
1604 def issaveline(self, l):
1604 def issaveline(self, l):
1605 if l.name == '.hg.patches.save.line':
1605 if l.name == '.hg.patches.save.line':
1606 return True
1606 return True
1607
1607
1608 def qrepo(self, create=False):
1608 def qrepo(self, create=False):
1609 ui = self.ui.copy()
1609 ui = self.ui.copy()
1610 ui.setconfig('paths', 'default', '', overlay=False)
1610 ui.setconfig('paths', 'default', '', overlay=False)
1611 ui.setconfig('paths', 'default-push', '', overlay=False)
1611 ui.setconfig('paths', 'default-push', '', overlay=False)
1612 if create or os.path.isdir(self.join(".hg")):
1612 if create or os.path.isdir(self.join(".hg")):
1613 return hg.repository(ui, path=self.path, create=create)
1613 return hg.repository(ui, path=self.path, create=create)
1614
1614
1615 def restore(self, repo, rev, delete=None, qupdate=None):
1615 def restore(self, repo, rev, delete=None, qupdate=None):
1616 desc = repo[rev].description().strip()
1616 desc = repo[rev].description().strip()
1617 lines = desc.splitlines()
1617 lines = desc.splitlines()
1618 i = 0
1618 i = 0
1619 datastart = None
1619 datastart = None
1620 series = []
1620 series = []
1621 applied = []
1621 applied = []
1622 qpp = None
1622 qpp = None
1623 for i, line in enumerate(lines):
1623 for i, line in enumerate(lines):
1624 if line == 'Patch Data:':
1624 if line == 'Patch Data:':
1625 datastart = i + 1
1625 datastart = i + 1
1626 elif line.startswith('Dirstate:'):
1626 elif line.startswith('Dirstate:'):
1627 l = line.rstrip()
1627 l = line.rstrip()
1628 l = l[10:].split(' ')
1628 l = l[10:].split(' ')
1629 qpp = [bin(x) for x in l]
1629 qpp = [bin(x) for x in l]
1630 elif datastart is not None:
1630 elif datastart is not None:
1631 l = line.rstrip()
1631 l = line.rstrip()
1632 n, name = l.split(':', 1)
1632 n, name = l.split(':', 1)
1633 if n:
1633 if n:
1634 applied.append(statusentry(bin(n), name))
1634 applied.append(statusentry(bin(n), name))
1635 else:
1635 else:
1636 series.append(l)
1636 series.append(l)
1637 if datastart is None:
1637 if datastart is None:
1638 self.ui.warn(_("No saved patch data found\n"))
1638 self.ui.warn(_("No saved patch data found\n"))
1639 return 1
1639 return 1
1640 self.ui.warn(_("restoring status: %s\n") % lines[0])
1640 self.ui.warn(_("restoring status: %s\n") % lines[0])
1641 self.fullseries = series
1641 self.fullseries = series
1642 self.applied = applied
1642 self.applied = applied
1643 self.parseseries()
1643 self.parseseries()
1644 self.seriesdirty = 1
1644 self.seriesdirty = 1
1645 self.applieddirty = 1
1645 self.applieddirty = 1
1646 heads = repo.changelog.heads()
1646 heads = repo.changelog.heads()
1647 if delete:
1647 if delete:
1648 if rev not in heads:
1648 if rev not in heads:
1649 self.ui.warn(_("save entry has children, leaving it alone\n"))
1649 self.ui.warn(_("save entry has children, leaving it alone\n"))
1650 else:
1650 else:
1651 self.ui.warn(_("removing save entry %s\n") % short(rev))
1651 self.ui.warn(_("removing save entry %s\n") % short(rev))
1652 pp = repo.dirstate.parents()
1652 pp = repo.dirstate.parents()
1653 if rev in pp:
1653 if rev in pp:
1654 update = True
1654 update = True
1655 else:
1655 else:
1656 update = False
1656 update = False
1657 self.strip(repo, [rev], update=update, backup='strip')
1657 self.strip(repo, [rev], update=update, backup='strip')
1658 if qpp:
1658 if qpp:
1659 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1659 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1660 (short(qpp[0]), short(qpp[1])))
1660 (short(qpp[0]), short(qpp[1])))
1661 if qupdate:
1661 if qupdate:
1662 self.ui.status(_("updating queue directory\n"))
1662 self.ui.status(_("updating queue directory\n"))
1663 r = self.qrepo()
1663 r = self.qrepo()
1664 if not r:
1664 if not r:
1665 self.ui.warn(_("Unable to load queue repository\n"))
1665 self.ui.warn(_("Unable to load queue repository\n"))
1666 return 1
1666 return 1
1667 hg.clean(r, qpp[0])
1667 hg.clean(r, qpp[0])
1668
1668
1669 def save(self, repo, msg=None):
1669 def save(self, repo, msg=None):
1670 if not self.applied:
1670 if not self.applied:
1671 self.ui.warn(_("save: no patches applied, exiting\n"))
1671 self.ui.warn(_("save: no patches applied, exiting\n"))
1672 return 1
1672 return 1
1673 if self.issaveline(self.applied[-1]):
1673 if self.issaveline(self.applied[-1]):
1674 self.ui.warn(_("status is already saved\n"))
1674 self.ui.warn(_("status is already saved\n"))
1675 return 1
1675 return 1
1676
1676
1677 if not msg:
1677 if not msg:
1678 msg = _("hg patches saved state")
1678 msg = _("hg patches saved state")
1679 else:
1679 else:
1680 msg = "hg patches: " + msg.rstrip('\r\n')
1680 msg = "hg patches: " + msg.rstrip('\r\n')
1681 r = self.qrepo()
1681 r = self.qrepo()
1682 if r:
1682 if r:
1683 pp = r.dirstate.parents()
1683 pp = r.dirstate.parents()
1684 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1684 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1685 msg += "\n\nPatch Data:\n"
1685 msg += "\n\nPatch Data:\n"
1686 msg += ''.join('%s\n' % x for x in self.applied)
1686 msg += ''.join('%s\n' % x for x in self.applied)
1687 msg += ''.join(':%s\n' % x for x in self.fullseries)
1687 msg += ''.join(':%s\n' % x for x in self.fullseries)
1688 n = repo.commit(msg, force=True)
1688 n = repo.commit(msg, force=True)
1689 if not n:
1689 if not n:
1690 self.ui.warn(_("repo commit failed\n"))
1690 self.ui.warn(_("repo commit failed\n"))
1691 return 1
1691 return 1
1692 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1692 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1693 self.applieddirty = 1
1693 self.applieddirty = 1
1694 self.removeundo(repo)
1694 self.removeundo(repo)
1695
1695
1696 def fullseriesend(self):
1696 def fullseriesend(self):
1697 if self.applied:
1697 if self.applied:
1698 p = self.applied[-1].name
1698 p = self.applied[-1].name
1699 end = self.findseries(p)
1699 end = self.findseries(p)
1700 if end is None:
1700 if end is None:
1701 return len(self.fullseries)
1701 return len(self.fullseries)
1702 return end + 1
1702 return end + 1
1703 return 0
1703 return 0
1704
1704
1705 def seriesend(self, all_patches=False):
1705 def seriesend(self, all_patches=False):
1706 """If all_patches is False, return the index of the next pushable patch
1706 """If all_patches is False, return the index of the next pushable patch
1707 in the series, or the series length. If all_patches is True, return the
1707 in the series, or the series length. If all_patches is True, return the
1708 index of the first patch past the last applied one.
1708 index of the first patch past the last applied one.
1709 """
1709 """
1710 end = 0
1710 end = 0
1711 def next(start):
1711 def next(start):
1712 if all_patches or start >= len(self.series):
1712 if all_patches or start >= len(self.series):
1713 return start
1713 return start
1714 for i in xrange(start, len(self.series)):
1714 for i in xrange(start, len(self.series)):
1715 p, reason = self.pushable(i)
1715 p, reason = self.pushable(i)
1716 if p:
1716 if p:
1717 break
1717 break
1718 self.explainpushable(i)
1718 self.explainpushable(i)
1719 return i
1719 return i
1720 if self.applied:
1720 if self.applied:
1721 p = self.applied[-1].name
1721 p = self.applied[-1].name
1722 try:
1722 try:
1723 end = self.series.index(p)
1723 end = self.series.index(p)
1724 except ValueError:
1724 except ValueError:
1725 return 0
1725 return 0
1726 return next(end + 1)
1726 return next(end + 1)
1727 return next(end)
1727 return next(end)
1728
1728
1729 def appliedname(self, index):
1729 def appliedname(self, index):
1730 pname = self.applied[index].name
1730 pname = self.applied[index].name
1731 if not self.ui.verbose:
1731 if not self.ui.verbose:
1732 p = pname
1732 p = pname
1733 else:
1733 else:
1734 p = str(self.series.index(pname)) + " " + pname
1734 p = str(self.series.index(pname)) + " " + pname
1735 return p
1735 return p
1736
1736
1737 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1737 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1738 force=None, git=False):
1738 force=None, git=False):
1739 def checkseries(patchname):
1739 def checkseries(patchname):
1740 if patchname in self.series:
1740 if patchname in self.series:
1741 raise util.Abort(_('patch %s is already in the series file')
1741 raise util.Abort(_('patch %s is already in the series file')
1742 % patchname)
1742 % patchname)
1743
1743
1744 if rev:
1744 if rev:
1745 if files:
1745 if files:
1746 raise util.Abort(_('option "-r" not valid when importing '
1746 raise util.Abort(_('option "-r" not valid when importing '
1747 'files'))
1747 'files'))
1748 rev = scmutil.revrange(repo, rev)
1748 rev = scmutil.revrange(repo, rev)
1749 rev.sort(reverse=True)
1749 rev.sort(reverse=True)
1750 if (len(files) > 1 or len(rev) > 1) and patchname:
1750 if (len(files) > 1 or len(rev) > 1) and patchname:
1751 raise util.Abort(_('option "-n" not valid when importing multiple '
1751 raise util.Abort(_('option "-n" not valid when importing multiple '
1752 'patches'))
1752 'patches'))
1753 if rev:
1753 if rev:
1754 # If mq patches are applied, we can only import revisions
1754 # If mq patches are applied, we can only import revisions
1755 # that form a linear path to qbase.
1755 # that form a linear path to qbase.
1756 # Otherwise, they should form a linear path to a head.
1756 # Otherwise, they should form a linear path to a head.
1757 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1757 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1758 if len(heads) > 1:
1758 if len(heads) > 1:
1759 raise util.Abort(_('revision %d is the root of more than one '
1759 raise util.Abort(_('revision %d is the root of more than one '
1760 'branch') % rev[-1])
1760 'branch') % rev[-1])
1761 if self.applied:
1761 if self.applied:
1762 base = repo.changelog.node(rev[0])
1762 base = repo.changelog.node(rev[0])
1763 if base in [n.node for n in self.applied]:
1763 if base in [n.node for n in self.applied]:
1764 raise util.Abort(_('revision %d is already managed')
1764 raise util.Abort(_('revision %d is already managed')
1765 % rev[0])
1765 % rev[0])
1766 if heads != [self.applied[-1].node]:
1766 if heads != [self.applied[-1].node]:
1767 raise util.Abort(_('revision %d is not the parent of '
1767 raise util.Abort(_('revision %d is not the parent of '
1768 'the queue') % rev[0])
1768 'the queue') % rev[0])
1769 base = repo.changelog.rev(self.applied[0].node)
1769 base = repo.changelog.rev(self.applied[0].node)
1770 lastparent = repo.changelog.parentrevs(base)[0]
1770 lastparent = repo.changelog.parentrevs(base)[0]
1771 else:
1771 else:
1772 if heads != [repo.changelog.node(rev[0])]:
1772 if heads != [repo.changelog.node(rev[0])]:
1773 raise util.Abort(_('revision %d has unmanaged children')
1773 raise util.Abort(_('revision %d has unmanaged children')
1774 % rev[0])
1774 % rev[0])
1775 lastparent = None
1775 lastparent = None
1776
1776
1777 diffopts = self.diffopts({'git': git})
1777 diffopts = self.diffopts({'git': git})
1778 for r in rev:
1778 for r in rev:
1779 p1, p2 = repo.changelog.parentrevs(r)
1779 p1, p2 = repo.changelog.parentrevs(r)
1780 n = repo.changelog.node(r)
1780 n = repo.changelog.node(r)
1781 if p2 != nullrev:
1781 if p2 != nullrev:
1782 raise util.Abort(_('cannot import merge revision %d') % r)
1782 raise util.Abort(_('cannot import merge revision %d') % r)
1783 if lastparent and lastparent != r:
1783 if lastparent and lastparent != r:
1784 raise util.Abort(_('revision %d is not the parent of %d')
1784 raise util.Abort(_('revision %d is not the parent of %d')
1785 % (r, lastparent))
1785 % (r, lastparent))
1786 lastparent = p1
1786 lastparent = p1
1787
1787
1788 if not patchname:
1788 if not patchname:
1789 patchname = normname('%d.diff' % r)
1789 patchname = normname('%d.diff' % r)
1790 checkseries(patchname)
1790 checkseries(patchname)
1791 self.checkpatchname(patchname, force)
1791 self.checkpatchname(patchname, force)
1792 self.fullseries.insert(0, patchname)
1792 self.fullseries.insert(0, patchname)
1793
1793
1794 patchf = self.opener(patchname, "w")
1794 patchf = self.opener(patchname, "w")
1795 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1795 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1796 patchf.close()
1796 patchf.close()
1797
1797
1798 se = statusentry(n, patchname)
1798 se = statusentry(n, patchname)
1799 self.applied.insert(0, se)
1799 self.applied.insert(0, se)
1800
1800
1801 self.added.append(patchname)
1801 self.added.append(patchname)
1802 patchname = None
1802 patchname = None
1803 self.parseseries()
1803 self.parseseries()
1804 self.applieddirty = 1
1804 self.applieddirty = 1
1805 self.seriesdirty = True
1805 self.seriesdirty = True
1806
1806
1807 for i, filename in enumerate(files):
1807 for i, filename in enumerate(files):
1808 if existing:
1808 if existing:
1809 if filename == '-':
1809 if filename == '-':
1810 raise util.Abort(_('-e is incompatible with import from -'))
1810 raise util.Abort(_('-e is incompatible with import from -'))
1811 filename = normname(filename)
1811 filename = normname(filename)
1812 self.checkreservedname(filename)
1812 self.checkreservedname(filename)
1813 originpath = self.join(filename)
1813 originpath = self.join(filename)
1814 if not os.path.isfile(originpath):
1814 if not os.path.isfile(originpath):
1815 raise util.Abort(_("patch %s does not exist") % filename)
1815 raise util.Abort(_("patch %s does not exist") % filename)
1816
1816
1817 if patchname:
1817 if patchname:
1818 self.checkpatchname(patchname, force)
1818 self.checkpatchname(patchname, force)
1819
1819
1820 self.ui.write(_('renaming %s to %s\n')
1820 self.ui.write(_('renaming %s to %s\n')
1821 % (filename, patchname))
1821 % (filename, patchname))
1822 util.rename(originpath, self.join(patchname))
1822 util.rename(originpath, self.join(patchname))
1823 else:
1823 else:
1824 patchname = filename
1824 patchname = filename
1825
1825
1826 else:
1826 else:
1827 if filename == '-' and not patchname:
1827 if filename == '-' and not patchname:
1828 raise util.Abort(_('need --name to import a patch from -'))
1828 raise util.Abort(_('need --name to import a patch from -'))
1829 elif not patchname:
1829 elif not patchname:
1830 patchname = normname(os.path.basename(filename.rstrip('/')))
1830 patchname = normname(os.path.basename(filename.rstrip('/')))
1831 self.checkpatchname(patchname, force)
1831 self.checkpatchname(patchname, force)
1832 try:
1832 try:
1833 if filename == '-':
1833 if filename == '-':
1834 text = self.ui.fin.read()
1834 text = self.ui.fin.read()
1835 else:
1835 else:
1836 fp = url.open(self.ui, filename)
1836 fp = url.open(self.ui, filename)
1837 text = fp.read()
1837 text = fp.read()
1838 fp.close()
1838 fp.close()
1839 except (OSError, IOError):
1839 except (OSError, IOError):
1840 raise util.Abort(_("unable to read file %s") % filename)
1840 raise util.Abort(_("unable to read file %s") % filename)
1841 patchf = self.opener(patchname, "w")
1841 patchf = self.opener(patchname, "w")
1842 patchf.write(text)
1842 patchf.write(text)
1843 patchf.close()
1843 patchf.close()
1844 if not force:
1844 if not force:
1845 checkseries(patchname)
1845 checkseries(patchname)
1846 if patchname not in self.series:
1846 if patchname not in self.series:
1847 index = self.fullseriesend() + i
1847 index = self.fullseriesend() + i
1848 self.fullseries[index:index] = [patchname]
1848 self.fullseries[index:index] = [patchname]
1849 self.parseseries()
1849 self.parseseries()
1850 self.seriesdirty = True
1850 self.seriesdirty = True
1851 self.ui.warn(_("adding %s to series file\n") % patchname)
1851 self.ui.warn(_("adding %s to series file\n") % patchname)
1852 self.added.append(patchname)
1852 self.added.append(patchname)
1853 patchname = None
1853 patchname = None
1854
1854
1855 self.removeundo(repo)
1855 self.removeundo(repo)
1856
1856
1857 @command("qdelete|qremove|qrm",
1857 @command("qdelete|qremove|qrm",
1858 [('k', 'keep', None, _('keep patch file')),
1858 [('k', 'keep', None, _('keep patch file')),
1859 ('r', 'rev', [],
1859 ('r', 'rev', [],
1860 _('stop managing a revision (DEPRECATED)'), _('REV'))],
1860 _('stop managing a revision (DEPRECATED)'), _('REV'))],
1861 _('hg qdelete [-k] [PATCH]...'))
1861 _('hg qdelete [-k] [PATCH]...'))
1862 def delete(ui, repo, *patches, **opts):
1862 def delete(ui, repo, *patches, **opts):
1863 """remove patches from queue
1863 """remove patches from queue
1864
1864
1865 The patches must not be applied, and at least one patch is required. With
1865 The patches must not be applied, and at least one patch is required. With
1866 -k/--keep, the patch files are preserved in the patch directory.
1866 -k/--keep, the patch files are preserved in the patch directory.
1867
1867
1868 To stop managing a patch and move it into permanent history,
1868 To stop managing a patch and move it into permanent history,
1869 use the :hg:`qfinish` command."""
1869 use the :hg:`qfinish` command."""
1870 q = repo.mq
1870 q = repo.mq
1871 q.delete(repo, patches, opts)
1871 q.delete(repo, patches, opts)
1872 q.savedirty()
1872 q.savedirty()
1873 return 0
1873 return 0
1874
1874
1875 @command("qapplied",
1875 @command("qapplied",
1876 [('1', 'last', None, _('show only the last patch'))
1876 [('1', 'last', None, _('show only the last patch'))
1877 ] + seriesopts,
1877 ] + seriesopts,
1878 _('hg qapplied [-1] [-s] [PATCH]'))
1878 _('hg qapplied [-1] [-s] [PATCH]'))
1879 def applied(ui, repo, patch=None, **opts):
1879 def applied(ui, repo, patch=None, **opts):
1880 """print the patches already applied
1880 """print the patches already applied
1881
1881
1882 Returns 0 on success."""
1882 Returns 0 on success."""
1883
1883
1884 q = repo.mq
1884 q = repo.mq
1885
1885
1886 if patch:
1886 if patch:
1887 if patch not in q.series:
1887 if patch not in q.series:
1888 raise util.Abort(_("patch %s is not in series file") % patch)
1888 raise util.Abort(_("patch %s is not in series file") % patch)
1889 end = q.series.index(patch) + 1
1889 end = q.series.index(patch) + 1
1890 else:
1890 else:
1891 end = q.seriesend(True)
1891 end = q.seriesend(True)
1892
1892
1893 if opts.get('last') and not end:
1893 if opts.get('last') and not end:
1894 ui.write(_("no patches applied\n"))
1894 ui.write(_("no patches applied\n"))
1895 return 1
1895 return 1
1896 elif opts.get('last') and end == 1:
1896 elif opts.get('last') and end == 1:
1897 ui.write(_("only one patch applied\n"))
1897 ui.write(_("only one patch applied\n"))
1898 return 1
1898 return 1
1899 elif opts.get('last'):
1899 elif opts.get('last'):
1900 start = end - 2
1900 start = end - 2
1901 end = 1
1901 end = 1
1902 else:
1902 else:
1903 start = 0
1903 start = 0
1904
1904
1905 q.qseries(repo, length=end, start=start, status='A',
1905 q.qseries(repo, length=end, start=start, status='A',
1906 summary=opts.get('summary'))
1906 summary=opts.get('summary'))
1907
1907
1908
1908
1909 @command("qunapplied",
1909 @command("qunapplied",
1910 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
1910 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
1911 _('hg qunapplied [-1] [-s] [PATCH]'))
1911 _('hg qunapplied [-1] [-s] [PATCH]'))
1912 def unapplied(ui, repo, patch=None, **opts):
1912 def unapplied(ui, repo, patch=None, **opts):
1913 """print the patches not yet applied
1913 """print the patches not yet applied
1914
1914
1915 Returns 0 on success."""
1915 Returns 0 on success."""
1916
1916
1917 q = repo.mq
1917 q = repo.mq
1918 if patch:
1918 if patch:
1919 if patch not in q.series:
1919 if patch not in q.series:
1920 raise util.Abort(_("patch %s is not in series file") % patch)
1920 raise util.Abort(_("patch %s is not in series file") % patch)
1921 start = q.series.index(patch) + 1
1921 start = q.series.index(patch) + 1
1922 else:
1922 else:
1923 start = q.seriesend(True)
1923 start = q.seriesend(True)
1924
1924
1925 if start == len(q.series) and opts.get('first'):
1925 if start == len(q.series) and opts.get('first'):
1926 ui.write(_("all patches applied\n"))
1926 ui.write(_("all patches applied\n"))
1927 return 1
1927 return 1
1928
1928
1929 length = opts.get('first') and 1 or None
1929 length = opts.get('first') and 1 or None
1930 q.qseries(repo, start=start, length=length, status='U',
1930 q.qseries(repo, start=start, length=length, status='U',
1931 summary=opts.get('summary'))
1931 summary=opts.get('summary'))
1932
1932
1933 @command("qimport",
1933 @command("qimport",
1934 [('e', 'existing', None, _('import file in patch directory')),
1934 [('e', 'existing', None, _('import file in patch directory')),
1935 ('n', 'name', '',
1935 ('n', 'name', '',
1936 _('name of patch file'), _('NAME')),
1936 _('name of patch file'), _('NAME')),
1937 ('f', 'force', None, _('overwrite existing files')),
1937 ('f', 'force', None, _('overwrite existing files')),
1938 ('r', 'rev', [],
1938 ('r', 'rev', [],
1939 _('place existing revisions under mq control'), _('REV')),
1939 _('place existing revisions under mq control'), _('REV')),
1940 ('g', 'git', None, _('use git extended diff format')),
1940 ('g', 'git', None, _('use git extended diff format')),
1941 ('P', 'push', None, _('qpush after importing'))],
1941 ('P', 'push', None, _('qpush after importing'))],
1942 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...'))
1942 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...'))
1943 def qimport(ui, repo, *filename, **opts):
1943 def qimport(ui, repo, *filename, **opts):
1944 """import a patch
1944 """import a patch
1945
1945
1946 The patch is inserted into the series after the last applied
1946 The patch is inserted into the series after the last applied
1947 patch. If no patches have been applied, qimport prepends the patch
1947 patch. If no patches have been applied, qimport prepends the patch
1948 to the series.
1948 to the series.
1949
1949
1950 The patch will have the same name as its source file unless you
1950 The patch will have the same name as its source file unless you
1951 give it a new one with -n/--name.
1951 give it a new one with -n/--name.
1952
1952
1953 You can register an existing patch inside the patch directory with
1953 You can register an existing patch inside the patch directory with
1954 the -e/--existing flag.
1954 the -e/--existing flag.
1955
1955
1956 With -f/--force, an existing patch of the same name will be
1956 With -f/--force, an existing patch of the same name will be
1957 overwritten.
1957 overwritten.
1958
1958
1959 An existing changeset may be placed under mq control with -r/--rev
1959 An existing changeset may be placed under mq control with -r/--rev
1960 (e.g. qimport --rev tip -n patch will place tip under mq control).
1960 (e.g. qimport --rev tip -n patch will place tip under mq control).
1961 With -g/--git, patches imported with --rev will use the git diff
1961 With -g/--git, patches imported with --rev will use the git diff
1962 format. See the diffs help topic for information on why this is
1962 format. See the diffs help topic for information on why this is
1963 important for preserving rename/copy information and permission
1963 important for preserving rename/copy information and permission
1964 changes. Use :hg:`qfinish` to remove changesets from mq control.
1964 changes. Use :hg:`qfinish` to remove changesets from mq control.
1965
1965
1966 To import a patch from standard input, pass - as the patch file.
1966 To import a patch from standard input, pass - as the patch file.
1967 When importing from standard input, a patch name must be specified
1967 When importing from standard input, a patch name must be specified
1968 using the --name flag.
1968 using the --name flag.
1969
1969
1970 To import an existing patch while renaming it::
1970 To import an existing patch while renaming it::
1971
1971
1972 hg qimport -e existing-patch -n new-name
1972 hg qimport -e existing-patch -n new-name
1973
1973
1974 Returns 0 if import succeeded.
1974 Returns 0 if import succeeded.
1975 """
1975 """
1976 q = repo.mq
1976 q = repo.mq
1977 try:
1977 try:
1978 q.qimport(repo, filename, patchname=opts.get('name'),
1978 q.qimport(repo, filename, patchname=opts.get('name'),
1979 existing=opts.get('existing'), force=opts.get('force'),
1979 existing=opts.get('existing'), force=opts.get('force'),
1980 rev=opts.get('rev'), git=opts.get('git'))
1980 rev=opts.get('rev'), git=opts.get('git'))
1981 finally:
1981 finally:
1982 q.savedirty()
1982 q.savedirty()
1983
1983
1984 if opts.get('push') and not opts.get('rev'):
1984 if opts.get('push') and not opts.get('rev'):
1985 return q.push(repo, None)
1985 return q.push(repo, None)
1986 return 0
1986 return 0
1987
1987
1988 def qinit(ui, repo, create):
1988 def qinit(ui, repo, create):
1989 """initialize a new queue repository
1989 """initialize a new queue repository
1990
1990
1991 This command also creates a series file for ordering patches, and
1991 This command also creates a series file for ordering patches, and
1992 an mq-specific .hgignore file in the queue repository, to exclude
1992 an mq-specific .hgignore file in the queue repository, to exclude
1993 the status and guards files (these contain mostly transient state).
1993 the status and guards files (these contain mostly transient state).
1994
1994
1995 Returns 0 if initialization succeeded."""
1995 Returns 0 if initialization succeeded."""
1996 q = repo.mq
1996 q = repo.mq
1997 r = q.init(repo, create)
1997 r = q.init(repo, create)
1998 q.savedirty()
1998 q.savedirty()
1999 if r:
1999 if r:
2000 if not os.path.exists(r.wjoin('.hgignore')):
2000 if not os.path.exists(r.wjoin('.hgignore')):
2001 fp = r.wopener('.hgignore', 'w')
2001 fp = r.wopener('.hgignore', 'w')
2002 fp.write('^\\.hg\n')
2002 fp.write('^\\.hg\n')
2003 fp.write('^\\.mq\n')
2003 fp.write('^\\.mq\n')
2004 fp.write('syntax: glob\n')
2004 fp.write('syntax: glob\n')
2005 fp.write('status\n')
2005 fp.write('status\n')
2006 fp.write('guards\n')
2006 fp.write('guards\n')
2007 fp.close()
2007 fp.close()
2008 if not os.path.exists(r.wjoin('series')):
2008 if not os.path.exists(r.wjoin('series')):
2009 r.wopener('series', 'w').close()
2009 r.wopener('series', 'w').close()
2010 r[None].add(['.hgignore', 'series'])
2010 r[None].add(['.hgignore', 'series'])
2011 commands.add(ui, r)
2011 commands.add(ui, r)
2012 return 0
2012 return 0
2013
2013
2014 @command("^qinit",
2014 @command("^qinit",
2015 [('c', 'create-repo', None, _('create queue repository'))],
2015 [('c', 'create-repo', None, _('create queue repository'))],
2016 _('hg qinit [-c]'))
2016 _('hg qinit [-c]'))
2017 def init(ui, repo, **opts):
2017 def init(ui, repo, **opts):
2018 """init a new queue repository (DEPRECATED)
2018 """init a new queue repository (DEPRECATED)
2019
2019
2020 The queue repository is unversioned by default. If
2020 The queue repository is unversioned by default. If
2021 -c/--create-repo is specified, qinit will create a separate nested
2021 -c/--create-repo is specified, qinit will create a separate nested
2022 repository for patches (qinit -c may also be run later to convert
2022 repository for patches (qinit -c may also be run later to convert
2023 an unversioned patch repository into a versioned one). You can use
2023 an unversioned patch repository into a versioned one). You can use
2024 qcommit to commit changes to this queue repository.
2024 qcommit to commit changes to this queue repository.
2025
2025
2026 This command is deprecated. Without -c, it's implied by other relevant
2026 This command is deprecated. Without -c, it's implied by other relevant
2027 commands. With -c, use :hg:`init --mq` instead."""
2027 commands. With -c, use :hg:`init --mq` instead."""
2028 return qinit(ui, repo, create=opts.get('create_repo'))
2028 return qinit(ui, repo, create=opts.get('create_repo'))
2029
2029
2030 @command("qclone",
2030 @command("qclone",
2031 [('', 'pull', None, _('use pull protocol to copy metadata')),
2031 [('', 'pull', None, _('use pull protocol to copy metadata')),
2032 ('U', 'noupdate', None, _('do not update the new working directories')),
2032 ('U', 'noupdate', None, _('do not update the new working directories')),
2033 ('', 'uncompressed', None,
2033 ('', 'uncompressed', None,
2034 _('use uncompressed transfer (fast over LAN)')),
2034 _('use uncompressed transfer (fast over LAN)')),
2035 ('p', 'patches', '',
2035 ('p', 'patches', '',
2036 _('location of source patch repository'), _('REPO')),
2036 _('location of source patch repository'), _('REPO')),
2037 ] + commands.remoteopts,
2037 ] + commands.remoteopts,
2038 _('hg qclone [OPTION]... SOURCE [DEST]'))
2038 _('hg qclone [OPTION]... SOURCE [DEST]'))
2039 def clone(ui, source, dest=None, **opts):
2039 def clone(ui, source, dest=None, **opts):
2040 '''clone main and patch repository at same time
2040 '''clone main and patch repository at same time
2041
2041
2042 If source is local, destination will have no patches applied. If
2042 If source is local, destination will have no patches applied. If
2043 source is remote, this command can not check if patches are
2043 source is remote, this command can not check if patches are
2044 applied in source, so cannot guarantee that patches are not
2044 applied in source, so cannot guarantee that patches are not
2045 applied in destination. If you clone remote repository, be sure
2045 applied in destination. If you clone remote repository, be sure
2046 before that it has no patches applied.
2046 before that it has no patches applied.
2047
2047
2048 Source patch repository is looked for in <src>/.hg/patches by
2048 Source patch repository is looked for in <src>/.hg/patches by
2049 default. Use -p <url> to change.
2049 default. Use -p <url> to change.
2050
2050
2051 The patch directory must be a nested Mercurial repository, as
2051 The patch directory must be a nested Mercurial repository, as
2052 would be created by :hg:`init --mq`.
2052 would be created by :hg:`init --mq`.
2053
2053
2054 Return 0 on success.
2054 Return 0 on success.
2055 '''
2055 '''
2056 def patchdir(repo):
2056 def patchdir(repo):
2057 url = repo.url()
2057 url = repo.url()
2058 if url.endswith('/'):
2058 if url.endswith('/'):
2059 url = url[:-1]
2059 url = url[:-1]
2060 return url + '/.hg/patches'
2060 return url + '/.hg/patches'
2061 if dest is None:
2061 if dest is None:
2062 dest = hg.defaultdest(source)
2062 dest = hg.defaultdest(source)
2063 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
2063 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
2064 if opts.get('patches'):
2064 if opts.get('patches'):
2065 patchespath = ui.expandpath(opts.get('patches'))
2065 patchespath = ui.expandpath(opts.get('patches'))
2066 else:
2066 else:
2067 patchespath = patchdir(sr)
2067 patchespath = patchdir(sr)
2068 try:
2068 try:
2069 hg.repository(ui, patchespath)
2069 hg.repository(ui, patchespath)
2070 except error.RepoError:
2070 except error.RepoError:
2071 raise util.Abort(_('versioned patch repository not found'
2071 raise util.Abort(_('versioned patch repository not found'
2072 ' (see init --mq)'))
2072 ' (see init --mq)'))
2073 qbase, destrev = None, None
2073 qbase, destrev = None, None
2074 if sr.local():
2074 if sr.local():
2075 if sr.mq.applied:
2075 if sr.mq.applied:
2076 qbase = sr.mq.applied[0].node
2076 qbase = sr.mq.applied[0].node
2077 if not hg.islocal(dest):
2077 if not hg.islocal(dest):
2078 heads = set(sr.heads())
2078 heads = set(sr.heads())
2079 destrev = list(heads.difference(sr.heads(qbase)))
2079 destrev = list(heads.difference(sr.heads(qbase)))
2080 destrev.append(sr.changelog.parents(qbase)[0])
2080 destrev.append(sr.changelog.parents(qbase)[0])
2081 elif sr.capable('lookup'):
2081 elif sr.capable('lookup'):
2082 try:
2082 try:
2083 qbase = sr.lookup('qbase')
2083 qbase = sr.lookup('qbase')
2084 except error.RepoError:
2084 except error.RepoError:
2085 pass
2085 pass
2086 ui.note(_('cloning main repository\n'))
2086 ui.note(_('cloning main repository\n'))
2087 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2087 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2088 pull=opts.get('pull'),
2088 pull=opts.get('pull'),
2089 rev=destrev,
2089 rev=destrev,
2090 update=False,
2090 update=False,
2091 stream=opts.get('uncompressed'))
2091 stream=opts.get('uncompressed'))
2092 ui.note(_('cloning patch repository\n'))
2092 ui.note(_('cloning patch repository\n'))
2093 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2093 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2094 pull=opts.get('pull'), update=not opts.get('noupdate'),
2094 pull=opts.get('pull'), update=not opts.get('noupdate'),
2095 stream=opts.get('uncompressed'))
2095 stream=opts.get('uncompressed'))
2096 if dr.local():
2096 if dr.local():
2097 if qbase:
2097 if qbase:
2098 ui.note(_('stripping applied patches from destination '
2098 ui.note(_('stripping applied patches from destination '
2099 'repository\n'))
2099 'repository\n'))
2100 dr.mq.strip(dr, [qbase], update=False, backup=None)
2100 dr.mq.strip(dr, [qbase], update=False, backup=None)
2101 if not opts.get('noupdate'):
2101 if not opts.get('noupdate'):
2102 ui.note(_('updating destination repository\n'))
2102 ui.note(_('updating destination repository\n'))
2103 hg.update(dr, dr.changelog.tip())
2103 hg.update(dr, dr.changelog.tip())
2104
2104
2105 @command("qcommit|qci",
2105 @command("qcommit|qci",
2106 commands.table["^commit|ci"][1],
2106 commands.table["^commit|ci"][1],
2107 _('hg qcommit [OPTION]... [FILE]...'))
2107 _('hg qcommit [OPTION]... [FILE]...'))
2108 def commit(ui, repo, *pats, **opts):
2108 def commit(ui, repo, *pats, **opts):
2109 """commit changes in the queue repository (DEPRECATED)
2109 """commit changes in the queue repository (DEPRECATED)
2110
2110
2111 This command is deprecated; use :hg:`commit --mq` instead."""
2111 This command is deprecated; use :hg:`commit --mq` instead."""
2112 q = repo.mq
2112 q = repo.mq
2113 r = q.qrepo()
2113 r = q.qrepo()
2114 if not r:
2114 if not r:
2115 raise util.Abort('no queue repository')
2115 raise util.Abort('no queue repository')
2116 commands.commit(r.ui, r, *pats, **opts)
2116 commands.commit(r.ui, r, *pats, **opts)
2117
2117
2118 @command("qseries",
2118 @command("qseries",
2119 [('m', 'missing', None, _('print patches not in series')),
2119 [('m', 'missing', None, _('print patches not in series')),
2120 ] + seriesopts,
2120 ] + seriesopts,
2121 _('hg qseries [-ms]'))
2121 _('hg qseries [-ms]'))
2122 def series(ui, repo, **opts):
2122 def series(ui, repo, **opts):
2123 """print the entire series file
2123 """print the entire series file
2124
2124
2125 Returns 0 on success."""
2125 Returns 0 on success."""
2126 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2126 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2127 return 0
2127 return 0
2128
2128
2129 @command("qtop", seriesopts, _('hg qtop [-s]'))
2129 @command("qtop", seriesopts, _('hg qtop [-s]'))
2130 def top(ui, repo, **opts):
2130 def top(ui, repo, **opts):
2131 """print the name of the current patch
2131 """print the name of the current patch
2132
2132
2133 Returns 0 on success."""
2133 Returns 0 on success."""
2134 q = repo.mq
2134 q = repo.mq
2135 t = q.applied and q.seriesend(True) or 0
2135 t = q.applied and q.seriesend(True) or 0
2136 if t:
2136 if t:
2137 q.qseries(repo, start=t - 1, length=1, status='A',
2137 q.qseries(repo, start=t - 1, length=1, status='A',
2138 summary=opts.get('summary'))
2138 summary=opts.get('summary'))
2139 else:
2139 else:
2140 ui.write(_("no patches applied\n"))
2140 ui.write(_("no patches applied\n"))
2141 return 1
2141 return 1
2142
2142
2143 @command("qnext", seriesopts, _('hg qnext [-s]'))
2143 @command("qnext", seriesopts, _('hg qnext [-s]'))
2144 def next(ui, repo, **opts):
2144 def next(ui, repo, **opts):
2145 """print the name of the next patch
2145 """print the name of the next patch
2146
2146
2147 Returns 0 on success."""
2147 Returns 0 on success."""
2148 q = repo.mq
2148 q = repo.mq
2149 end = q.seriesend()
2149 end = q.seriesend()
2150 if end == len(q.series):
2150 if end == len(q.series):
2151 ui.write(_("all patches applied\n"))
2151 ui.write(_("all patches applied\n"))
2152 return 1
2152 return 1
2153 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2153 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2154
2154
2155 @command("qprev", seriesopts, _('hg qprev [-s]'))
2155 @command("qprev", seriesopts, _('hg qprev [-s]'))
2156 def prev(ui, repo, **opts):
2156 def prev(ui, repo, **opts):
2157 """print the name of the previous patch
2157 """print the name of the previous patch
2158
2158
2159 Returns 0 on success."""
2159 Returns 0 on success."""
2160 q = repo.mq
2160 q = repo.mq
2161 l = len(q.applied)
2161 l = len(q.applied)
2162 if l == 1:
2162 if l == 1:
2163 ui.write(_("only one patch applied\n"))
2163 ui.write(_("only one patch applied\n"))
2164 return 1
2164 return 1
2165 if not l:
2165 if not l:
2166 ui.write(_("no patches applied\n"))
2166 ui.write(_("no patches applied\n"))
2167 return 1
2167 return 1
2168 q.qseries(repo, start=l - 2, length=1, status='A',
2168 q.qseries(repo, start=l - 2, length=1, status='A',
2169 summary=opts.get('summary'))
2169 summary=opts.get('summary'))
2170
2170
2171 def setupheaderopts(ui, opts):
2171 def setupheaderopts(ui, opts):
2172 if not opts.get('user') and opts.get('currentuser'):
2172 if not opts.get('user') and opts.get('currentuser'):
2173 opts['user'] = ui.username()
2173 opts['user'] = ui.username()
2174 if not opts.get('date') and opts.get('currentdate'):
2174 if not opts.get('date') and opts.get('currentdate'):
2175 opts['date'] = "%d %d" % util.makedate()
2175 opts['date'] = "%d %d" % util.makedate()
2176
2176
2177 @command("^qnew",
2177 @command("^qnew",
2178 [('e', 'edit', None, _('edit commit message')),
2178 [('e', 'edit', None, _('edit commit message')),
2179 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2179 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2180 ('g', 'git', None, _('use git extended diff format')),
2180 ('g', 'git', None, _('use git extended diff format')),
2181 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2181 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2182 ('u', 'user', '',
2182 ('u', 'user', '',
2183 _('add "From: <USER>" to patch'), _('USER')),
2183 _('add "From: <USER>" to patch'), _('USER')),
2184 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2184 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2185 ('d', 'date', '',
2185 ('d', 'date', '',
2186 _('add "Date: <DATE>" to patch'), _('DATE'))
2186 _('add "Date: <DATE>" to patch'), _('DATE'))
2187 ] + commands.walkopts + commands.commitopts,
2187 ] + commands.walkopts + commands.commitopts,
2188 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2188 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2189 def new(ui, repo, patch, *args, **opts):
2189 def new(ui, repo, patch, *args, **opts):
2190 """create a new patch
2190 """create a new patch
2191
2191
2192 qnew creates a new patch on top of the currently-applied patch (if
2192 qnew creates a new patch on top of the currently-applied patch (if
2193 any). The patch will be initialized with any outstanding changes
2193 any). The patch will be initialized with any outstanding changes
2194 in the working directory. You may also use -I/--include,
2194 in the working directory. You may also use -I/--include,
2195 -X/--exclude, and/or a list of files after the patch name to add
2195 -X/--exclude, and/or a list of files after the patch name to add
2196 only changes to matching files to the new patch, leaving the rest
2196 only changes to matching files to the new patch, leaving the rest
2197 as uncommitted modifications.
2197 as uncommitted modifications.
2198
2198
2199 -u/--user and -d/--date can be used to set the (given) user and
2199 -u/--user and -d/--date can be used to set the (given) user and
2200 date, respectively. -U/--currentuser and -D/--currentdate set user
2200 date, respectively. -U/--currentuser and -D/--currentdate set user
2201 to current user and date to current date.
2201 to current user and date to current date.
2202
2202
2203 -e/--edit, -m/--message or -l/--logfile set the patch header as
2203 -e/--edit, -m/--message or -l/--logfile set the patch header as
2204 well as the commit message. If none is specified, the header is
2204 well as the commit message. If none is specified, the header is
2205 empty and the commit message is '[mq]: PATCH'.
2205 empty and the commit message is '[mq]: PATCH'.
2206
2206
2207 Use the -g/--git option to keep the patch in the git extended diff
2207 Use the -g/--git option to keep the patch in the git extended diff
2208 format. Read the diffs help topic for more information on why this
2208 format. Read the diffs help topic for more information on why this
2209 is important for preserving permission changes and copy/rename
2209 is important for preserving permission changes and copy/rename
2210 information.
2210 information.
2211
2211
2212 Returns 0 on successful creation of a new patch.
2212 Returns 0 on successful creation of a new patch.
2213 """
2213 """
2214 msg = cmdutil.logmessage(ui, opts)
2214 msg = cmdutil.logmessage(ui, opts)
2215 def getmsg():
2215 def getmsg():
2216 return ui.edit(msg, opts.get('user') or ui.username())
2216 return ui.edit(msg, opts.get('user') or ui.username())
2217 q = repo.mq
2217 q = repo.mq
2218 opts['msg'] = msg
2218 opts['msg'] = msg
2219 if opts.get('edit'):
2219 if opts.get('edit'):
2220 opts['msg'] = getmsg
2220 opts['msg'] = getmsg
2221 else:
2221 else:
2222 opts['msg'] = msg
2222 opts['msg'] = msg
2223 setupheaderopts(ui, opts)
2223 setupheaderopts(ui, opts)
2224 q.new(repo, patch, *args, **opts)
2224 q.new(repo, patch, *args, **opts)
2225 q.savedirty()
2225 q.savedirty()
2226 return 0
2226 return 0
2227
2227
2228 @command("^qrefresh",
2228 @command("^qrefresh",
2229 [('e', 'edit', None, _('edit commit message')),
2229 [('e', 'edit', None, _('edit commit message')),
2230 ('g', 'git', None, _('use git extended diff format')),
2230 ('g', 'git', None, _('use git extended diff format')),
2231 ('s', 'short', None,
2231 ('s', 'short', None,
2232 _('refresh only files already in the patch and specified files')),
2232 _('refresh only files already in the patch and specified files')),
2233 ('U', 'currentuser', None,
2233 ('U', 'currentuser', None,
2234 _('add/update author field in patch with current user')),
2234 _('add/update author field in patch with current user')),
2235 ('u', 'user', '',
2235 ('u', 'user', '',
2236 _('add/update author field in patch with given user'), _('USER')),
2236 _('add/update author field in patch with given user'), _('USER')),
2237 ('D', 'currentdate', None,
2237 ('D', 'currentdate', None,
2238 _('add/update date field in patch with current date')),
2238 _('add/update date field in patch with current date')),
2239 ('d', 'date', '',
2239 ('d', 'date', '',
2240 _('add/update date field in patch with given date'), _('DATE'))
2240 _('add/update date field in patch with given date'), _('DATE'))
2241 ] + commands.walkopts + commands.commitopts,
2241 ] + commands.walkopts + commands.commitopts,
2242 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2242 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2243 def refresh(ui, repo, *pats, **opts):
2243 def refresh(ui, repo, *pats, **opts):
2244 """update the current patch
2244 """update the current patch
2245
2245
2246 If any file patterns are provided, the refreshed patch will
2246 If any file patterns are provided, the refreshed patch will
2247 contain only the modifications that match those patterns; the
2247 contain only the modifications that match those patterns; the
2248 remaining modifications will remain in the working directory.
2248 remaining modifications will remain in the working directory.
2249
2249
2250 If -s/--short is specified, files currently included in the patch
2250 If -s/--short is specified, files currently included in the patch
2251 will be refreshed just like matched files and remain in the patch.
2251 will be refreshed just like matched files and remain in the patch.
2252
2252
2253 If -e/--edit is specified, Mercurial will start your configured editor for
2253 If -e/--edit is specified, Mercurial will start your configured editor for
2254 you to enter a message. In case qrefresh fails, you will find a backup of
2254 you to enter a message. In case qrefresh fails, you will find a backup of
2255 your message in ``.hg/last-message.txt``.
2255 your message in ``.hg/last-message.txt``.
2256
2256
2257 hg add/remove/copy/rename work as usual, though you might want to
2257 hg add/remove/copy/rename work as usual, though you might want to
2258 use git-style patches (-g/--git or [diff] git=1) to track copies
2258 use git-style patches (-g/--git or [diff] git=1) to track copies
2259 and renames. See the diffs help topic for more information on the
2259 and renames. See the diffs help topic for more information on the
2260 git diff format.
2260 git diff format.
2261
2261
2262 Returns 0 on success.
2262 Returns 0 on success.
2263 """
2263 """
2264 q = repo.mq
2264 q = repo.mq
2265 message = cmdutil.logmessage(ui, opts)
2265 message = cmdutil.logmessage(ui, opts)
2266 if opts.get('edit'):
2266 if opts.get('edit'):
2267 if not q.applied:
2267 if not q.applied:
2268 ui.write(_("no patches applied\n"))
2268 ui.write(_("no patches applied\n"))
2269 return 1
2269 return 1
2270 if message:
2270 if message:
2271 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2271 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2272 patch = q.applied[-1].name
2272 patch = q.applied[-1].name
2273 ph = patchheader(q.join(patch), q.plainmode)
2273 ph = patchheader(q.join(patch), q.plainmode)
2274 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2274 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2275 # We don't want to lose the patch message if qrefresh fails (issue2062)
2275 # We don't want to lose the patch message if qrefresh fails (issue2062)
2276 repo.savecommitmessage(message)
2276 repo.savecommitmessage(message)
2277 setupheaderopts(ui, opts)
2277 setupheaderopts(ui, opts)
2278 wlock = repo.wlock()
2278 wlock = repo.wlock()
2279 try:
2279 try:
2280 ret = q.refresh(repo, pats, msg=message, **opts)
2280 ret = q.refresh(repo, pats, msg=message, **opts)
2281 q.savedirty()
2281 q.savedirty()
2282 return ret
2282 return ret
2283 finally:
2283 finally:
2284 wlock.release()
2284 wlock.release()
2285
2285
2286 @command("^qdiff",
2286 @command("^qdiff",
2287 commands.diffopts + commands.diffopts2 + commands.walkopts,
2287 commands.diffopts + commands.diffopts2 + commands.walkopts,
2288 _('hg qdiff [OPTION]... [FILE]...'))
2288 _('hg qdiff [OPTION]... [FILE]...'))
2289 def diff(ui, repo, *pats, **opts):
2289 def diff(ui, repo, *pats, **opts):
2290 """diff of the current patch and subsequent modifications
2290 """diff of the current patch and subsequent modifications
2291
2291
2292 Shows a diff which includes the current patch as well as any
2292 Shows a diff which includes the current patch as well as any
2293 changes which have been made in the working directory since the
2293 changes which have been made in the working directory since the
2294 last refresh (thus showing what the current patch would become
2294 last refresh (thus showing what the current patch would become
2295 after a qrefresh).
2295 after a qrefresh).
2296
2296
2297 Use :hg:`diff` if you only want to see the changes made since the
2297 Use :hg:`diff` if you only want to see the changes made since the
2298 last qrefresh, or :hg:`export qtip` if you want to see changes
2298 last qrefresh, or :hg:`export qtip` if you want to see changes
2299 made by the current patch without including changes made since the
2299 made by the current patch without including changes made since the
2300 qrefresh.
2300 qrefresh.
2301
2301
2302 Returns 0 on success.
2302 Returns 0 on success.
2303 """
2303 """
2304 repo.mq.diff(repo, pats, opts)
2304 repo.mq.diff(repo, pats, opts)
2305 return 0
2305 return 0
2306
2306
2307 @command('qfold',
2307 @command('qfold',
2308 [('e', 'edit', None, _('edit patch header')),
2308 [('e', 'edit', None, _('edit patch header')),
2309 ('k', 'keep', None, _('keep folded patch files')),
2309 ('k', 'keep', None, _('keep folded patch files')),
2310 ] + commands.commitopts,
2310 ] + commands.commitopts,
2311 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2311 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2312 def fold(ui, repo, *files, **opts):
2312 def fold(ui, repo, *files, **opts):
2313 """fold the named patches into the current patch
2313 """fold the named patches into the current patch
2314
2314
2315 Patches must not yet be applied. Each patch will be successively
2315 Patches must not yet be applied. Each patch will be successively
2316 applied to the current patch in the order given. If all the
2316 applied to the current patch in the order given. If all the
2317 patches apply successfully, the current patch will be refreshed
2317 patches apply successfully, the current patch will be refreshed
2318 with the new cumulative patch, and the folded patches will be
2318 with the new cumulative patch, and the folded patches will be
2319 deleted. With -k/--keep, the folded patch files will not be
2319 deleted. With -k/--keep, the folded patch files will not be
2320 removed afterwards.
2320 removed afterwards.
2321
2321
2322 The header for each folded patch will be concatenated with the
2322 The header for each folded patch will be concatenated with the
2323 current patch header, separated by a line of ``* * *``.
2323 current patch header, separated by a line of ``* * *``.
2324
2324
2325 Returns 0 on success."""
2325 Returns 0 on success."""
2326
2326
2327 q = repo.mq
2327 q = repo.mq
2328
2328
2329 if not files:
2329 if not files:
2330 raise util.Abort(_('qfold requires at least one patch name'))
2330 raise util.Abort(_('qfold requires at least one patch name'))
2331 if not q.checktoppatch(repo)[0]:
2331 if not q.checktoppatch(repo)[0]:
2332 raise util.Abort(_('no patches applied'))
2332 raise util.Abort(_('no patches applied'))
2333 q.checklocalchanges(repo)
2333 q.checklocalchanges(repo)
2334
2334
2335 message = cmdutil.logmessage(ui, opts)
2335 message = cmdutil.logmessage(ui, opts)
2336 if opts.get('edit'):
2336 if opts.get('edit'):
2337 if message:
2337 if message:
2338 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2338 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2339
2339
2340 parent = q.lookup('qtip')
2340 parent = q.lookup('qtip')
2341 patches = []
2341 patches = []
2342 messages = []
2342 messages = []
2343 for f in files:
2343 for f in files:
2344 p = q.lookup(f)
2344 p = q.lookup(f)
2345 if p in patches or p == parent:
2345 if p in patches or p == parent:
2346 ui.warn(_('Skipping already folded patch %s\n') % p)
2346 ui.warn(_('Skipping already folded patch %s\n') % p)
2347 if q.isapplied(p):
2347 if q.isapplied(p):
2348 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2348 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2349 patches.append(p)
2349 patches.append(p)
2350
2350
2351 for p in patches:
2351 for p in patches:
2352 if not message:
2352 if not message:
2353 ph = patchheader(q.join(p), q.plainmode)
2353 ph = patchheader(q.join(p), q.plainmode)
2354 if ph.message:
2354 if ph.message:
2355 messages.append(ph.message)
2355 messages.append(ph.message)
2356 pf = q.join(p)
2356 pf = q.join(p)
2357 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2357 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2358 if not patchsuccess:
2358 if not patchsuccess:
2359 raise util.Abort(_('error folding patch %s') % p)
2359 raise util.Abort(_('error folding patch %s') % p)
2360
2360
2361 if not message:
2361 if not message:
2362 ph = patchheader(q.join(parent), q.plainmode)
2362 ph = patchheader(q.join(parent), q.plainmode)
2363 message, user = ph.message, ph.user
2363 message, user = ph.message, ph.user
2364 for msg in messages:
2364 for msg in messages:
2365 message.append('* * *')
2365 message.append('* * *')
2366 message.extend(msg)
2366 message.extend(msg)
2367 message = '\n'.join(message)
2367 message = '\n'.join(message)
2368
2368
2369 if opts.get('edit'):
2369 if opts.get('edit'):
2370 message = ui.edit(message, user or ui.username())
2370 message = ui.edit(message, user or ui.username())
2371
2371
2372 diffopts = q.patchopts(q.diffopts(), *patches)
2372 diffopts = q.patchopts(q.diffopts(), *patches)
2373 wlock = repo.wlock()
2373 wlock = repo.wlock()
2374 try:
2374 try:
2375 q.refresh(repo, msg=message, git=diffopts.git)
2375 q.refresh(repo, msg=message, git=diffopts.git)
2376 q.delete(repo, patches, opts)
2376 q.delete(repo, patches, opts)
2377 q.savedirty()
2377 q.savedirty()
2378 finally:
2378 finally:
2379 wlock.release()
2379 wlock.release()
2380
2380
2381 @command("qgoto",
2381 @command("qgoto",
2382 [('f', 'force', None, _('overwrite any local changes'))],
2382 [('f', 'force', None, _('overwrite any local changes'))],
2383 _('hg qgoto [OPTION]... PATCH'))
2383 _('hg qgoto [OPTION]... PATCH'))
2384 def goto(ui, repo, patch, **opts):
2384 def goto(ui, repo, patch, **opts):
2385 '''push or pop patches until named patch is at top of stack
2385 '''push or pop patches until named patch is at top of stack
2386
2386
2387 Returns 0 on success.'''
2387 Returns 0 on success.'''
2388 q = repo.mq
2388 q = repo.mq
2389 patch = q.lookup(patch)
2389 patch = q.lookup(patch)
2390 if q.isapplied(patch):
2390 if q.isapplied(patch):
2391 ret = q.pop(repo, patch, force=opts.get('force'))
2391 ret = q.pop(repo, patch, force=opts.get('force'))
2392 else:
2392 else:
2393 ret = q.push(repo, patch, force=opts.get('force'))
2393 ret = q.push(repo, patch, force=opts.get('force'))
2394 q.savedirty()
2394 q.savedirty()
2395 return ret
2395 return ret
2396
2396
2397 @command("qguard",
2397 @command("qguard",
2398 [('l', 'list', None, _('list all patches and guards')),
2398 [('l', 'list', None, _('list all patches and guards')),
2399 ('n', 'none', None, _('drop all guards'))],
2399 ('n', 'none', None, _('drop all guards'))],
2400 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2400 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2401 def guard(ui, repo, *args, **opts):
2401 def guard(ui, repo, *args, **opts):
2402 '''set or print guards for a patch
2402 '''set or print guards for a patch
2403
2403
2404 Guards control whether a patch can be pushed. A patch with no
2404 Guards control whether a patch can be pushed. A patch with no
2405 guards is always pushed. A patch with a positive guard ("+foo") is
2405 guards is always pushed. A patch with a positive guard ("+foo") is
2406 pushed only if the :hg:`qselect` command has activated it. A patch with
2406 pushed only if the :hg:`qselect` command has activated it. A patch with
2407 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2407 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2408 has activated it.
2408 has activated it.
2409
2409
2410 With no arguments, print the currently active guards.
2410 With no arguments, print the currently active guards.
2411 With arguments, set guards for the named patch.
2411 With arguments, set guards for the named patch.
2412
2412
2413 .. note::
2413 .. note::
2414 Specifying negative guards now requires '--'.
2414 Specifying negative guards now requires '--'.
2415
2415
2416 To set guards on another patch::
2416 To set guards on another patch::
2417
2417
2418 hg qguard other.patch -- +2.6.17 -stable
2418 hg qguard other.patch -- +2.6.17 -stable
2419
2419
2420 Returns 0 on success.
2420 Returns 0 on success.
2421 '''
2421 '''
2422 def status(idx):
2422 def status(idx):
2423 guards = q.seriesguards[idx] or ['unguarded']
2423 guards = q.seriesguards[idx] or ['unguarded']
2424 if q.series[idx] in applied:
2424 if q.series[idx] in applied:
2425 state = 'applied'
2425 state = 'applied'
2426 elif q.pushable(idx)[0]:
2426 elif q.pushable(idx)[0]:
2427 state = 'unapplied'
2427 state = 'unapplied'
2428 else:
2428 else:
2429 state = 'guarded'
2429 state = 'guarded'
2430 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2430 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2431 ui.write('%s: ' % ui.label(q.series[idx], label))
2431 ui.write('%s: ' % ui.label(q.series[idx], label))
2432
2432
2433 for i, guard in enumerate(guards):
2433 for i, guard in enumerate(guards):
2434 if guard.startswith('+'):
2434 if guard.startswith('+'):
2435 ui.write(guard, label='qguard.positive')
2435 ui.write(guard, label='qguard.positive')
2436 elif guard.startswith('-'):
2436 elif guard.startswith('-'):
2437 ui.write(guard, label='qguard.negative')
2437 ui.write(guard, label='qguard.negative')
2438 else:
2438 else:
2439 ui.write(guard, label='qguard.unguarded')
2439 ui.write(guard, label='qguard.unguarded')
2440 if i != len(guards) - 1:
2440 if i != len(guards) - 1:
2441 ui.write(' ')
2441 ui.write(' ')
2442 ui.write('\n')
2442 ui.write('\n')
2443 q = repo.mq
2443 q = repo.mq
2444 applied = set(p.name for p in q.applied)
2444 applied = set(p.name for p in q.applied)
2445 patch = None
2445 patch = None
2446 args = list(args)
2446 args = list(args)
2447 if opts.get('list'):
2447 if opts.get('list'):
2448 if args or opts.get('none'):
2448 if args or opts.get('none'):
2449 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2449 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2450 for i in xrange(len(q.series)):
2450 for i in xrange(len(q.series)):
2451 status(i)
2451 status(i)
2452 return
2452 return
2453 if not args or args[0][0:1] in '-+':
2453 if not args or args[0][0:1] in '-+':
2454 if not q.applied:
2454 if not q.applied:
2455 raise util.Abort(_('no patches applied'))
2455 raise util.Abort(_('no patches applied'))
2456 patch = q.applied[-1].name
2456 patch = q.applied[-1].name
2457 if patch is None and args[0][0:1] not in '-+':
2457 if patch is None and args[0][0:1] not in '-+':
2458 patch = args.pop(0)
2458 patch = args.pop(0)
2459 if patch is None:
2459 if patch is None:
2460 raise util.Abort(_('no patch to work with'))
2460 raise util.Abort(_('no patch to work with'))
2461 if args or opts.get('none'):
2461 if args or opts.get('none'):
2462 idx = q.findseries(patch)
2462 idx = q.findseries(patch)
2463 if idx is None:
2463 if idx is None:
2464 raise util.Abort(_('no patch named %s') % patch)
2464 raise util.Abort(_('no patch named %s') % patch)
2465 q.setguards(idx, args)
2465 q.setguards(idx, args)
2466 q.savedirty()
2466 q.savedirty()
2467 else:
2467 else:
2468 status(q.series.index(q.lookup(patch)))
2468 status(q.series.index(q.lookup(patch)))
2469
2469
2470 @command("qheader", [], _('hg qheader [PATCH]'))
2470 @command("qheader", [], _('hg qheader [PATCH]'))
2471 def header(ui, repo, patch=None):
2471 def header(ui, repo, patch=None):
2472 """print the header of the topmost or specified patch
2472 """print the header of the topmost or specified patch
2473
2473
2474 Returns 0 on success."""
2474 Returns 0 on success."""
2475 q = repo.mq
2475 q = repo.mq
2476
2476
2477 if patch:
2477 if patch:
2478 patch = q.lookup(patch)
2478 patch = q.lookup(patch)
2479 else:
2479 else:
2480 if not q.applied:
2480 if not q.applied:
2481 ui.write(_('no patches applied\n'))
2481 ui.write(_('no patches applied\n'))
2482 return 1
2482 return 1
2483 patch = q.lookup('qtip')
2483 patch = q.lookup('qtip')
2484 ph = patchheader(q.join(patch), q.plainmode)
2484 ph = patchheader(q.join(patch), q.plainmode)
2485
2485
2486 ui.write('\n'.join(ph.message) + '\n')
2486 ui.write('\n'.join(ph.message) + '\n')
2487
2487
2488 def lastsavename(path):
2488 def lastsavename(path):
2489 (directory, base) = os.path.split(path)
2489 (directory, base) = os.path.split(path)
2490 names = os.listdir(directory)
2490 names = os.listdir(directory)
2491 namere = re.compile("%s.([0-9]+)" % base)
2491 namere = re.compile("%s.([0-9]+)" % base)
2492 maxindex = None
2492 maxindex = None
2493 maxname = None
2493 maxname = None
2494 for f in names:
2494 for f in names:
2495 m = namere.match(f)
2495 m = namere.match(f)
2496 if m:
2496 if m:
2497 index = int(m.group(1))
2497 index = int(m.group(1))
2498 if maxindex is None or index > maxindex:
2498 if maxindex is None or index > maxindex:
2499 maxindex = index
2499 maxindex = index
2500 maxname = f
2500 maxname = f
2501 if maxname:
2501 if maxname:
2502 return (os.path.join(directory, maxname), maxindex)
2502 return (os.path.join(directory, maxname), maxindex)
2503 return (None, None)
2503 return (None, None)
2504
2504
2505 def savename(path):
2505 def savename(path):
2506 (last, index) = lastsavename(path)
2506 (last, index) = lastsavename(path)
2507 if last is None:
2507 if last is None:
2508 index = 0
2508 index = 0
2509 newpath = path + ".%d" % (index + 1)
2509 newpath = path + ".%d" % (index + 1)
2510 return newpath
2510 return newpath
2511
2511
2512 @command("^qpush",
2512 @command("^qpush",
2513 [('f', 'force', None, _('apply on top of local changes')),
2513 [('f', 'force', None, _('apply on top of local changes')),
2514 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
2514 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
2515 ('l', 'list', None, _('list patch name in commit text')),
2515 ('l', 'list', None, _('list patch name in commit text')),
2516 ('a', 'all', None, _('apply all patches')),
2516 ('a', 'all', None, _('apply all patches')),
2517 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2517 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2518 ('n', 'name', '',
2518 ('n', 'name', '',
2519 _('merge queue name (DEPRECATED)'), _('NAME')),
2519 _('merge queue name (DEPRECATED)'), _('NAME')),
2520 ('', 'move', None, _('reorder patch series and apply only the patch'))],
2520 ('', 'move', None, _('reorder patch series and apply only the patch'))],
2521 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2521 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2522 def push(ui, repo, patch=None, **opts):
2522 def push(ui, repo, patch=None, **opts):
2523 """push the next patch onto the stack
2523 """push the next patch onto the stack
2524
2524
2525 When -f/--force is applied, all local changes in patched files
2525 When -f/--force is applied, all local changes in patched files
2526 will be lost.
2526 will be lost.
2527
2527
2528 Return 0 on success.
2528 Return 0 on success.
2529 """
2529 """
2530 q = repo.mq
2530 q = repo.mq
2531 mergeq = None
2531 mergeq = None
2532
2532
2533 if opts.get('merge'):
2533 if opts.get('merge'):
2534 if opts.get('name'):
2534 if opts.get('name'):
2535 newpath = repo.join(opts.get('name'))
2535 newpath = repo.join(opts.get('name'))
2536 else:
2536 else:
2537 newpath, i = lastsavename(q.path)
2537 newpath, i = lastsavename(q.path)
2538 if not newpath:
2538 if not newpath:
2539 ui.warn(_("no saved queues found, please use -n\n"))
2539 ui.warn(_("no saved queues found, please use -n\n"))
2540 return 1
2540 return 1
2541 mergeq = queue(ui, repo.join(""), newpath)
2541 mergeq = queue(ui, repo.join(""), newpath)
2542 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2542 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2543 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2543 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2544 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2544 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2545 exact=opts.get('exact'))
2545 exact=opts.get('exact'))
2546 return ret
2546 return ret
2547
2547
2548 @command("^qpop",
2548 @command("^qpop",
2549 [('a', 'all', None, _('pop all patches')),
2549 [('a', 'all', None, _('pop all patches')),
2550 ('n', 'name', '',
2550 ('n', 'name', '',
2551 _('queue name to pop (DEPRECATED)'), _('NAME')),
2551 _('queue name to pop (DEPRECATED)'), _('NAME')),
2552 ('f', 'force', None, _('forget any local changes to patched files'))],
2552 ('f', 'force', None, _('forget any local changes to patched files'))],
2553 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2553 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2554 def pop(ui, repo, patch=None, **opts):
2554 def pop(ui, repo, patch=None, **opts):
2555 """pop the current patch off the stack
2555 """pop the current patch off the stack
2556
2556
2557 By default, pops off the top of the patch stack. If given a patch
2557 By default, pops off the top of the patch stack. If given a patch
2558 name, keeps popping off patches until the named patch is at the
2558 name, keeps popping off patches until the named patch is at the
2559 top of the stack.
2559 top of the stack.
2560
2560
2561 Return 0 on success.
2561 Return 0 on success.
2562 """
2562 """
2563 localupdate = True
2563 localupdate = True
2564 if opts.get('name'):
2564 if opts.get('name'):
2565 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2565 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2566 ui.warn(_('using patch queue: %s\n') % q.path)
2566 ui.warn(_('using patch queue: %s\n') % q.path)
2567 localupdate = False
2567 localupdate = False
2568 else:
2568 else:
2569 q = repo.mq
2569 q = repo.mq
2570 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2570 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2571 all=opts.get('all'))
2571 all=opts.get('all'))
2572 q.savedirty()
2572 q.savedirty()
2573 return ret
2573 return ret
2574
2574
2575 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2575 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2576 def rename(ui, repo, patch, name=None, **opts):
2576 def rename(ui, repo, patch, name=None, **opts):
2577 """rename a patch
2577 """rename a patch
2578
2578
2579 With one argument, renames the current patch to PATCH1.
2579 With one argument, renames the current patch to PATCH1.
2580 With two arguments, renames PATCH1 to PATCH2.
2580 With two arguments, renames PATCH1 to PATCH2.
2581
2581
2582 Returns 0 on success."""
2582 Returns 0 on success."""
2583
2583
2584 q = repo.mq
2584 q = repo.mq
2585
2585
2586 if not name:
2586 if not name:
2587 name = patch
2587 name = patch
2588 patch = None
2588 patch = None
2589
2589
2590 if patch:
2590 if patch:
2591 patch = q.lookup(patch)
2591 patch = q.lookup(patch)
2592 else:
2592 else:
2593 if not q.applied:
2593 if not q.applied:
2594 ui.write(_('no patches applied\n'))
2594 ui.write(_('no patches applied\n'))
2595 return
2595 return
2596 patch = q.lookup('qtip')
2596 patch = q.lookup('qtip')
2597 absdest = q.join(name)
2597 absdest = q.join(name)
2598 if os.path.isdir(absdest):
2598 if os.path.isdir(absdest):
2599 name = normname(os.path.join(name, os.path.basename(patch)))
2599 name = normname(os.path.join(name, os.path.basename(patch)))
2600 absdest = q.join(name)
2600 absdest = q.join(name)
2601 q.checkpatchname(name)
2601 q.checkpatchname(name)
2602
2602
2603 ui.note(_('renaming %s to %s\n') % (patch, name))
2603 ui.note(_('renaming %s to %s\n') % (patch, name))
2604 i = q.findseries(patch)
2604 i = q.findseries(patch)
2605 guards = q.guard_re.findall(q.fullseries[i])
2605 guards = q.guard_re.findall(q.fullseries[i])
2606 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2606 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2607 q.parseseries()
2607 q.parseseries()
2608 q.seriesdirty = 1
2608 q.seriesdirty = 1
2609
2609
2610 info = q.isapplied(patch)
2610 info = q.isapplied(patch)
2611 if info:
2611 if info:
2612 q.applied[info[0]] = statusentry(info[1], name)
2612 q.applied[info[0]] = statusentry(info[1], name)
2613 q.applieddirty = 1
2613 q.applieddirty = 1
2614
2614
2615 destdir = os.path.dirname(absdest)
2615 destdir = os.path.dirname(absdest)
2616 if not os.path.isdir(destdir):
2616 if not os.path.isdir(destdir):
2617 os.makedirs(destdir)
2617 os.makedirs(destdir)
2618 util.rename(q.join(patch), absdest)
2618 util.rename(q.join(patch), absdest)
2619 r = q.qrepo()
2619 r = q.qrepo()
2620 if r and patch in r.dirstate:
2620 if r and patch in r.dirstate:
2621 wctx = r[None]
2621 wctx = r[None]
2622 wlock = r.wlock()
2622 wlock = r.wlock()
2623 try:
2623 try:
2624 if r.dirstate[patch] == 'a':
2624 if r.dirstate[patch] == 'a':
2625 r.dirstate.drop(patch)
2625 r.dirstate.drop(patch)
2626 r.dirstate.add(name)
2626 r.dirstate.add(name)
2627 else:
2627 else:
2628 if r.dirstate[name] == 'r':
2628 if r.dirstate[name] == 'r':
2629 wctx.undelete([name])
2629 wctx.undelete([name])
2630 wctx.copy(patch, name)
2630 wctx.copy(patch, name)
2631 wctx.forget([patch])
2631 wctx.forget([patch])
2632 finally:
2632 finally:
2633 wlock.release()
2633 wlock.release()
2634
2634
2635 q.savedirty()
2635 q.savedirty()
2636
2636
2637 @command("qrestore",
2637 @command("qrestore",
2638 [('d', 'delete', None, _('delete save entry')),
2638 [('d', 'delete', None, _('delete save entry')),
2639 ('u', 'update', None, _('update queue working directory'))],
2639 ('u', 'update', None, _('update queue working directory'))],
2640 _('hg qrestore [-d] [-u] REV'))
2640 _('hg qrestore [-d] [-u] REV'))
2641 def restore(ui, repo, rev, **opts):
2641 def restore(ui, repo, rev, **opts):
2642 """restore the queue state saved by a revision (DEPRECATED)
2642 """restore the queue state saved by a revision (DEPRECATED)
2643
2643
2644 This command is deprecated, use :hg:`rebase` instead."""
2644 This command is deprecated, use :hg:`rebase` instead."""
2645 rev = repo.lookup(rev)
2645 rev = repo.lookup(rev)
2646 q = repo.mq
2646 q = repo.mq
2647 q.restore(repo, rev, delete=opts.get('delete'),
2647 q.restore(repo, rev, delete=opts.get('delete'),
2648 qupdate=opts.get('update'))
2648 qupdate=opts.get('update'))
2649 q.savedirty()
2649 q.savedirty()
2650 return 0
2650 return 0
2651
2651
2652 @command("qsave",
2652 @command("qsave",
2653 [('c', 'copy', None, _('copy patch directory')),
2653 [('c', 'copy', None, _('copy patch directory')),
2654 ('n', 'name', '',
2654 ('n', 'name', '',
2655 _('copy directory name'), _('NAME')),
2655 _('copy directory name'), _('NAME')),
2656 ('e', 'empty', None, _('clear queue status file')),
2656 ('e', 'empty', None, _('clear queue status file')),
2657 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2657 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2658 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2658 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2659 def save(ui, repo, **opts):
2659 def save(ui, repo, **opts):
2660 """save current queue state (DEPRECATED)
2660 """save current queue state (DEPRECATED)
2661
2661
2662 This command is deprecated, use :hg:`rebase` instead."""
2662 This command is deprecated, use :hg:`rebase` instead."""
2663 q = repo.mq
2663 q = repo.mq
2664 message = cmdutil.logmessage(ui, opts)
2664 message = cmdutil.logmessage(ui, opts)
2665 ret = q.save(repo, msg=message)
2665 ret = q.save(repo, msg=message)
2666 if ret:
2666 if ret:
2667 return ret
2667 return ret
2668 q.savedirty()
2668 q.savedirty()
2669 if opts.get('copy'):
2669 if opts.get('copy'):
2670 path = q.path
2670 path = q.path
2671 if opts.get('name'):
2671 if opts.get('name'):
2672 newpath = os.path.join(q.basepath, opts.get('name'))
2672 newpath = os.path.join(q.basepath, opts.get('name'))
2673 if os.path.exists(newpath):
2673 if os.path.exists(newpath):
2674 if not os.path.isdir(newpath):
2674 if not os.path.isdir(newpath):
2675 raise util.Abort(_('destination %s exists and is not '
2675 raise util.Abort(_('destination %s exists and is not '
2676 'a directory') % newpath)
2676 'a directory') % newpath)
2677 if not opts.get('force'):
2677 if not opts.get('force'):
2678 raise util.Abort(_('destination %s exists, '
2678 raise util.Abort(_('destination %s exists, '
2679 'use -f to force') % newpath)
2679 'use -f to force') % newpath)
2680 else:
2680 else:
2681 newpath = savename(path)
2681 newpath = savename(path)
2682 ui.warn(_("copy %s to %s\n") % (path, newpath))
2682 ui.warn(_("copy %s to %s\n") % (path, newpath))
2683 util.copyfiles(path, newpath)
2683 util.copyfiles(path, newpath)
2684 if opts.get('empty'):
2684 if opts.get('empty'):
2685 try:
2685 try:
2686 os.unlink(q.join(q.statuspath))
2686 os.unlink(q.join(q.statuspath))
2687 except:
2687 except:
2688 pass
2688 pass
2689 return 0
2689 return 0
2690
2690
2691 @command("strip",
2691 @command("strip",
2692 [('f', 'force', None, _('force removal of changesets, discard '
2692 [('f', 'force', None, _('force removal of changesets, discard '
2693 'uncommitted changes (no backup)')),
2693 'uncommitted changes (no backup)')),
2694 ('b', 'backup', None, _('bundle only changesets with local revision'
2694 ('b', 'backup', None, _('bundle only changesets with local revision'
2695 ' number greater than REV which are not'
2695 ' number greater than REV which are not'
2696 ' descendants of REV (DEPRECATED)')),
2696 ' descendants of REV (DEPRECATED)')),
2697 ('n', 'no-backup', None, _('no backups')),
2697 ('n', 'no-backup', None, _('no backups')),
2698 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2698 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2699 ('k', 'keep', None, _("do not modify working copy during strip"))],
2699 ('k', 'keep', None, _("do not modify working copy during strip"))],
2700 _('hg strip [-k] [-f] [-n] REV...'))
2700 _('hg strip [-k] [-f] [-n] REV...'))
2701 def strip(ui, repo, *revs, **opts):
2701 def strip(ui, repo, *revs, **opts):
2702 """strip changesets and all their descendants from the repository
2702 """strip changesets and all their descendants from the repository
2703
2703
2704 The strip command removes the specified changesets and all their
2704 The strip command removes the specified changesets and all their
2705 descendants. If the working directory has uncommitted changes, the
2705 descendants. If the working directory has uncommitted changes, the
2706 operation is aborted unless the --force flag is supplied, in which
2706 operation is aborted unless the --force flag is supplied, in which
2707 case changes will be discarded.
2707 case changes will be discarded.
2708
2708
2709 If a parent of the working directory is stripped, then the working
2709 If a parent of the working directory is stripped, then the working
2710 directory will automatically be updated to the most recent
2710 directory will automatically be updated to the most recent
2711 available ancestor of the stripped parent after the operation
2711 available ancestor of the stripped parent after the operation
2712 completes.
2712 completes.
2713
2713
2714 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2714 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2715 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2715 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2716 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2716 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2717 where BUNDLE is the bundle file created by the strip. Note that
2717 where BUNDLE is the bundle file created by the strip. Note that
2718 the local revision numbers will in general be different after the
2718 the local revision numbers will in general be different after the
2719 restore.
2719 restore.
2720
2720
2721 Use the --no-backup option to discard the backup bundle once the
2721 Use the --no-backup option to discard the backup bundle once the
2722 operation completes.
2722 operation completes.
2723
2723
2724 Return 0 on success.
2724 Return 0 on success.
2725 """
2725 """
2726 backup = 'all'
2726 backup = 'all'
2727 if opts.get('backup'):
2727 if opts.get('backup'):
2728 backup = 'strip'
2728 backup = 'strip'
2729 elif opts.get('no_backup') or opts.get('nobackup'):
2729 elif opts.get('no_backup') or opts.get('nobackup'):
2730 backup = 'none'
2730 backup = 'none'
2731
2731
2732 cl = repo.changelog
2732 cl = repo.changelog
2733 revs = set(scmutil.revrange(repo, revs))
2733 revs = set(scmutil.revrange(repo, revs))
2734 if not revs:
2734 if not revs:
2735 raise util.Abort(_('empty revision set'))
2735 raise util.Abort(_('empty revision set'))
2736
2736
2737 descendants = set(cl.descendants(*revs))
2737 descendants = set(cl.descendants(*revs))
2738 strippedrevs = revs.union(descendants)
2738 strippedrevs = revs.union(descendants)
2739 roots = revs.difference(descendants)
2739 roots = revs.difference(descendants)
2740
2740
2741 update = False
2741 update = False
2742 # if one of the wdir parent is stripped we'll need
2742 # if one of the wdir parent is stripped we'll need
2743 # to update away to an earlier revision
2743 # to update away to an earlier revision
2744 for p in repo.dirstate.parents():
2744 for p in repo.dirstate.parents():
2745 if p != nullid and cl.rev(p) in strippedrevs:
2745 if p != nullid and cl.rev(p) in strippedrevs:
2746 update = True
2746 update = True
2747 break
2747 break
2748
2748
2749 rootnodes = set(cl.node(r) for r in roots)
2749 rootnodes = set(cl.node(r) for r in roots)
2750
2750
2751 q = repo.mq
2751 q = repo.mq
2752 if q.applied:
2752 if q.applied:
2753 # refresh queue state if we're about to strip
2753 # refresh queue state if we're about to strip
2754 # applied patches
2754 # applied patches
2755 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2755 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2756 q.applieddirty = True
2756 q.applieddirty = True
2757 start = 0
2757 start = 0
2758 end = len(q.applied)
2758 end = len(q.applied)
2759 for i, statusentry in enumerate(q.applied):
2759 for i, statusentry in enumerate(q.applied):
2760 if statusentry.node in rootnodes:
2760 if statusentry.node in rootnodes:
2761 # if one of the stripped roots is an applied
2761 # if one of the stripped roots is an applied
2762 # patch, only part of the queue is stripped
2762 # patch, only part of the queue is stripped
2763 start = i
2763 start = i
2764 break
2764 break
2765 del q.applied[start:end]
2765 del q.applied[start:end]
2766 q.savedirty()
2766 q.savedirty()
2767
2767
2768 revs = list(rootnodes)
2768 revs = list(rootnodes)
2769 if update and opts.get('keep'):
2769 if update and opts.get('keep'):
2770 wlock = repo.wlock()
2770 wlock = repo.wlock()
2771 try:
2771 try:
2772 urev = repo.mq.qparents(repo, revs[0])
2772 urev = repo.mq.qparents(repo, revs[0])
2773 repo.dirstate.rebuild(urev, repo[urev].manifest())
2773 repo.dirstate.rebuild(urev, repo[urev].manifest())
2774 repo.dirstate.write()
2774 repo.dirstate.write()
2775 update = False
2775 update = False
2776 finally:
2776 finally:
2777 wlock.release()
2777 wlock.release()
2778
2778
2779 repo.mq.strip(repo, revs, backup=backup, update=update,
2779 repo.mq.strip(repo, revs, backup=backup, update=update,
2780 force=opts.get('force'))
2780 force=opts.get('force'))
2781 return 0
2781 return 0
2782
2782
2783 @command("qselect",
2783 @command("qselect",
2784 [('n', 'none', None, _('disable all guards')),
2784 [('n', 'none', None, _('disable all guards')),
2785 ('s', 'series', None, _('list all guards in series file')),
2785 ('s', 'series', None, _('list all guards in series file')),
2786 ('', 'pop', None, _('pop to before first guarded applied patch')),
2786 ('', 'pop', None, _('pop to before first guarded applied patch')),
2787 ('', 'reapply', None, _('pop, then reapply patches'))],
2787 ('', 'reapply', None, _('pop, then reapply patches'))],
2788 _('hg qselect [OPTION]... [GUARD]...'))
2788 _('hg qselect [OPTION]... [GUARD]...'))
2789 def select(ui, repo, *args, **opts):
2789 def select(ui, repo, *args, **opts):
2790 '''set or print guarded patches to push
2790 '''set or print guarded patches to push
2791
2791
2792 Use the :hg:`qguard` command to set or print guards on patch, then use
2792 Use the :hg:`qguard` command to set or print guards on patch, then use
2793 qselect to tell mq which guards to use. A patch will be pushed if
2793 qselect to tell mq which guards to use. A patch will be pushed if
2794 it has no guards or any positive guards match the currently
2794 it has no guards or any positive guards match the currently
2795 selected guard, but will not be pushed if any negative guards
2795 selected guard, but will not be pushed if any negative guards
2796 match the current guard. For example::
2796 match the current guard. For example::
2797
2797
2798 qguard foo.patch -- -stable (negative guard)
2798 qguard foo.patch -- -stable (negative guard)
2799 qguard bar.patch +stable (positive guard)
2799 qguard bar.patch +stable (positive guard)
2800 qselect stable
2800 qselect stable
2801
2801
2802 This activates the "stable" guard. mq will skip foo.patch (because
2802 This activates the "stable" guard. mq will skip foo.patch (because
2803 it has a negative match) but push bar.patch (because it has a
2803 it has a negative match) but push bar.patch (because it has a
2804 positive match).
2804 positive match).
2805
2805
2806 With no arguments, prints the currently active guards.
2806 With no arguments, prints the currently active guards.
2807 With one argument, sets the active guard.
2807 With one argument, sets the active guard.
2808
2808
2809 Use -n/--none to deactivate guards (no other arguments needed).
2809 Use -n/--none to deactivate guards (no other arguments needed).
2810 When no guards are active, patches with positive guards are
2810 When no guards are active, patches with positive guards are
2811 skipped and patches with negative guards are pushed.
2811 skipped and patches with negative guards are pushed.
2812
2812
2813 qselect can change the guards on applied patches. It does not pop
2813 qselect can change the guards on applied patches. It does not pop
2814 guarded patches by default. Use --pop to pop back to the last
2814 guarded patches by default. Use --pop to pop back to the last
2815 applied patch that is not guarded. Use --reapply (which implies
2815 applied patch that is not guarded. Use --reapply (which implies
2816 --pop) to push back to the current patch afterwards, but skip
2816 --pop) to push back to the current patch afterwards, but skip
2817 guarded patches.
2817 guarded patches.
2818
2818
2819 Use -s/--series to print a list of all guards in the series file
2819 Use -s/--series to print a list of all guards in the series file
2820 (no other arguments needed). Use -v for more information.
2820 (no other arguments needed). Use -v for more information.
2821
2821
2822 Returns 0 on success.'''
2822 Returns 0 on success.'''
2823
2823
2824 q = repo.mq
2824 q = repo.mq
2825 guards = q.active()
2825 guards = q.active()
2826 if args or opts.get('none'):
2826 if args or opts.get('none'):
2827 old_unapplied = q.unapplied(repo)
2827 old_unapplied = q.unapplied(repo)
2828 old_guarded = [i for i in xrange(len(q.applied)) if
2828 old_guarded = [i for i in xrange(len(q.applied)) if
2829 not q.pushable(i)[0]]
2829 not q.pushable(i)[0]]
2830 q.setactive(args)
2830 q.setactive(args)
2831 q.savedirty()
2831 q.savedirty()
2832 if not args:
2832 if not args:
2833 ui.status(_('guards deactivated\n'))
2833 ui.status(_('guards deactivated\n'))
2834 if not opts.get('pop') and not opts.get('reapply'):
2834 if not opts.get('pop') and not opts.get('reapply'):
2835 unapplied = q.unapplied(repo)
2835 unapplied = q.unapplied(repo)
2836 guarded = [i for i in xrange(len(q.applied))
2836 guarded = [i for i in xrange(len(q.applied))
2837 if not q.pushable(i)[0]]
2837 if not q.pushable(i)[0]]
2838 if len(unapplied) != len(old_unapplied):
2838 if len(unapplied) != len(old_unapplied):
2839 ui.status(_('number of unguarded, unapplied patches has '
2839 ui.status(_('number of unguarded, unapplied patches has '
2840 'changed from %d to %d\n') %
2840 'changed from %d to %d\n') %
2841 (len(old_unapplied), len(unapplied)))
2841 (len(old_unapplied), len(unapplied)))
2842 if len(guarded) != len(old_guarded):
2842 if len(guarded) != len(old_guarded):
2843 ui.status(_('number of guarded, applied patches has changed '
2843 ui.status(_('number of guarded, applied patches has changed '
2844 'from %d to %d\n') %
2844 'from %d to %d\n') %
2845 (len(old_guarded), len(guarded)))
2845 (len(old_guarded), len(guarded)))
2846 elif opts.get('series'):
2846 elif opts.get('series'):
2847 guards = {}
2847 guards = {}
2848 noguards = 0
2848 noguards = 0
2849 for gs in q.seriesguards:
2849 for gs in q.seriesguards:
2850 if not gs:
2850 if not gs:
2851 noguards += 1
2851 noguards += 1
2852 for g in gs:
2852 for g in gs:
2853 guards.setdefault(g, 0)
2853 guards.setdefault(g, 0)
2854 guards[g] += 1
2854 guards[g] += 1
2855 if ui.verbose:
2855 if ui.verbose:
2856 guards['NONE'] = noguards
2856 guards['NONE'] = noguards
2857 guards = guards.items()
2857 guards = guards.items()
2858 guards.sort(key=lambda x: x[0][1:])
2858 guards.sort(key=lambda x: x[0][1:])
2859 if guards:
2859 if guards:
2860 ui.note(_('guards in series file:\n'))
2860 ui.note(_('guards in series file:\n'))
2861 for guard, count in guards:
2861 for guard, count in guards:
2862 ui.note('%2d ' % count)
2862 ui.note('%2d ' % count)
2863 ui.write(guard, '\n')
2863 ui.write(guard, '\n')
2864 else:
2864 else:
2865 ui.note(_('no guards in series file\n'))
2865 ui.note(_('no guards in series file\n'))
2866 else:
2866 else:
2867 if guards:
2867 if guards:
2868 ui.note(_('active guards:\n'))
2868 ui.note(_('active guards:\n'))
2869 for g in guards:
2869 for g in guards:
2870 ui.write(g, '\n')
2870 ui.write(g, '\n')
2871 else:
2871 else:
2872 ui.write(_('no active guards\n'))
2872 ui.write(_('no active guards\n'))
2873 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2873 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2874 popped = False
2874 popped = False
2875 if opts.get('pop') or opts.get('reapply'):
2875 if opts.get('pop') or opts.get('reapply'):
2876 for i in xrange(len(q.applied)):
2876 for i in xrange(len(q.applied)):
2877 pushable, reason = q.pushable(i)
2877 pushable, reason = q.pushable(i)
2878 if not pushable:
2878 if not pushable:
2879 ui.status(_('popping guarded patches\n'))
2879 ui.status(_('popping guarded patches\n'))
2880 popped = True
2880 popped = True
2881 if i == 0:
2881 if i == 0:
2882 q.pop(repo, all=True)
2882 q.pop(repo, all=True)
2883 else:
2883 else:
2884 q.pop(repo, i - 1)
2884 q.pop(repo, i - 1)
2885 break
2885 break
2886 if popped:
2886 if popped:
2887 try:
2887 try:
2888 if reapply:
2888 if reapply:
2889 ui.status(_('reapplying unguarded patches\n'))
2889 ui.status(_('reapplying unguarded patches\n'))
2890 q.push(repo, reapply)
2890 q.push(repo, reapply)
2891 finally:
2891 finally:
2892 q.savedirty()
2892 q.savedirty()
2893
2893
2894 @command("qfinish",
2894 @command("qfinish",
2895 [('a', 'applied', None, _('finish all applied changesets'))],
2895 [('a', 'applied', None, _('finish all applied changesets'))],
2896 _('hg qfinish [-a] [REV]...'))
2896 _('hg qfinish [-a] [REV]...'))
2897 def finish(ui, repo, *revrange, **opts):
2897 def finish(ui, repo, *revrange, **opts):
2898 """move applied patches into repository history
2898 """move applied patches into repository history
2899
2899
2900 Finishes the specified revisions (corresponding to applied
2900 Finishes the specified revisions (corresponding to applied
2901 patches) by moving them out of mq control into regular repository
2901 patches) by moving them out of mq control into regular repository
2902 history.
2902 history.
2903
2903
2904 Accepts a revision range or the -a/--applied option. If --applied
2904 Accepts a revision range or the -a/--applied option. If --applied
2905 is specified, all applied mq revisions are removed from mq
2905 is specified, all applied mq revisions are removed from mq
2906 control. Otherwise, the given revisions must be at the base of the
2906 control. Otherwise, the given revisions must be at the base of the
2907 stack of applied patches.
2907 stack of applied patches.
2908
2908
2909 This can be especially useful if your changes have been applied to
2909 This can be especially useful if your changes have been applied to
2910 an upstream repository, or if you are about to push your changes
2910 an upstream repository, or if you are about to push your changes
2911 to upstream.
2911 to upstream.
2912
2912
2913 Returns 0 on success.
2913 Returns 0 on success.
2914 """
2914 """
2915 if not opts.get('applied') and not revrange:
2915 if not opts.get('applied') and not revrange:
2916 raise util.Abort(_('no revisions specified'))
2916 raise util.Abort(_('no revisions specified'))
2917 elif opts.get('applied'):
2917 elif opts.get('applied'):
2918 revrange = ('qbase::qtip',) + revrange
2918 revrange = ('qbase::qtip',) + revrange
2919
2919
2920 q = repo.mq
2920 q = repo.mq
2921 if not q.applied:
2921 if not q.applied:
2922 ui.status(_('no patches applied\n'))
2922 ui.status(_('no patches applied\n'))
2923 return 0
2923 return 0
2924
2924
2925 revs = scmutil.revrange(repo, revrange)
2925 revs = scmutil.revrange(repo, revrange)
2926 q.finish(repo, revs)
2926 q.finish(repo, revs)
2927 q.savedirty()
2927 q.savedirty()
2928 return 0
2928 return 0
2929
2929
2930 @command("qqueue",
2930 @command("qqueue",
2931 [('l', 'list', False, _('list all available queues')),
2931 [('l', 'list', False, _('list all available queues')),
2932 ('c', 'create', False, _('create new queue')),
2932 ('c', 'create', False, _('create new queue')),
2933 ('', 'rename', False, _('rename active queue')),
2933 ('', 'rename', False, _('rename active queue')),
2934 ('', 'delete', False, _('delete reference to queue')),
2934 ('', 'delete', False, _('delete reference to queue')),
2935 ('', 'purge', False, _('delete queue, and remove patch dir')),
2935 ('', 'purge', False, _('delete queue, and remove patch dir')),
2936 ],
2936 ],
2937 _('[OPTION] [QUEUE]'))
2937 _('[OPTION] [QUEUE]'))
2938 def qqueue(ui, repo, name=None, **opts):
2938 def qqueue(ui, repo, name=None, **opts):
2939 '''manage multiple patch queues
2939 '''manage multiple patch queues
2940
2940
2941 Supports switching between different patch queues, as well as creating
2941 Supports switching between different patch queues, as well as creating
2942 new patch queues and deleting existing ones.
2942 new patch queues and deleting existing ones.
2943
2943
2944 Omitting a queue name or specifying -l/--list will show you the registered
2944 Omitting a queue name or specifying -l/--list will show you the registered
2945 queues - by default the "normal" patches queue is registered. The currently
2945 queues - by default the "normal" patches queue is registered. The currently
2946 active queue will be marked with "(active)".
2946 active queue will be marked with "(active)".
2947
2947
2948 To create a new queue, use -c/--create. The queue is automatically made
2948 To create a new queue, use -c/--create. The queue is automatically made
2949 active, except in the case where there are applied patches from the
2949 active, except in the case where there are applied patches from the
2950 currently active queue in the repository. Then the queue will only be
2950 currently active queue in the repository. Then the queue will only be
2951 created and switching will fail.
2951 created and switching will fail.
2952
2952
2953 To delete an existing queue, use --delete. You cannot delete the currently
2953 To delete an existing queue, use --delete. You cannot delete the currently
2954 active queue.
2954 active queue.
2955
2955
2956 Returns 0 on success.
2956 Returns 0 on success.
2957 '''
2957 '''
2958
2958
2959 q = repo.mq
2959 q = repo.mq
2960
2960
2961 _defaultqueue = 'patches'
2961 _defaultqueue = 'patches'
2962 _allqueues = 'patches.queues'
2962 _allqueues = 'patches.queues'
2963 _activequeue = 'patches.queue'
2963 _activequeue = 'patches.queue'
2964
2964
2965 def _getcurrent():
2965 def _getcurrent():
2966 cur = os.path.basename(q.path)
2966 cur = os.path.basename(q.path)
2967 if cur.startswith('patches-'):
2967 if cur.startswith('patches-'):
2968 cur = cur[8:]
2968 cur = cur[8:]
2969 return cur
2969 return cur
2970
2970
2971 def _noqueues():
2971 def _noqueues():
2972 try:
2972 try:
2973 fh = repo.opener(_allqueues, 'r')
2973 fh = repo.opener(_allqueues, 'r')
2974 fh.close()
2974 fh.close()
2975 except IOError:
2975 except IOError:
2976 return True
2976 return True
2977
2977
2978 return False
2978 return False
2979
2979
2980 def _getqueues():
2980 def _getqueues():
2981 current = _getcurrent()
2981 current = _getcurrent()
2982
2982
2983 try:
2983 try:
2984 fh = repo.opener(_allqueues, 'r')
2984 fh = repo.opener(_allqueues, 'r')
2985 queues = [queue.strip() for queue in fh if queue.strip()]
2985 queues = [queue.strip() for queue in fh if queue.strip()]
2986 fh.close()
2986 fh.close()
2987 if current not in queues:
2987 if current not in queues:
2988 queues.append(current)
2988 queues.append(current)
2989 except IOError:
2989 except IOError:
2990 queues = [_defaultqueue]
2990 queues = [_defaultqueue]
2991
2991
2992 return sorted(queues)
2992 return sorted(queues)
2993
2993
2994 def _setactive(name):
2994 def _setactive(name):
2995 if q.applied:
2995 if q.applied:
2996 raise util.Abort(_('patches applied - cannot set new queue active'))
2996 raise util.Abort(_('patches applied - cannot set new queue active'))
2997 _setactivenocheck(name)
2997 _setactivenocheck(name)
2998
2998
2999 def _setactivenocheck(name):
2999 def _setactivenocheck(name):
3000 fh = repo.opener(_activequeue, 'w')
3000 fh = repo.opener(_activequeue, 'w')
3001 if name != 'patches':
3001 if name != 'patches':
3002 fh.write(name)
3002 fh.write(name)
3003 fh.close()
3003 fh.close()
3004
3004
3005 def _addqueue(name):
3005 def _addqueue(name):
3006 fh = repo.opener(_allqueues, 'a')
3006 fh = repo.opener(_allqueues, 'a')
3007 fh.write('%s\n' % (name,))
3007 fh.write('%s\n' % (name,))
3008 fh.close()
3008 fh.close()
3009
3009
3010 def _queuedir(name):
3010 def _queuedir(name):
3011 if name == 'patches':
3011 if name == 'patches':
3012 return repo.join('patches')
3012 return repo.join('patches')
3013 else:
3013 else:
3014 return repo.join('patches-' + name)
3014 return repo.join('patches-' + name)
3015
3015
3016 def _validname(name):
3016 def _validname(name):
3017 for n in name:
3017 for n in name:
3018 if n in ':\\/.':
3018 if n in ':\\/.':
3019 return False
3019 return False
3020 return True
3020 return True
3021
3021
3022 def _delete(name):
3022 def _delete(name):
3023 if name not in existing:
3023 if name not in existing:
3024 raise util.Abort(_('cannot delete queue that does not exist'))
3024 raise util.Abort(_('cannot delete queue that does not exist'))
3025
3025
3026 current = _getcurrent()
3026 current = _getcurrent()
3027
3027
3028 if name == current:
3028 if name == current:
3029 raise util.Abort(_('cannot delete currently active queue'))
3029 raise util.Abort(_('cannot delete currently active queue'))
3030
3030
3031 fh = repo.opener('patches.queues.new', 'w')
3031 fh = repo.opener('patches.queues.new', 'w')
3032 for queue in existing:
3032 for queue in existing:
3033 if queue == name:
3033 if queue == name:
3034 continue
3034 continue
3035 fh.write('%s\n' % (queue,))
3035 fh.write('%s\n' % (queue,))
3036 fh.close()
3036 fh.close()
3037 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3037 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3038
3038
3039 if not name or opts.get('list'):
3039 if not name or opts.get('list'):
3040 current = _getcurrent()
3040 current = _getcurrent()
3041 for queue in _getqueues():
3041 for queue in _getqueues():
3042 ui.write('%s' % (queue,))
3042 ui.write('%s' % (queue,))
3043 if queue == current and not ui.quiet:
3043 if queue == current and not ui.quiet:
3044 ui.write(_(' (active)\n'))
3044 ui.write(_(' (active)\n'))
3045 else:
3045 else:
3046 ui.write('\n')
3046 ui.write('\n')
3047 return
3047 return
3048
3048
3049 if not _validname(name):
3049 if not _validname(name):
3050 raise util.Abort(
3050 raise util.Abort(
3051 _('invalid queue name, may not contain the characters ":\\/."'))
3051 _('invalid queue name, may not contain the characters ":\\/."'))
3052
3052
3053 existing = _getqueues()
3053 existing = _getqueues()
3054
3054
3055 if opts.get('create'):
3055 if opts.get('create'):
3056 if name in existing:
3056 if name in existing:
3057 raise util.Abort(_('queue "%s" already exists') % name)
3057 raise util.Abort(_('queue "%s" already exists') % name)
3058 if _noqueues():
3058 if _noqueues():
3059 _addqueue(_defaultqueue)
3059 _addqueue(_defaultqueue)
3060 _addqueue(name)
3060 _addqueue(name)
3061 _setactive(name)
3061 _setactive(name)
3062 elif opts.get('rename'):
3062 elif opts.get('rename'):
3063 current = _getcurrent()
3063 current = _getcurrent()
3064 if name == current:
3064 if name == current:
3065 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3065 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3066 if name in existing:
3066 if name in existing:
3067 raise util.Abort(_('queue "%s" already exists') % name)
3067 raise util.Abort(_('queue "%s" already exists') % name)
3068
3068
3069 olddir = _queuedir(current)
3069 olddir = _queuedir(current)
3070 newdir = _queuedir(name)
3070 newdir = _queuedir(name)
3071
3071
3072 if os.path.exists(newdir):
3072 if os.path.exists(newdir):
3073 raise util.Abort(_('non-queue directory "%s" already exists') %
3073 raise util.Abort(_('non-queue directory "%s" already exists') %
3074 newdir)
3074 newdir)
3075
3075
3076 fh = repo.opener('patches.queues.new', 'w')
3076 fh = repo.opener('patches.queues.new', 'w')
3077 for queue in existing:
3077 for queue in existing:
3078 if queue == current:
3078 if queue == current:
3079 fh.write('%s\n' % (name,))
3079 fh.write('%s\n' % (name,))
3080 if os.path.exists(olddir):
3080 if os.path.exists(olddir):
3081 util.rename(olddir, newdir)
3081 util.rename(olddir, newdir)
3082 else:
3082 else:
3083 fh.write('%s\n' % (queue,))
3083 fh.write('%s\n' % (queue,))
3084 fh.close()
3084 fh.close()
3085 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3085 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3086 _setactivenocheck(name)
3086 _setactivenocheck(name)
3087 elif opts.get('delete'):
3087 elif opts.get('delete'):
3088 _delete(name)
3088 _delete(name)
3089 elif opts.get('purge'):
3089 elif opts.get('purge'):
3090 if name in existing:
3090 if name in existing:
3091 _delete(name)
3091 _delete(name)
3092 qdir = _queuedir(name)
3092 qdir = _queuedir(name)
3093 if os.path.exists(qdir):
3093 if os.path.exists(qdir):
3094 shutil.rmtree(qdir)
3094 shutil.rmtree(qdir)
3095 else:
3095 else:
3096 if name not in existing:
3096 if name not in existing:
3097 raise util.Abort(_('use --create to create a new queue'))
3097 raise util.Abort(_('use --create to create a new queue'))
3098 _setactive(name)
3098 _setactive(name)
3099
3099
3100 def reposetup(ui, repo):
3100 def reposetup(ui, repo):
3101 class mqrepo(repo.__class__):
3101 class mqrepo(repo.__class__):
3102 @util.propertycache
3102 @util.propertycache
3103 def mq(self):
3103 def mq(self):
3104 return queue(self.ui, self.join(""))
3104 return queue(self.ui, self.join(""))
3105
3105
3106 def abortifwdirpatched(self, errmsg, force=False):
3106 def abortifwdirpatched(self, errmsg, force=False):
3107 if self.mq.applied and not force:
3107 if self.mq.applied and not force:
3108 parents = self.dirstate.parents()
3108 parents = self.dirstate.parents()
3109 patches = [s.node for s in self.mq.applied]
3109 patches = [s.node for s in self.mq.applied]
3110 if parents[0] in patches or parents[1] in patches:
3110 if parents[0] in patches or parents[1] in patches:
3111 raise util.Abort(errmsg)
3111 raise util.Abort(errmsg)
3112
3112
3113 def commit(self, text="", user=None, date=None, match=None,
3113 def commit(self, text="", user=None, date=None, match=None,
3114 force=False, editor=False, extra={}):
3114 force=False, editor=False, extra={}):
3115 self.abortifwdirpatched(
3115 self.abortifwdirpatched(
3116 _('cannot commit over an applied mq patch'),
3116 _('cannot commit over an applied mq patch'),
3117 force)
3117 force)
3118
3118
3119 return super(mqrepo, self).commit(text, user, date, match, force,
3119 return super(mqrepo, self).commit(text, user, date, match, force,
3120 editor, extra)
3120 editor, extra)
3121
3121
3122 def checkpush(self, force, revs):
3122 def checkpush(self, force, revs):
3123 if self.mq.applied and not force:
3123 if self.mq.applied and not force:
3124 haspatches = True
3124 haspatches = True
3125 if revs:
3125 if revs:
3126 # Assume applied patches have no non-patch descendants
3126 # Assume applied patches have no non-patch descendants
3127 # and are not on remote already. If they appear in the
3127 # and are not on remote already. If they appear in the
3128 # set of resolved 'revs', bail out.
3128 # set of resolved 'revs', bail out.
3129 applied = set(e.node for e in self.mq.applied)
3129 applied = set(e.node for e in self.mq.applied)
3130 haspatches = bool([n for n in revs if n in applied])
3130 haspatches = bool([n for n in revs if n in applied])
3131 if haspatches:
3131 if haspatches:
3132 raise util.Abort(_('source has mq patches applied'))
3132 raise util.Abort(_('source has mq patches applied'))
3133 super(mqrepo, self).checkpush(force, revs)
3133 super(mqrepo, self).checkpush(force, revs)
3134
3134
3135 def _findtags(self):
3135 def _findtags(self):
3136 '''augment tags from base class with patch tags'''
3136 '''augment tags from base class with patch tags'''
3137 result = super(mqrepo, self)._findtags()
3137 result = super(mqrepo, self)._findtags()
3138
3138
3139 q = self.mq
3139 q = self.mq
3140 if not q.applied:
3140 if not q.applied:
3141 return result
3141 return result
3142
3142
3143 mqtags = [(patch.node, patch.name) for patch in q.applied]
3143 mqtags = [(patch.node, patch.name) for patch in q.applied]
3144
3144
3145 try:
3145 try:
3146 self.changelog.rev(mqtags[-1][0])
3146 self.changelog.rev(mqtags[-1][0])
3147 except error.LookupError:
3147 except error.LookupError:
3148 self.ui.warn(_('mq status file refers to unknown node %s\n')
3148 self.ui.warn(_('mq status file refers to unknown node %s\n')
3149 % short(mqtags[-1][0]))
3149 % short(mqtags[-1][0]))
3150 return result
3150 return result
3151
3151
3152 mqtags.append((mqtags[-1][0], 'qtip'))
3152 mqtags.append((mqtags[-1][0], 'qtip'))
3153 mqtags.append((mqtags[0][0], 'qbase'))
3153 mqtags.append((mqtags[0][0], 'qbase'))
3154 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3154 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3155 tags = result[0]
3155 tags = result[0]
3156 for patch in mqtags:
3156 for patch in mqtags:
3157 if patch[1] in tags:
3157 if patch[1] in tags:
3158 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
3158 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
3159 % patch[1])
3159 % patch[1])
3160 else:
3160 else:
3161 tags[patch[1]] = patch[0]
3161 tags[patch[1]] = patch[0]
3162
3162
3163 return result
3163 return result
3164
3164
3165 def _branchtags(self, partial, lrev):
3165 def _branchtags(self, partial, lrev):
3166 q = self.mq
3166 q = self.mq
3167 if not q.applied:
3167 if not q.applied:
3168 return super(mqrepo, self)._branchtags(partial, lrev)
3168 return super(mqrepo, self)._branchtags(partial, lrev)
3169
3169
3170 cl = self.changelog
3170 cl = self.changelog
3171 qbasenode = q.applied[0].node
3171 qbasenode = q.applied[0].node
3172 try:
3172 try:
3173 qbase = cl.rev(qbasenode)
3173 qbase = cl.rev(qbasenode)
3174 except error.LookupError:
3174 except error.LookupError:
3175 self.ui.warn(_('mq status file refers to unknown node %s\n')
3175 self.ui.warn(_('mq status file refers to unknown node %s\n')
3176 % short(qbasenode))
3176 % short(qbasenode))
3177 return super(mqrepo, self)._branchtags(partial, lrev)
3177 return super(mqrepo, self)._branchtags(partial, lrev)
3178
3178
3179 start = lrev + 1
3179 start = lrev + 1
3180 if start < qbase:
3180 if start < qbase:
3181 # update the cache (excluding the patches) and save it
3181 # update the cache (excluding the patches) and save it
3182 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3182 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3183 self._updatebranchcache(partial, ctxgen)
3183 self._updatebranchcache(partial, ctxgen)
3184 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3184 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3185 start = qbase
3185 start = qbase
3186 # if start = qbase, the cache is as updated as it should be.
3186 # if start = qbase, the cache is as updated as it should be.
3187 # if start > qbase, the cache includes (part of) the patches.
3187 # if start > qbase, the cache includes (part of) the patches.
3188 # we might as well use it, but we won't save it.
3188 # we might as well use it, but we won't save it.
3189
3189
3190 # update the cache up to the tip
3190 # update the cache up to the tip
3191 ctxgen = (self[r] for r in xrange(start, len(cl)))
3191 ctxgen = (self[r] for r in xrange(start, len(cl)))
3192 self._updatebranchcache(partial, ctxgen)
3192 self._updatebranchcache(partial, ctxgen)
3193
3193
3194 return partial
3194 return partial
3195
3195
3196 if repo.local():
3196 if repo.local():
3197 repo.__class__ = mqrepo
3197 repo.__class__ = mqrepo
3198
3198
3199 def mqimport(orig, ui, repo, *args, **kwargs):
3199 def mqimport(orig, ui, repo, *args, **kwargs):
3200 if (hasattr(repo, 'abortifwdirpatched')
3200 if (hasattr(repo, 'abortifwdirpatched')
3201 and not kwargs.get('no_commit', False)):
3201 and not kwargs.get('no_commit', False)):
3202 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3202 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3203 kwargs.get('force'))
3203 kwargs.get('force'))
3204 return orig(ui, repo, *args, **kwargs)
3204 return orig(ui, repo, *args, **kwargs)
3205
3205
3206 def mqinit(orig, ui, *args, **kwargs):
3206 def mqinit(orig, ui, *args, **kwargs):
3207 mq = kwargs.pop('mq', None)
3207 mq = kwargs.pop('mq', None)
3208
3208
3209 if not mq:
3209 if not mq:
3210 return orig(ui, *args, **kwargs)
3210 return orig(ui, *args, **kwargs)
3211
3211
3212 if args:
3212 if args:
3213 repopath = args[0]
3213 repopath = args[0]
3214 if not hg.islocal(repopath):
3214 if not hg.islocal(repopath):
3215 raise util.Abort(_('only a local queue repository '
3215 raise util.Abort(_('only a local queue repository '
3216 'may be initialized'))
3216 'may be initialized'))
3217 else:
3217 else:
3218 repopath = cmdutil.findrepo(os.getcwd())
3218 repopath = cmdutil.findrepo(os.getcwd())
3219 if not repopath:
3219 if not repopath:
3220 raise util.Abort(_('there is no Mercurial repository here '
3220 raise util.Abort(_('there is no Mercurial repository here '
3221 '(.hg not found)'))
3221 '(.hg not found)'))
3222 repo = hg.repository(ui, repopath)
3222 repo = hg.repository(ui, repopath)
3223 return qinit(ui, repo, True)
3223 return qinit(ui, repo, True)
3224
3224
3225 def mqcommand(orig, ui, repo, *args, **kwargs):
3225 def mqcommand(orig, ui, repo, *args, **kwargs):
3226 """Add --mq option to operate on patch repository instead of main"""
3226 """Add --mq option to operate on patch repository instead of main"""
3227
3227
3228 # some commands do not like getting unknown options
3228 # some commands do not like getting unknown options
3229 mq = kwargs.pop('mq', None)
3229 mq = kwargs.pop('mq', None)
3230
3230
3231 if not mq:
3231 if not mq:
3232 return orig(ui, repo, *args, **kwargs)
3232 return orig(ui, repo, *args, **kwargs)
3233
3233
3234 q = repo.mq
3234 q = repo.mq
3235 r = q.qrepo()
3235 r = q.qrepo()
3236 if not r:
3236 if not r:
3237 raise util.Abort(_('no queue repository'))
3237 raise util.Abort(_('no queue repository'))
3238 return orig(r.ui, r, *args, **kwargs)
3238 return orig(r.ui, r, *args, **kwargs)
3239
3239
3240 def summary(orig, ui, repo, *args, **kwargs):
3240 def summary(orig, ui, repo, *args, **kwargs):
3241 r = orig(ui, repo, *args, **kwargs)
3241 r = orig(ui, repo, *args, **kwargs)
3242 q = repo.mq
3242 q = repo.mq
3243 m = []
3243 m = []
3244 a, u = len(q.applied), len(q.unapplied(repo))
3244 a, u = len(q.applied), len(q.unapplied(repo))
3245 if a:
3245 if a:
3246 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3246 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3247 if u:
3247 if u:
3248 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3248 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3249 if m:
3249 if m:
3250 ui.write("mq: %s\n" % ', '.join(m))
3250 ui.write("mq: %s\n" % ', '.join(m))
3251 else:
3251 else:
3252 ui.note(_("mq: (empty queue)\n"))
3252 ui.note(_("mq: (empty queue)\n"))
3253 return r
3253 return r
3254
3254
3255 def revsetmq(repo, subset, x):
3255 def revsetmq(repo, subset, x):
3256 """``mq()``
3256 """``mq()``
3257 Changesets managed by MQ.
3257 Changesets managed by MQ.
3258 """
3258 """
3259 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3259 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3260 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3260 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3261 return [r for r in subset if r in applied]
3261 return [r for r in subset if r in applied]
3262
3262
3263 def extsetup(ui):
3263 def extsetup(ui):
3264 revset.symbols['mq'] = revsetmq
3264 revset.symbols['mq'] = revsetmq
3265
3265
3266 # tell hggettext to extract docstrings from these functions:
3266 # tell hggettext to extract docstrings from these functions:
3267 i18nfunctions = [revsetmq]
3267 i18nfunctions = [revsetmq]
3268
3268
3269 def uisetup(ui):
3269 def uisetup(ui):
3270 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3270 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3271
3271
3272 extensions.wrapcommand(commands.table, 'import', mqimport)
3272 extensions.wrapcommand(commands.table, 'import', mqimport)
3273 extensions.wrapcommand(commands.table, 'summary', summary)
3273 extensions.wrapcommand(commands.table, 'summary', summary)
3274
3274
3275 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3275 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3276 entry[1].extend(mqopt)
3276 entry[1].extend(mqopt)
3277
3277
3278 nowrap = set(commands.norepo.split(" "))
3278 nowrap = set(commands.norepo.split(" "))
3279
3279
3280 def dotable(cmdtable):
3280 def dotable(cmdtable):
3281 for cmd in cmdtable.keys():
3281 for cmd in cmdtable.keys():
3282 cmd = cmdutil.parsealiases(cmd)[0]
3282 cmd = cmdutil.parsealiases(cmd)[0]
3283 if cmd in nowrap:
3283 if cmd in nowrap:
3284 continue
3284 continue
3285 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3285 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3286 entry[1].extend(mqopt)
3286 entry[1].extend(mqopt)
3287
3287
3288 dotable(commands.table)
3288 dotable(commands.table)
3289
3289
3290 for extname, extmodule in extensions.extensions():
3290 for extname, extmodule in extensions.extensions():
3291 if extmodule.__file__ != __file__:
3291 if extmodule.__file__ != __file__:
3292 dotable(getattr(extmodule, 'cmdtable', {}))
3292 dotable(getattr(extmodule, 'cmdtable', {}))
3293
3293
3294
3294
3295 colortable = {'qguard.negative': 'red',
3295 colortable = {'qguard.negative': 'red',
3296 'qguard.positive': 'yellow',
3296 'qguard.positive': 'yellow',
3297 'qguard.unguarded': 'green',
3297 'qguard.unguarded': 'green',
3298 'qseries.applied': 'blue bold underline',
3298 'qseries.applied': 'blue bold underline',
3299 'qseries.guarded': 'black bold',
3299 'qseries.guarded': 'black bold',
3300 'qseries.missing': 'red bold',
3300 'qseries.missing': 'red bold',
3301 'qseries.unapplied': 'black bold'}
3301 'qseries.unapplied': 'black bold'}
@@ -1,110 +1,110 b''
1 # Copyright (C) 2006 - Marco Barisione <marco@barisione.org>
1 # Copyright (C) 2006 - Marco Barisione <marco@barisione.org>
2 #
2 #
3 # This is a small extension for Mercurial (http://mercurial.selenic.com/)
3 # This is a small extension for Mercurial (http://mercurial.selenic.com/)
4 # that removes files not known to mercurial
4 # that removes files not known to mercurial
5 #
5 #
6 # This program was inspired by the "cvspurge" script contained in CVS
6 # This program was inspired by the "cvspurge" script contained in CVS
7 # utilities (http://www.red-bean.com/cvsutils/).
7 # utilities (http://www.red-bean.com/cvsutils/).
8 #
8 #
9 # For help on the usage of "hg purge" use:
9 # For help on the usage of "hg purge" use:
10 # hg help purge
10 # hg help purge
11 #
11 #
12 # This program is free software; you can redistribute it and/or modify
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 2 of the License, or
14 # the Free Software Foundation; either version 2 of the License, or
15 # (at your option) any later version.
15 # (at your option) any later version.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
25
26 '''command to delete untracked files from the working directory'''
26 '''command to delete untracked files from the working directory'''
27
27
28 from mercurial import util, commands, cmdutil, scmutil
28 from mercurial import util, commands, cmdutil, scmutil
29 from mercurial.i18n import _
29 from mercurial.i18n import _
30 import os, stat
30 import os, stat
31
31
32 cmdtable = {}
32 cmdtable = {}
33 command = cmdutil.command(cmdtable)
33 command = cmdutil.command(cmdtable)
34
34
35 @command('purge|clean',
35 @command('purge|clean',
36 [('a', 'abort-on-err', None, _('abort if an error occurs')),
36 [('a', 'abort-on-err', None, _('abort if an error occurs')),
37 ('', 'all', None, _('purge ignored files too')),
37 ('', 'all', None, _('purge ignored files too')),
38 ('p', 'print', None, _('print filenames instead of deleting them')),
38 ('p', 'print', None, _('print filenames instead of deleting them')),
39 ('0', 'print0', None, _('end filenames with NUL, for use with xargs'
39 ('0', 'print0', None, _('end filenames with NUL, for use with xargs'
40 ' (implies -p/--print)')),
40 ' (implies -p/--print)')),
41 ] + commands.walkopts,
41 ] + commands.walkopts,
42 _('hg purge [OPTION]... [DIR]...'))
42 _('hg purge [OPTION]... [DIR]...'))
43 def purge(ui, repo, *dirs, **opts):
43 def purge(ui, repo, *dirs, **opts):
44 '''removes files not tracked by Mercurial
44 '''removes files not tracked by Mercurial
45
45
46 Delete files not known to Mercurial. This is useful to test local
46 Delete files not known to Mercurial. This is useful to test local
47 and uncommitted changes in an otherwise-clean source tree.
47 and uncommitted changes in an otherwise-clean source tree.
48
48
49 This means that purge will delete:
49 This means that purge will delete:
50
50
51 - Unknown files: files marked with "?" by :hg:`status`
51 - Unknown files: files marked with "?" by :hg:`status`
52 - Empty directories: in fact Mercurial ignores directories unless
52 - Empty directories: in fact Mercurial ignores directories unless
53 they contain files under source control management
53 they contain files under source control management
54
54
55 But it will leave untouched:
55 But it will leave untouched:
56
56
57 - Modified and unmodified tracked files
57 - Modified and unmodified tracked files
58 - Ignored files (unless --all is specified)
58 - Ignored files (unless --all is specified)
59 - New files added to the repository (with :hg:`add`)
59 - New files added to the repository (with :hg:`add`)
60
60
61 If directories are given on the command line, only files in these
61 If directories are given on the command line, only files in these
62 directories are considered.
62 directories are considered.
63
63
64 Be careful with purge, as you could irreversibly delete some files
64 Be careful with purge, as you could irreversibly delete some files
65 you forgot to add to the repository. If you only want to print the
65 you forgot to add to the repository. If you only want to print the
66 list of files that this program would delete, use the --print
66 list of files that this program would delete, use the --print
67 option.
67 option.
68 '''
68 '''
69 act = not opts['print']
69 act = not opts['print']
70 eol = '\n'
70 eol = '\n'
71 if opts['print0']:
71 if opts['print0']:
72 eol = '\0'
72 eol = '\0'
73 act = False # --print0 implies --print
73 act = False # --print0 implies --print
74
74
75 def remove(remove_func, name):
75 def remove(remove_func, name):
76 if act:
76 if act:
77 try:
77 try:
78 remove_func(repo.wjoin(name))
78 remove_func(repo.wjoin(name))
79 except OSError:
79 except OSError:
80 m = _('%s cannot be removed') % name
80 m = _('%s cannot be removed') % name
81 if opts['abort_on_err']:
81 if opts['abort_on_err']:
82 raise util.Abort(m)
82 raise util.Abort(m)
83 ui.warn(_('warning: %s\n') % m)
83 ui.warn(_('warning: %s\n') % m)
84 else:
84 else:
85 ui.write('%s%s' % (name, eol))
85 ui.write('%s%s' % (name, eol))
86
86
87 def removefile(path):
87 def removefile(path):
88 try:
88 try:
89 os.remove(path)
89 os.remove(path)
90 except OSError:
90 except OSError:
91 # read-only files cannot be unlinked under Windows
91 # read-only files cannot be unlinked under Windows
92 s = os.stat(path)
92 s = os.stat(path)
93 if (s.st_mode & stat.S_IWRITE) != 0:
93 if (s.st_mode & stat.S_IWRITE) != 0:
94 raise
94 raise
95 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
95 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
96 os.remove(path)
96 os.remove(path)
97
97
98 directories = []
98 directories = []
99 match = scmutil.match(repo, dirs, opts)
99 match = scmutil.match(repo[None], dirs, opts)
100 match.dir = directories.append
100 match.dir = directories.append
101 status = repo.status(match=match, ignored=opts['all'], unknown=True)
101 status = repo.status(match=match, ignored=opts['all'], unknown=True)
102
102
103 for f in sorted(status[4] + status[5]):
103 for f in sorted(status[4] + status[5]):
104 ui.note(_('Removing file %s\n') % f)
104 ui.note(_('Removing file %s\n') % f)
105 remove(removefile, f)
105 remove(removefile, f)
106
106
107 for f in sorted(directories, reverse=True):
107 for f in sorted(directories, reverse=True):
108 if match(f) and not os.listdir(repo.wjoin(f)):
108 if match(f) and not os.listdir(repo.wjoin(f)):
109 ui.note(_('Removing directory %s\n') % f)
109 ui.note(_('Removing directory %s\n') % f)
110 remove(os.rmdir, f)
110 remove(os.rmdir, f)
@@ -1,1243 +1,1244 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile
10 import os, sys, errno, re, tempfile
11 import util, scmutil, templater, patch, error, templatekw, revlog
11 import util, scmutil, templater, patch, error, templatekw, revlog
12 import match as matchmod
12 import match as matchmod
13 import subrepo
13 import subrepo
14
14
15 def parsealiases(cmd):
15 def parsealiases(cmd):
16 return cmd.lstrip("^").split("|")
16 return cmd.lstrip("^").split("|")
17
17
18 def findpossible(cmd, table, strict=False):
18 def findpossible(cmd, table, strict=False):
19 """
19 """
20 Return cmd -> (aliases, command table entry)
20 Return cmd -> (aliases, command table entry)
21 for each matching command.
21 for each matching command.
22 Return debug commands (or their aliases) only if no normal command matches.
22 Return debug commands (or their aliases) only if no normal command matches.
23 """
23 """
24 choice = {}
24 choice = {}
25 debugchoice = {}
25 debugchoice = {}
26 for e in table.keys():
26 for e in table.keys():
27 aliases = parsealiases(e)
27 aliases = parsealiases(e)
28 found = None
28 found = None
29 if cmd in aliases:
29 if cmd in aliases:
30 found = cmd
30 found = cmd
31 elif not strict:
31 elif not strict:
32 for a in aliases:
32 for a in aliases:
33 if a.startswith(cmd):
33 if a.startswith(cmd):
34 found = a
34 found = a
35 break
35 break
36 if found is not None:
36 if found is not None:
37 if aliases[0].startswith("debug") or found.startswith("debug"):
37 if aliases[0].startswith("debug") or found.startswith("debug"):
38 debugchoice[found] = (aliases, table[e])
38 debugchoice[found] = (aliases, table[e])
39 else:
39 else:
40 choice[found] = (aliases, table[e])
40 choice[found] = (aliases, table[e])
41
41
42 if not choice and debugchoice:
42 if not choice and debugchoice:
43 choice = debugchoice
43 choice = debugchoice
44
44
45 return choice
45 return choice
46
46
47 def findcmd(cmd, table, strict=True):
47 def findcmd(cmd, table, strict=True):
48 """Return (aliases, command table entry) for command string."""
48 """Return (aliases, command table entry) for command string."""
49 choice = findpossible(cmd, table, strict)
49 choice = findpossible(cmd, table, strict)
50
50
51 if cmd in choice:
51 if cmd in choice:
52 return choice[cmd]
52 return choice[cmd]
53
53
54 if len(choice) > 1:
54 if len(choice) > 1:
55 clist = choice.keys()
55 clist = choice.keys()
56 clist.sort()
56 clist.sort()
57 raise error.AmbiguousCommand(cmd, clist)
57 raise error.AmbiguousCommand(cmd, clist)
58
58
59 if choice:
59 if choice:
60 return choice.values()[0]
60 return choice.values()[0]
61
61
62 raise error.UnknownCommand(cmd)
62 raise error.UnknownCommand(cmd)
63
63
64 def findrepo(p):
64 def findrepo(p):
65 while not os.path.isdir(os.path.join(p, ".hg")):
65 while not os.path.isdir(os.path.join(p, ".hg")):
66 oldp, p = p, os.path.dirname(p)
66 oldp, p = p, os.path.dirname(p)
67 if p == oldp:
67 if p == oldp:
68 return None
68 return None
69
69
70 return p
70 return p
71
71
72 def bailifchanged(repo):
72 def bailifchanged(repo):
73 if repo.dirstate.p2() != nullid:
73 if repo.dirstate.p2() != nullid:
74 raise util.Abort(_('outstanding uncommitted merge'))
74 raise util.Abort(_('outstanding uncommitted merge'))
75 modified, added, removed, deleted = repo.status()[:4]
75 modified, added, removed, deleted = repo.status()[:4]
76 if modified or added or removed or deleted:
76 if modified or added or removed or deleted:
77 raise util.Abort(_("outstanding uncommitted changes"))
77 raise util.Abort(_("outstanding uncommitted changes"))
78
78
79 def logmessage(ui, opts):
79 def logmessage(ui, opts):
80 """ get the log message according to -m and -l option """
80 """ get the log message according to -m and -l option """
81 message = opts.get('message')
81 message = opts.get('message')
82 logfile = opts.get('logfile')
82 logfile = opts.get('logfile')
83
83
84 if message and logfile:
84 if message and logfile:
85 raise util.Abort(_('options --message and --logfile are mutually '
85 raise util.Abort(_('options --message and --logfile are mutually '
86 'exclusive'))
86 'exclusive'))
87 if not message and logfile:
87 if not message and logfile:
88 try:
88 try:
89 if logfile == '-':
89 if logfile == '-':
90 message = ui.fin.read()
90 message = ui.fin.read()
91 else:
91 else:
92 message = '\n'.join(util.readfile(logfile).splitlines())
92 message = '\n'.join(util.readfile(logfile).splitlines())
93 except IOError, inst:
93 except IOError, inst:
94 raise util.Abort(_("can't read commit message '%s': %s") %
94 raise util.Abort(_("can't read commit message '%s': %s") %
95 (logfile, inst.strerror))
95 (logfile, inst.strerror))
96 return message
96 return message
97
97
98 def loglimit(opts):
98 def loglimit(opts):
99 """get the log limit according to option -l/--limit"""
99 """get the log limit according to option -l/--limit"""
100 limit = opts.get('limit')
100 limit = opts.get('limit')
101 if limit:
101 if limit:
102 try:
102 try:
103 limit = int(limit)
103 limit = int(limit)
104 except ValueError:
104 except ValueError:
105 raise util.Abort(_('limit must be a positive integer'))
105 raise util.Abort(_('limit must be a positive integer'))
106 if limit <= 0:
106 if limit <= 0:
107 raise util.Abort(_('limit must be positive'))
107 raise util.Abort(_('limit must be positive'))
108 else:
108 else:
109 limit = None
109 limit = None
110 return limit
110 return limit
111
111
112 def makefilename(repo, pat, node,
112 def makefilename(repo, pat, node,
113 total=None, seqno=None, revwidth=None, pathname=None):
113 total=None, seqno=None, revwidth=None, pathname=None):
114 node_expander = {
114 node_expander = {
115 'H': lambda: hex(node),
115 'H': lambda: hex(node),
116 'R': lambda: str(repo.changelog.rev(node)),
116 'R': lambda: str(repo.changelog.rev(node)),
117 'h': lambda: short(node),
117 'h': lambda: short(node),
118 }
118 }
119 expander = {
119 expander = {
120 '%': lambda: '%',
120 '%': lambda: '%',
121 'b': lambda: os.path.basename(repo.root),
121 'b': lambda: os.path.basename(repo.root),
122 }
122 }
123
123
124 try:
124 try:
125 if node:
125 if node:
126 expander.update(node_expander)
126 expander.update(node_expander)
127 if node:
127 if node:
128 expander['r'] = (lambda:
128 expander['r'] = (lambda:
129 str(repo.changelog.rev(node)).zfill(revwidth or 0))
129 str(repo.changelog.rev(node)).zfill(revwidth or 0))
130 if total is not None:
130 if total is not None:
131 expander['N'] = lambda: str(total)
131 expander['N'] = lambda: str(total)
132 if seqno is not None:
132 if seqno is not None:
133 expander['n'] = lambda: str(seqno)
133 expander['n'] = lambda: str(seqno)
134 if total is not None and seqno is not None:
134 if total is not None and seqno is not None:
135 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
135 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
136 if pathname is not None:
136 if pathname is not None:
137 expander['s'] = lambda: os.path.basename(pathname)
137 expander['s'] = lambda: os.path.basename(pathname)
138 expander['d'] = lambda: os.path.dirname(pathname) or '.'
138 expander['d'] = lambda: os.path.dirname(pathname) or '.'
139 expander['p'] = lambda: pathname
139 expander['p'] = lambda: pathname
140
140
141 newname = []
141 newname = []
142 patlen = len(pat)
142 patlen = len(pat)
143 i = 0
143 i = 0
144 while i < patlen:
144 while i < patlen:
145 c = pat[i]
145 c = pat[i]
146 if c == '%':
146 if c == '%':
147 i += 1
147 i += 1
148 c = pat[i]
148 c = pat[i]
149 c = expander[c]()
149 c = expander[c]()
150 newname.append(c)
150 newname.append(c)
151 i += 1
151 i += 1
152 return ''.join(newname)
152 return ''.join(newname)
153 except KeyError, inst:
153 except KeyError, inst:
154 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
154 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
155 inst.args[0])
155 inst.args[0])
156
156
157 def makefileobj(repo, pat, node=None, total=None,
157 def makefileobj(repo, pat, node=None, total=None,
158 seqno=None, revwidth=None, mode='wb', pathname=None):
158 seqno=None, revwidth=None, mode='wb', pathname=None):
159
159
160 writable = mode not in ('r', 'rb')
160 writable = mode not in ('r', 'rb')
161
161
162 if not pat or pat == '-':
162 if not pat or pat == '-':
163 fp = writable and repo.ui.fout or repo.ui.fin
163 fp = writable and repo.ui.fout or repo.ui.fin
164 if hasattr(fp, 'fileno'):
164 if hasattr(fp, 'fileno'):
165 return os.fdopen(os.dup(fp.fileno()), mode)
165 return os.fdopen(os.dup(fp.fileno()), mode)
166 else:
166 else:
167 # if this fp can't be duped properly, return
167 # if this fp can't be duped properly, return
168 # a dummy object that can be closed
168 # a dummy object that can be closed
169 class wrappedfileobj(object):
169 class wrappedfileobj(object):
170 noop = lambda x: None
170 noop = lambda x: None
171 def __init__(self, f):
171 def __init__(self, f):
172 self.f = f
172 self.f = f
173 def __getattr__(self, attr):
173 def __getattr__(self, attr):
174 if attr == 'close':
174 if attr == 'close':
175 return self.noop
175 return self.noop
176 else:
176 else:
177 return getattr(self.f, attr)
177 return getattr(self.f, attr)
178
178
179 return wrappedfileobj(fp)
179 return wrappedfileobj(fp)
180 if hasattr(pat, 'write') and writable:
180 if hasattr(pat, 'write') and writable:
181 return pat
181 return pat
182 if hasattr(pat, 'read') and 'r' in mode:
182 if hasattr(pat, 'read') and 'r' in mode:
183 return pat
183 return pat
184 return open(makefilename(repo, pat, node, total, seqno, revwidth,
184 return open(makefilename(repo, pat, node, total, seqno, revwidth,
185 pathname),
185 pathname),
186 mode)
186 mode)
187
187
188 def openrevlog(repo, cmd, file_, opts):
188 def openrevlog(repo, cmd, file_, opts):
189 """opens the changelog, manifest, a filelog or a given revlog"""
189 """opens the changelog, manifest, a filelog or a given revlog"""
190 cl = opts['changelog']
190 cl = opts['changelog']
191 mf = opts['manifest']
191 mf = opts['manifest']
192 msg = None
192 msg = None
193 if cl and mf:
193 if cl and mf:
194 msg = _('cannot specify --changelog and --manifest at the same time')
194 msg = _('cannot specify --changelog and --manifest at the same time')
195 elif cl or mf:
195 elif cl or mf:
196 if file_:
196 if file_:
197 msg = _('cannot specify filename with --changelog or --manifest')
197 msg = _('cannot specify filename with --changelog or --manifest')
198 elif not repo:
198 elif not repo:
199 msg = _('cannot specify --changelog or --manifest '
199 msg = _('cannot specify --changelog or --manifest '
200 'without a repository')
200 'without a repository')
201 if msg:
201 if msg:
202 raise util.Abort(msg)
202 raise util.Abort(msg)
203
203
204 r = None
204 r = None
205 if repo:
205 if repo:
206 if cl:
206 if cl:
207 r = repo.changelog
207 r = repo.changelog
208 elif mf:
208 elif mf:
209 r = repo.manifest
209 r = repo.manifest
210 elif file_:
210 elif file_:
211 filelog = repo.file(file_)
211 filelog = repo.file(file_)
212 if len(filelog):
212 if len(filelog):
213 r = filelog
213 r = filelog
214 if not r:
214 if not r:
215 if not file_:
215 if not file_:
216 raise error.CommandError(cmd, _('invalid arguments'))
216 raise error.CommandError(cmd, _('invalid arguments'))
217 if not os.path.isfile(file_):
217 if not os.path.isfile(file_):
218 raise util.Abort(_("revlog '%s' not found") % file_)
218 raise util.Abort(_("revlog '%s' not found") % file_)
219 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
219 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
220 file_[:-2] + ".i")
220 file_[:-2] + ".i")
221 return r
221 return r
222
222
223 def copy(ui, repo, pats, opts, rename=False):
223 def copy(ui, repo, pats, opts, rename=False):
224 # called with the repo lock held
224 # called with the repo lock held
225 #
225 #
226 # hgsep => pathname that uses "/" to separate directories
226 # hgsep => pathname that uses "/" to separate directories
227 # ossep => pathname that uses os.sep to separate directories
227 # ossep => pathname that uses os.sep to separate directories
228 cwd = repo.getcwd()
228 cwd = repo.getcwd()
229 targets = {}
229 targets = {}
230 after = opts.get("after")
230 after = opts.get("after")
231 dryrun = opts.get("dry_run")
231 dryrun = opts.get("dry_run")
232 wctx = repo[None]
232 wctx = repo[None]
233
233
234 def walkpat(pat):
234 def walkpat(pat):
235 srcs = []
235 srcs = []
236 badstates = after and '?' or '?r'
236 badstates = after and '?' or '?r'
237 m = scmutil.match(repo, [pat], opts, globbed=True)
237 m = scmutil.match(repo[None], [pat], opts, globbed=True)
238 for abs in repo.walk(m):
238 for abs in repo.walk(m):
239 state = repo.dirstate[abs]
239 state = repo.dirstate[abs]
240 rel = m.rel(abs)
240 rel = m.rel(abs)
241 exact = m.exact(abs)
241 exact = m.exact(abs)
242 if state in badstates:
242 if state in badstates:
243 if exact and state == '?':
243 if exact and state == '?':
244 ui.warn(_('%s: not copying - file is not managed\n') % rel)
244 ui.warn(_('%s: not copying - file is not managed\n') % rel)
245 if exact and state == 'r':
245 if exact and state == 'r':
246 ui.warn(_('%s: not copying - file has been marked for'
246 ui.warn(_('%s: not copying - file has been marked for'
247 ' remove\n') % rel)
247 ' remove\n') % rel)
248 continue
248 continue
249 # abs: hgsep
249 # abs: hgsep
250 # rel: ossep
250 # rel: ossep
251 srcs.append((abs, rel, exact))
251 srcs.append((abs, rel, exact))
252 return srcs
252 return srcs
253
253
254 # abssrc: hgsep
254 # abssrc: hgsep
255 # relsrc: ossep
255 # relsrc: ossep
256 # otarget: ossep
256 # otarget: ossep
257 def copyfile(abssrc, relsrc, otarget, exact):
257 def copyfile(abssrc, relsrc, otarget, exact):
258 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
258 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
259 reltarget = repo.pathto(abstarget, cwd)
259 reltarget = repo.pathto(abstarget, cwd)
260 target = repo.wjoin(abstarget)
260 target = repo.wjoin(abstarget)
261 src = repo.wjoin(abssrc)
261 src = repo.wjoin(abssrc)
262 state = repo.dirstate[abstarget]
262 state = repo.dirstate[abstarget]
263
263
264 scmutil.checkportable(ui, abstarget)
264 scmutil.checkportable(ui, abstarget)
265
265
266 # check for collisions
266 # check for collisions
267 prevsrc = targets.get(abstarget)
267 prevsrc = targets.get(abstarget)
268 if prevsrc is not None:
268 if prevsrc is not None:
269 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
269 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
270 (reltarget, repo.pathto(abssrc, cwd),
270 (reltarget, repo.pathto(abssrc, cwd),
271 repo.pathto(prevsrc, cwd)))
271 repo.pathto(prevsrc, cwd)))
272 return
272 return
273
273
274 # check for overwrites
274 # check for overwrites
275 exists = os.path.lexists(target)
275 exists = os.path.lexists(target)
276 if not after and exists or after and state in 'mn':
276 if not after and exists or after and state in 'mn':
277 if not opts['force']:
277 if not opts['force']:
278 ui.warn(_('%s: not overwriting - file exists\n') %
278 ui.warn(_('%s: not overwriting - file exists\n') %
279 reltarget)
279 reltarget)
280 return
280 return
281
281
282 if after:
282 if after:
283 if not exists:
283 if not exists:
284 if rename:
284 if rename:
285 ui.warn(_('%s: not recording move - %s does not exist\n') %
285 ui.warn(_('%s: not recording move - %s does not exist\n') %
286 (relsrc, reltarget))
286 (relsrc, reltarget))
287 else:
287 else:
288 ui.warn(_('%s: not recording copy - %s does not exist\n') %
288 ui.warn(_('%s: not recording copy - %s does not exist\n') %
289 (relsrc, reltarget))
289 (relsrc, reltarget))
290 return
290 return
291 elif not dryrun:
291 elif not dryrun:
292 try:
292 try:
293 if exists:
293 if exists:
294 os.unlink(target)
294 os.unlink(target)
295 targetdir = os.path.dirname(target) or '.'
295 targetdir = os.path.dirname(target) or '.'
296 if not os.path.isdir(targetdir):
296 if not os.path.isdir(targetdir):
297 os.makedirs(targetdir)
297 os.makedirs(targetdir)
298 util.copyfile(src, target)
298 util.copyfile(src, target)
299 srcexists = True
299 srcexists = True
300 except IOError, inst:
300 except IOError, inst:
301 if inst.errno == errno.ENOENT:
301 if inst.errno == errno.ENOENT:
302 ui.warn(_('%s: deleted in working copy\n') % relsrc)
302 ui.warn(_('%s: deleted in working copy\n') % relsrc)
303 srcexists = False
303 srcexists = False
304 else:
304 else:
305 ui.warn(_('%s: cannot copy - %s\n') %
305 ui.warn(_('%s: cannot copy - %s\n') %
306 (relsrc, inst.strerror))
306 (relsrc, inst.strerror))
307 return True # report a failure
307 return True # report a failure
308
308
309 if ui.verbose or not exact:
309 if ui.verbose or not exact:
310 if rename:
310 if rename:
311 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
311 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
312 else:
312 else:
313 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
313 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
314
314
315 targets[abstarget] = abssrc
315 targets[abstarget] = abssrc
316
316
317 # fix up dirstate
317 # fix up dirstate
318 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
318 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
319 dryrun=dryrun, cwd=cwd)
319 dryrun=dryrun, cwd=cwd)
320 if rename and not dryrun:
320 if rename and not dryrun:
321 if not after and srcexists:
321 if not after and srcexists:
322 util.unlinkpath(repo.wjoin(abssrc))
322 util.unlinkpath(repo.wjoin(abssrc))
323 wctx.forget([abssrc])
323 wctx.forget([abssrc])
324
324
325 # pat: ossep
325 # pat: ossep
326 # dest ossep
326 # dest ossep
327 # srcs: list of (hgsep, hgsep, ossep, bool)
327 # srcs: list of (hgsep, hgsep, ossep, bool)
328 # return: function that takes hgsep and returns ossep
328 # return: function that takes hgsep and returns ossep
329 def targetpathfn(pat, dest, srcs):
329 def targetpathfn(pat, dest, srcs):
330 if os.path.isdir(pat):
330 if os.path.isdir(pat):
331 abspfx = scmutil.canonpath(repo.root, cwd, pat)
331 abspfx = scmutil.canonpath(repo.root, cwd, pat)
332 abspfx = util.localpath(abspfx)
332 abspfx = util.localpath(abspfx)
333 if destdirexists:
333 if destdirexists:
334 striplen = len(os.path.split(abspfx)[0])
334 striplen = len(os.path.split(abspfx)[0])
335 else:
335 else:
336 striplen = len(abspfx)
336 striplen = len(abspfx)
337 if striplen:
337 if striplen:
338 striplen += len(os.sep)
338 striplen += len(os.sep)
339 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
339 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
340 elif destdirexists:
340 elif destdirexists:
341 res = lambda p: os.path.join(dest,
341 res = lambda p: os.path.join(dest,
342 os.path.basename(util.localpath(p)))
342 os.path.basename(util.localpath(p)))
343 else:
343 else:
344 res = lambda p: dest
344 res = lambda p: dest
345 return res
345 return res
346
346
347 # pat: ossep
347 # pat: ossep
348 # dest ossep
348 # dest ossep
349 # srcs: list of (hgsep, hgsep, ossep, bool)
349 # srcs: list of (hgsep, hgsep, ossep, bool)
350 # return: function that takes hgsep and returns ossep
350 # return: function that takes hgsep and returns ossep
351 def targetpathafterfn(pat, dest, srcs):
351 def targetpathafterfn(pat, dest, srcs):
352 if matchmod.patkind(pat):
352 if matchmod.patkind(pat):
353 # a mercurial pattern
353 # a mercurial pattern
354 res = lambda p: os.path.join(dest,
354 res = lambda p: os.path.join(dest,
355 os.path.basename(util.localpath(p)))
355 os.path.basename(util.localpath(p)))
356 else:
356 else:
357 abspfx = scmutil.canonpath(repo.root, cwd, pat)
357 abspfx = scmutil.canonpath(repo.root, cwd, pat)
358 if len(abspfx) < len(srcs[0][0]):
358 if len(abspfx) < len(srcs[0][0]):
359 # A directory. Either the target path contains the last
359 # A directory. Either the target path contains the last
360 # component of the source path or it does not.
360 # component of the source path or it does not.
361 def evalpath(striplen):
361 def evalpath(striplen):
362 score = 0
362 score = 0
363 for s in srcs:
363 for s in srcs:
364 t = os.path.join(dest, util.localpath(s[0])[striplen:])
364 t = os.path.join(dest, util.localpath(s[0])[striplen:])
365 if os.path.lexists(t):
365 if os.path.lexists(t):
366 score += 1
366 score += 1
367 return score
367 return score
368
368
369 abspfx = util.localpath(abspfx)
369 abspfx = util.localpath(abspfx)
370 striplen = len(abspfx)
370 striplen = len(abspfx)
371 if striplen:
371 if striplen:
372 striplen += len(os.sep)
372 striplen += len(os.sep)
373 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
373 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
374 score = evalpath(striplen)
374 score = evalpath(striplen)
375 striplen1 = len(os.path.split(abspfx)[0])
375 striplen1 = len(os.path.split(abspfx)[0])
376 if striplen1:
376 if striplen1:
377 striplen1 += len(os.sep)
377 striplen1 += len(os.sep)
378 if evalpath(striplen1) > score:
378 if evalpath(striplen1) > score:
379 striplen = striplen1
379 striplen = striplen1
380 res = lambda p: os.path.join(dest,
380 res = lambda p: os.path.join(dest,
381 util.localpath(p)[striplen:])
381 util.localpath(p)[striplen:])
382 else:
382 else:
383 # a file
383 # a file
384 if destdirexists:
384 if destdirexists:
385 res = lambda p: os.path.join(dest,
385 res = lambda p: os.path.join(dest,
386 os.path.basename(util.localpath(p)))
386 os.path.basename(util.localpath(p)))
387 else:
387 else:
388 res = lambda p: dest
388 res = lambda p: dest
389 return res
389 return res
390
390
391
391
392 pats = scmutil.expandpats(pats)
392 pats = scmutil.expandpats(pats)
393 if not pats:
393 if not pats:
394 raise util.Abort(_('no source or destination specified'))
394 raise util.Abort(_('no source or destination specified'))
395 if len(pats) == 1:
395 if len(pats) == 1:
396 raise util.Abort(_('no destination specified'))
396 raise util.Abort(_('no destination specified'))
397 dest = pats.pop()
397 dest = pats.pop()
398 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
398 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
399 if not destdirexists:
399 if not destdirexists:
400 if len(pats) > 1 or matchmod.patkind(pats[0]):
400 if len(pats) > 1 or matchmod.patkind(pats[0]):
401 raise util.Abort(_('with multiple sources, destination must be an '
401 raise util.Abort(_('with multiple sources, destination must be an '
402 'existing directory'))
402 'existing directory'))
403 if util.endswithsep(dest):
403 if util.endswithsep(dest):
404 raise util.Abort(_('destination %s is not a directory') % dest)
404 raise util.Abort(_('destination %s is not a directory') % dest)
405
405
406 tfn = targetpathfn
406 tfn = targetpathfn
407 if after:
407 if after:
408 tfn = targetpathafterfn
408 tfn = targetpathafterfn
409 copylist = []
409 copylist = []
410 for pat in pats:
410 for pat in pats:
411 srcs = walkpat(pat)
411 srcs = walkpat(pat)
412 if not srcs:
412 if not srcs:
413 continue
413 continue
414 copylist.append((tfn(pat, dest, srcs), srcs))
414 copylist.append((tfn(pat, dest, srcs), srcs))
415 if not copylist:
415 if not copylist:
416 raise util.Abort(_('no files to copy'))
416 raise util.Abort(_('no files to copy'))
417
417
418 errors = 0
418 errors = 0
419 for targetpath, srcs in copylist:
419 for targetpath, srcs in copylist:
420 for abssrc, relsrc, exact in srcs:
420 for abssrc, relsrc, exact in srcs:
421 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
421 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
422 errors += 1
422 errors += 1
423
423
424 if errors:
424 if errors:
425 ui.warn(_('(consider using --after)\n'))
425 ui.warn(_('(consider using --after)\n'))
426
426
427 return errors != 0
427 return errors != 0
428
428
429 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
429 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
430 runargs=None, appendpid=False):
430 runargs=None, appendpid=False):
431 '''Run a command as a service.'''
431 '''Run a command as a service.'''
432
432
433 if opts['daemon'] and not opts['daemon_pipefds']:
433 if opts['daemon'] and not opts['daemon_pipefds']:
434 # Signal child process startup with file removal
434 # Signal child process startup with file removal
435 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
435 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
436 os.close(lockfd)
436 os.close(lockfd)
437 try:
437 try:
438 if not runargs:
438 if not runargs:
439 runargs = util.hgcmd() + sys.argv[1:]
439 runargs = util.hgcmd() + sys.argv[1:]
440 runargs.append('--daemon-pipefds=%s' % lockpath)
440 runargs.append('--daemon-pipefds=%s' % lockpath)
441 # Don't pass --cwd to the child process, because we've already
441 # Don't pass --cwd to the child process, because we've already
442 # changed directory.
442 # changed directory.
443 for i in xrange(1, len(runargs)):
443 for i in xrange(1, len(runargs)):
444 if runargs[i].startswith('--cwd='):
444 if runargs[i].startswith('--cwd='):
445 del runargs[i]
445 del runargs[i]
446 break
446 break
447 elif runargs[i].startswith('--cwd'):
447 elif runargs[i].startswith('--cwd'):
448 del runargs[i:i + 2]
448 del runargs[i:i + 2]
449 break
449 break
450 def condfn():
450 def condfn():
451 return not os.path.exists(lockpath)
451 return not os.path.exists(lockpath)
452 pid = util.rundetached(runargs, condfn)
452 pid = util.rundetached(runargs, condfn)
453 if pid < 0:
453 if pid < 0:
454 raise util.Abort(_('child process failed to start'))
454 raise util.Abort(_('child process failed to start'))
455 finally:
455 finally:
456 try:
456 try:
457 os.unlink(lockpath)
457 os.unlink(lockpath)
458 except OSError, e:
458 except OSError, e:
459 if e.errno != errno.ENOENT:
459 if e.errno != errno.ENOENT:
460 raise
460 raise
461 if parentfn:
461 if parentfn:
462 return parentfn(pid)
462 return parentfn(pid)
463 else:
463 else:
464 return
464 return
465
465
466 if initfn:
466 if initfn:
467 initfn()
467 initfn()
468
468
469 if opts['pid_file']:
469 if opts['pid_file']:
470 mode = appendpid and 'a' or 'w'
470 mode = appendpid and 'a' or 'w'
471 fp = open(opts['pid_file'], mode)
471 fp = open(opts['pid_file'], mode)
472 fp.write(str(os.getpid()) + '\n')
472 fp.write(str(os.getpid()) + '\n')
473 fp.close()
473 fp.close()
474
474
475 if opts['daemon_pipefds']:
475 if opts['daemon_pipefds']:
476 lockpath = opts['daemon_pipefds']
476 lockpath = opts['daemon_pipefds']
477 try:
477 try:
478 os.setsid()
478 os.setsid()
479 except AttributeError:
479 except AttributeError:
480 pass
480 pass
481 os.unlink(lockpath)
481 os.unlink(lockpath)
482 util.hidewindow()
482 util.hidewindow()
483 sys.stdout.flush()
483 sys.stdout.flush()
484 sys.stderr.flush()
484 sys.stderr.flush()
485
485
486 nullfd = os.open(util.nulldev, os.O_RDWR)
486 nullfd = os.open(util.nulldev, os.O_RDWR)
487 logfilefd = nullfd
487 logfilefd = nullfd
488 if logfile:
488 if logfile:
489 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
489 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
490 os.dup2(nullfd, 0)
490 os.dup2(nullfd, 0)
491 os.dup2(logfilefd, 1)
491 os.dup2(logfilefd, 1)
492 os.dup2(logfilefd, 2)
492 os.dup2(logfilefd, 2)
493 if nullfd not in (0, 1, 2):
493 if nullfd not in (0, 1, 2):
494 os.close(nullfd)
494 os.close(nullfd)
495 if logfile and logfilefd not in (0, 1, 2):
495 if logfile and logfilefd not in (0, 1, 2):
496 os.close(logfilefd)
496 os.close(logfilefd)
497
497
498 if runfn:
498 if runfn:
499 return runfn()
499 return runfn()
500
500
501 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
501 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
502 opts=None):
502 opts=None):
503 '''export changesets as hg patches.'''
503 '''export changesets as hg patches.'''
504
504
505 total = len(revs)
505 total = len(revs)
506 revwidth = max([len(str(rev)) for rev in revs])
506 revwidth = max([len(str(rev)) for rev in revs])
507
507
508 def single(rev, seqno, fp):
508 def single(rev, seqno, fp):
509 ctx = repo[rev]
509 ctx = repo[rev]
510 node = ctx.node()
510 node = ctx.node()
511 parents = [p.node() for p in ctx.parents() if p]
511 parents = [p.node() for p in ctx.parents() if p]
512 branch = ctx.branch()
512 branch = ctx.branch()
513 if switch_parent:
513 if switch_parent:
514 parents.reverse()
514 parents.reverse()
515 prev = (parents and parents[0]) or nullid
515 prev = (parents and parents[0]) or nullid
516
516
517 shouldclose = False
517 shouldclose = False
518 if not fp:
518 if not fp:
519 fp = makefileobj(repo, template, node, total=total, seqno=seqno,
519 fp = makefileobj(repo, template, node, total=total, seqno=seqno,
520 revwidth=revwidth, mode='ab')
520 revwidth=revwidth, mode='ab')
521 if fp != template:
521 if fp != template:
522 shouldclose = True
522 shouldclose = True
523 if fp != sys.stdout and hasattr(fp, 'name'):
523 if fp != sys.stdout and hasattr(fp, 'name'):
524 repo.ui.note("%s\n" % fp.name)
524 repo.ui.note("%s\n" % fp.name)
525
525
526 fp.write("# HG changeset patch\n")
526 fp.write("# HG changeset patch\n")
527 fp.write("# User %s\n" % ctx.user())
527 fp.write("# User %s\n" % ctx.user())
528 fp.write("# Date %d %d\n" % ctx.date())
528 fp.write("# Date %d %d\n" % ctx.date())
529 if branch and branch != 'default':
529 if branch and branch != 'default':
530 fp.write("# Branch %s\n" % branch)
530 fp.write("# Branch %s\n" % branch)
531 fp.write("# Node ID %s\n" % hex(node))
531 fp.write("# Node ID %s\n" % hex(node))
532 fp.write("# Parent %s\n" % hex(prev))
532 fp.write("# Parent %s\n" % hex(prev))
533 if len(parents) > 1:
533 if len(parents) > 1:
534 fp.write("# Parent %s\n" % hex(parents[1]))
534 fp.write("# Parent %s\n" % hex(parents[1]))
535 fp.write(ctx.description().rstrip())
535 fp.write(ctx.description().rstrip())
536 fp.write("\n\n")
536 fp.write("\n\n")
537
537
538 for chunk in patch.diff(repo, prev, node, opts=opts):
538 for chunk in patch.diff(repo, prev, node, opts=opts):
539 fp.write(chunk)
539 fp.write(chunk)
540
540
541 if shouldclose:
541 if shouldclose:
542 fp.close()
542 fp.close()
543
543
544 for seqno, rev in enumerate(revs):
544 for seqno, rev in enumerate(revs):
545 single(rev, seqno + 1, fp)
545 single(rev, seqno + 1, fp)
546
546
547 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
547 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
548 changes=None, stat=False, fp=None, prefix='',
548 changes=None, stat=False, fp=None, prefix='',
549 listsubrepos=False):
549 listsubrepos=False):
550 '''show diff or diffstat.'''
550 '''show diff or diffstat.'''
551 if fp is None:
551 if fp is None:
552 write = ui.write
552 write = ui.write
553 else:
553 else:
554 def write(s, **kw):
554 def write(s, **kw):
555 fp.write(s)
555 fp.write(s)
556
556
557 if stat:
557 if stat:
558 diffopts = diffopts.copy(context=0)
558 diffopts = diffopts.copy(context=0)
559 width = 80
559 width = 80
560 if not ui.plain():
560 if not ui.plain():
561 width = ui.termwidth()
561 width = ui.termwidth()
562 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
562 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
563 prefix=prefix)
563 prefix=prefix)
564 for chunk, label in patch.diffstatui(util.iterlines(chunks),
564 for chunk, label in patch.diffstatui(util.iterlines(chunks),
565 width=width,
565 width=width,
566 git=diffopts.git):
566 git=diffopts.git):
567 write(chunk, label=label)
567 write(chunk, label=label)
568 else:
568 else:
569 for chunk, label in patch.diffui(repo, node1, node2, match,
569 for chunk, label in patch.diffui(repo, node1, node2, match,
570 changes, diffopts, prefix=prefix):
570 changes, diffopts, prefix=prefix):
571 write(chunk, label=label)
571 write(chunk, label=label)
572
572
573 if listsubrepos:
573 if listsubrepos:
574 ctx1 = repo[node1]
574 ctx1 = repo[node1]
575 ctx2 = repo[node2]
575 ctx2 = repo[node2]
576 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
576 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
577 if node2 is not None:
577 if node2 is not None:
578 node2 = ctx2.substate[subpath][1]
578 node2 = ctx2.substate[subpath][1]
579 submatch = matchmod.narrowmatcher(subpath, match)
579 submatch = matchmod.narrowmatcher(subpath, match)
580 sub.diff(diffopts, node2, submatch, changes=changes,
580 sub.diff(diffopts, node2, submatch, changes=changes,
581 stat=stat, fp=fp, prefix=prefix)
581 stat=stat, fp=fp, prefix=prefix)
582
582
583 class changeset_printer(object):
583 class changeset_printer(object):
584 '''show changeset information when templating not requested.'''
584 '''show changeset information when templating not requested.'''
585
585
586 def __init__(self, ui, repo, patch, diffopts, buffered):
586 def __init__(self, ui, repo, patch, diffopts, buffered):
587 self.ui = ui
587 self.ui = ui
588 self.repo = repo
588 self.repo = repo
589 self.buffered = buffered
589 self.buffered = buffered
590 self.patch = patch
590 self.patch = patch
591 self.diffopts = diffopts
591 self.diffopts = diffopts
592 self.header = {}
592 self.header = {}
593 self.hunk = {}
593 self.hunk = {}
594 self.lastheader = None
594 self.lastheader = None
595 self.footer = None
595 self.footer = None
596
596
597 def flush(self, rev):
597 def flush(self, rev):
598 if rev in self.header:
598 if rev in self.header:
599 h = self.header[rev]
599 h = self.header[rev]
600 if h != self.lastheader:
600 if h != self.lastheader:
601 self.lastheader = h
601 self.lastheader = h
602 self.ui.write(h)
602 self.ui.write(h)
603 del self.header[rev]
603 del self.header[rev]
604 if rev in self.hunk:
604 if rev in self.hunk:
605 self.ui.write(self.hunk[rev])
605 self.ui.write(self.hunk[rev])
606 del self.hunk[rev]
606 del self.hunk[rev]
607 return 1
607 return 1
608 return 0
608 return 0
609
609
610 def close(self):
610 def close(self):
611 if self.footer:
611 if self.footer:
612 self.ui.write(self.footer)
612 self.ui.write(self.footer)
613
613
614 def show(self, ctx, copies=None, matchfn=None, **props):
614 def show(self, ctx, copies=None, matchfn=None, **props):
615 if self.buffered:
615 if self.buffered:
616 self.ui.pushbuffer()
616 self.ui.pushbuffer()
617 self._show(ctx, copies, matchfn, props)
617 self._show(ctx, copies, matchfn, props)
618 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
618 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
619 else:
619 else:
620 self._show(ctx, copies, matchfn, props)
620 self._show(ctx, copies, matchfn, props)
621
621
622 def _show(self, ctx, copies, matchfn, props):
622 def _show(self, ctx, copies, matchfn, props):
623 '''show a single changeset or file revision'''
623 '''show a single changeset or file revision'''
624 changenode = ctx.node()
624 changenode = ctx.node()
625 rev = ctx.rev()
625 rev = ctx.rev()
626
626
627 if self.ui.quiet:
627 if self.ui.quiet:
628 self.ui.write("%d:%s\n" % (rev, short(changenode)),
628 self.ui.write("%d:%s\n" % (rev, short(changenode)),
629 label='log.node')
629 label='log.node')
630 return
630 return
631
631
632 log = self.repo.changelog
632 log = self.repo.changelog
633 date = util.datestr(ctx.date())
633 date = util.datestr(ctx.date())
634
634
635 hexfunc = self.ui.debugflag and hex or short
635 hexfunc = self.ui.debugflag and hex or short
636
636
637 parents = [(p, hexfunc(log.node(p)))
637 parents = [(p, hexfunc(log.node(p)))
638 for p in self._meaningful_parentrevs(log, rev)]
638 for p in self._meaningful_parentrevs(log, rev)]
639
639
640 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
640 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
641 label='log.changeset')
641 label='log.changeset')
642
642
643 branch = ctx.branch()
643 branch = ctx.branch()
644 # don't show the default branch name
644 # don't show the default branch name
645 if branch != 'default':
645 if branch != 'default':
646 self.ui.write(_("branch: %s\n") % branch,
646 self.ui.write(_("branch: %s\n") % branch,
647 label='log.branch')
647 label='log.branch')
648 for bookmark in self.repo.nodebookmarks(changenode):
648 for bookmark in self.repo.nodebookmarks(changenode):
649 self.ui.write(_("bookmark: %s\n") % bookmark,
649 self.ui.write(_("bookmark: %s\n") % bookmark,
650 label='log.bookmark')
650 label='log.bookmark')
651 for tag in self.repo.nodetags(changenode):
651 for tag in self.repo.nodetags(changenode):
652 self.ui.write(_("tag: %s\n") % tag,
652 self.ui.write(_("tag: %s\n") % tag,
653 label='log.tag')
653 label='log.tag')
654 for parent in parents:
654 for parent in parents:
655 self.ui.write(_("parent: %d:%s\n") % parent,
655 self.ui.write(_("parent: %d:%s\n") % parent,
656 label='log.parent')
656 label='log.parent')
657
657
658 if self.ui.debugflag:
658 if self.ui.debugflag:
659 mnode = ctx.manifestnode()
659 mnode = ctx.manifestnode()
660 self.ui.write(_("manifest: %d:%s\n") %
660 self.ui.write(_("manifest: %d:%s\n") %
661 (self.repo.manifest.rev(mnode), hex(mnode)),
661 (self.repo.manifest.rev(mnode), hex(mnode)),
662 label='ui.debug log.manifest')
662 label='ui.debug log.manifest')
663 self.ui.write(_("user: %s\n") % ctx.user(),
663 self.ui.write(_("user: %s\n") % ctx.user(),
664 label='log.user')
664 label='log.user')
665 self.ui.write(_("date: %s\n") % date,
665 self.ui.write(_("date: %s\n") % date,
666 label='log.date')
666 label='log.date')
667
667
668 if self.ui.debugflag:
668 if self.ui.debugflag:
669 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
669 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
670 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
670 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
671 files):
671 files):
672 if value:
672 if value:
673 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
673 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
674 label='ui.debug log.files')
674 label='ui.debug log.files')
675 elif ctx.files() and self.ui.verbose:
675 elif ctx.files() and self.ui.verbose:
676 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
676 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
677 label='ui.note log.files')
677 label='ui.note log.files')
678 if copies and self.ui.verbose:
678 if copies and self.ui.verbose:
679 copies = ['%s (%s)' % c for c in copies]
679 copies = ['%s (%s)' % c for c in copies]
680 self.ui.write(_("copies: %s\n") % ' '.join(copies),
680 self.ui.write(_("copies: %s\n") % ' '.join(copies),
681 label='ui.note log.copies')
681 label='ui.note log.copies')
682
682
683 extra = ctx.extra()
683 extra = ctx.extra()
684 if extra and self.ui.debugflag:
684 if extra and self.ui.debugflag:
685 for key, value in sorted(extra.items()):
685 for key, value in sorted(extra.items()):
686 self.ui.write(_("extra: %s=%s\n")
686 self.ui.write(_("extra: %s=%s\n")
687 % (key, value.encode('string_escape')),
687 % (key, value.encode('string_escape')),
688 label='ui.debug log.extra')
688 label='ui.debug log.extra')
689
689
690 description = ctx.description().strip()
690 description = ctx.description().strip()
691 if description:
691 if description:
692 if self.ui.verbose:
692 if self.ui.verbose:
693 self.ui.write(_("description:\n"),
693 self.ui.write(_("description:\n"),
694 label='ui.note log.description')
694 label='ui.note log.description')
695 self.ui.write(description,
695 self.ui.write(description,
696 label='ui.note log.description')
696 label='ui.note log.description')
697 self.ui.write("\n\n")
697 self.ui.write("\n\n")
698 else:
698 else:
699 self.ui.write(_("summary: %s\n") %
699 self.ui.write(_("summary: %s\n") %
700 description.splitlines()[0],
700 description.splitlines()[0],
701 label='log.summary')
701 label='log.summary')
702 self.ui.write("\n")
702 self.ui.write("\n")
703
703
704 self.showpatch(changenode, matchfn)
704 self.showpatch(changenode, matchfn)
705
705
706 def showpatch(self, node, matchfn):
706 def showpatch(self, node, matchfn):
707 if not matchfn:
707 if not matchfn:
708 matchfn = self.patch
708 matchfn = self.patch
709 if matchfn:
709 if matchfn:
710 stat = self.diffopts.get('stat')
710 stat = self.diffopts.get('stat')
711 diff = self.diffopts.get('patch')
711 diff = self.diffopts.get('patch')
712 diffopts = patch.diffopts(self.ui, self.diffopts)
712 diffopts = patch.diffopts(self.ui, self.diffopts)
713 prev = self.repo.changelog.parents(node)[0]
713 prev = self.repo.changelog.parents(node)[0]
714 if stat:
714 if stat:
715 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
715 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
716 match=matchfn, stat=True)
716 match=matchfn, stat=True)
717 if diff:
717 if diff:
718 if stat:
718 if stat:
719 self.ui.write("\n")
719 self.ui.write("\n")
720 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
720 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
721 match=matchfn, stat=False)
721 match=matchfn, stat=False)
722 self.ui.write("\n")
722 self.ui.write("\n")
723
723
724 def _meaningful_parentrevs(self, log, rev):
724 def _meaningful_parentrevs(self, log, rev):
725 """Return list of meaningful (or all if debug) parentrevs for rev.
725 """Return list of meaningful (or all if debug) parentrevs for rev.
726
726
727 For merges (two non-nullrev revisions) both parents are meaningful.
727 For merges (two non-nullrev revisions) both parents are meaningful.
728 Otherwise the first parent revision is considered meaningful if it
728 Otherwise the first parent revision is considered meaningful if it
729 is not the preceding revision.
729 is not the preceding revision.
730 """
730 """
731 parents = log.parentrevs(rev)
731 parents = log.parentrevs(rev)
732 if not self.ui.debugflag and parents[1] == nullrev:
732 if not self.ui.debugflag and parents[1] == nullrev:
733 if parents[0] >= rev - 1:
733 if parents[0] >= rev - 1:
734 parents = []
734 parents = []
735 else:
735 else:
736 parents = [parents[0]]
736 parents = [parents[0]]
737 return parents
737 return parents
738
738
739
739
740 class changeset_templater(changeset_printer):
740 class changeset_templater(changeset_printer):
741 '''format changeset information.'''
741 '''format changeset information.'''
742
742
743 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
743 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
744 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
744 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
745 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
745 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
746 defaulttempl = {
746 defaulttempl = {
747 'parent': '{rev}:{node|formatnode} ',
747 'parent': '{rev}:{node|formatnode} ',
748 'manifest': '{rev}:{node|formatnode}',
748 'manifest': '{rev}:{node|formatnode}',
749 'file_copy': '{name} ({source})',
749 'file_copy': '{name} ({source})',
750 'extra': '{key}={value|stringescape}'
750 'extra': '{key}={value|stringescape}'
751 }
751 }
752 # filecopy is preserved for compatibility reasons
752 # filecopy is preserved for compatibility reasons
753 defaulttempl['filecopy'] = defaulttempl['file_copy']
753 defaulttempl['filecopy'] = defaulttempl['file_copy']
754 self.t = templater.templater(mapfile, {'formatnode': formatnode},
754 self.t = templater.templater(mapfile, {'formatnode': formatnode},
755 cache=defaulttempl)
755 cache=defaulttempl)
756 self.cache = {}
756 self.cache = {}
757
757
758 def use_template(self, t):
758 def use_template(self, t):
759 '''set template string to use'''
759 '''set template string to use'''
760 self.t.cache['changeset'] = t
760 self.t.cache['changeset'] = t
761
761
762 def _meaningful_parentrevs(self, ctx):
762 def _meaningful_parentrevs(self, ctx):
763 """Return list of meaningful (or all if debug) parentrevs for rev.
763 """Return list of meaningful (or all if debug) parentrevs for rev.
764 """
764 """
765 parents = ctx.parents()
765 parents = ctx.parents()
766 if len(parents) > 1:
766 if len(parents) > 1:
767 return parents
767 return parents
768 if self.ui.debugflag:
768 if self.ui.debugflag:
769 return [parents[0], self.repo['null']]
769 return [parents[0], self.repo['null']]
770 if parents[0].rev() >= ctx.rev() - 1:
770 if parents[0].rev() >= ctx.rev() - 1:
771 return []
771 return []
772 return parents
772 return parents
773
773
774 def _show(self, ctx, copies, matchfn, props):
774 def _show(self, ctx, copies, matchfn, props):
775 '''show a single changeset or file revision'''
775 '''show a single changeset or file revision'''
776
776
777 showlist = templatekw.showlist
777 showlist = templatekw.showlist
778
778
779 # showparents() behaviour depends on ui trace level which
779 # showparents() behaviour depends on ui trace level which
780 # causes unexpected behaviours at templating level and makes
780 # causes unexpected behaviours at templating level and makes
781 # it harder to extract it in a standalone function. Its
781 # it harder to extract it in a standalone function. Its
782 # behaviour cannot be changed so leave it here for now.
782 # behaviour cannot be changed so leave it here for now.
783 def showparents(**args):
783 def showparents(**args):
784 ctx = args['ctx']
784 ctx = args['ctx']
785 parents = [[('rev', p.rev()), ('node', p.hex())]
785 parents = [[('rev', p.rev()), ('node', p.hex())]
786 for p in self._meaningful_parentrevs(ctx)]
786 for p in self._meaningful_parentrevs(ctx)]
787 return showlist('parent', parents, **args)
787 return showlist('parent', parents, **args)
788
788
789 props = props.copy()
789 props = props.copy()
790 props.update(templatekw.keywords)
790 props.update(templatekw.keywords)
791 props['parents'] = showparents
791 props['parents'] = showparents
792 props['templ'] = self.t
792 props['templ'] = self.t
793 props['ctx'] = ctx
793 props['ctx'] = ctx
794 props['repo'] = self.repo
794 props['repo'] = self.repo
795 props['revcache'] = {'copies': copies}
795 props['revcache'] = {'copies': copies}
796 props['cache'] = self.cache
796 props['cache'] = self.cache
797
797
798 # find correct templates for current mode
798 # find correct templates for current mode
799
799
800 tmplmodes = [
800 tmplmodes = [
801 (True, None),
801 (True, None),
802 (self.ui.verbose, 'verbose'),
802 (self.ui.verbose, 'verbose'),
803 (self.ui.quiet, 'quiet'),
803 (self.ui.quiet, 'quiet'),
804 (self.ui.debugflag, 'debug'),
804 (self.ui.debugflag, 'debug'),
805 ]
805 ]
806
806
807 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
807 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
808 for mode, postfix in tmplmodes:
808 for mode, postfix in tmplmodes:
809 for type in types:
809 for type in types:
810 cur = postfix and ('%s_%s' % (type, postfix)) or type
810 cur = postfix and ('%s_%s' % (type, postfix)) or type
811 if mode and cur in self.t:
811 if mode and cur in self.t:
812 types[type] = cur
812 types[type] = cur
813
813
814 try:
814 try:
815
815
816 # write header
816 # write header
817 if types['header']:
817 if types['header']:
818 h = templater.stringify(self.t(types['header'], **props))
818 h = templater.stringify(self.t(types['header'], **props))
819 if self.buffered:
819 if self.buffered:
820 self.header[ctx.rev()] = h
820 self.header[ctx.rev()] = h
821 else:
821 else:
822 if self.lastheader != h:
822 if self.lastheader != h:
823 self.lastheader = h
823 self.lastheader = h
824 self.ui.write(h)
824 self.ui.write(h)
825
825
826 # write changeset metadata, then patch if requested
826 # write changeset metadata, then patch if requested
827 key = types['changeset']
827 key = types['changeset']
828 self.ui.write(templater.stringify(self.t(key, **props)))
828 self.ui.write(templater.stringify(self.t(key, **props)))
829 self.showpatch(ctx.node(), matchfn)
829 self.showpatch(ctx.node(), matchfn)
830
830
831 if types['footer']:
831 if types['footer']:
832 if not self.footer:
832 if not self.footer:
833 self.footer = templater.stringify(self.t(types['footer'],
833 self.footer = templater.stringify(self.t(types['footer'],
834 **props))
834 **props))
835
835
836 except KeyError, inst:
836 except KeyError, inst:
837 msg = _("%s: no key named '%s'")
837 msg = _("%s: no key named '%s'")
838 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
838 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
839 except SyntaxError, inst:
839 except SyntaxError, inst:
840 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
840 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
841
841
842 def show_changeset(ui, repo, opts, buffered=False):
842 def show_changeset(ui, repo, opts, buffered=False):
843 """show one changeset using template or regular display.
843 """show one changeset using template or regular display.
844
844
845 Display format will be the first non-empty hit of:
845 Display format will be the first non-empty hit of:
846 1. option 'template'
846 1. option 'template'
847 2. option 'style'
847 2. option 'style'
848 3. [ui] setting 'logtemplate'
848 3. [ui] setting 'logtemplate'
849 4. [ui] setting 'style'
849 4. [ui] setting 'style'
850 If all of these values are either the unset or the empty string,
850 If all of these values are either the unset or the empty string,
851 regular display via changeset_printer() is done.
851 regular display via changeset_printer() is done.
852 """
852 """
853 # options
853 # options
854 patch = False
854 patch = False
855 if opts.get('patch') or opts.get('stat'):
855 if opts.get('patch') or opts.get('stat'):
856 patch = scmutil.matchall(repo)
856 patch = scmutil.matchall(repo)
857
857
858 tmpl = opts.get('template')
858 tmpl = opts.get('template')
859 style = None
859 style = None
860 if tmpl:
860 if tmpl:
861 tmpl = templater.parsestring(tmpl, quoted=False)
861 tmpl = templater.parsestring(tmpl, quoted=False)
862 else:
862 else:
863 style = opts.get('style')
863 style = opts.get('style')
864
864
865 # ui settings
865 # ui settings
866 if not (tmpl or style):
866 if not (tmpl or style):
867 tmpl = ui.config('ui', 'logtemplate')
867 tmpl = ui.config('ui', 'logtemplate')
868 if tmpl:
868 if tmpl:
869 tmpl = templater.parsestring(tmpl)
869 tmpl = templater.parsestring(tmpl)
870 else:
870 else:
871 style = util.expandpath(ui.config('ui', 'style', ''))
871 style = util.expandpath(ui.config('ui', 'style', ''))
872
872
873 if not (tmpl or style):
873 if not (tmpl or style):
874 return changeset_printer(ui, repo, patch, opts, buffered)
874 return changeset_printer(ui, repo, patch, opts, buffered)
875
875
876 mapfile = None
876 mapfile = None
877 if style and not tmpl:
877 if style and not tmpl:
878 mapfile = style
878 mapfile = style
879 if not os.path.split(mapfile)[0]:
879 if not os.path.split(mapfile)[0]:
880 mapname = (templater.templatepath('map-cmdline.' + mapfile)
880 mapname = (templater.templatepath('map-cmdline.' + mapfile)
881 or templater.templatepath(mapfile))
881 or templater.templatepath(mapfile))
882 if mapname:
882 if mapname:
883 mapfile = mapname
883 mapfile = mapname
884
884
885 try:
885 try:
886 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
886 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
887 except SyntaxError, inst:
887 except SyntaxError, inst:
888 raise util.Abort(inst.args[0])
888 raise util.Abort(inst.args[0])
889 if tmpl:
889 if tmpl:
890 t.use_template(tmpl)
890 t.use_template(tmpl)
891 return t
891 return t
892
892
893 def finddate(ui, repo, date):
893 def finddate(ui, repo, date):
894 """Find the tipmost changeset that matches the given date spec"""
894 """Find the tipmost changeset that matches the given date spec"""
895
895
896 df = util.matchdate(date)
896 df = util.matchdate(date)
897 m = scmutil.matchall(repo)
897 m = scmutil.matchall(repo)
898 results = {}
898 results = {}
899
899
900 def prep(ctx, fns):
900 def prep(ctx, fns):
901 d = ctx.date()
901 d = ctx.date()
902 if df(d[0]):
902 if df(d[0]):
903 results[ctx.rev()] = d
903 results[ctx.rev()] = d
904
904
905 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
905 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
906 rev = ctx.rev()
906 rev = ctx.rev()
907 if rev in results:
907 if rev in results:
908 ui.status(_("Found revision %s from %s\n") %
908 ui.status(_("Found revision %s from %s\n") %
909 (rev, util.datestr(results[rev])))
909 (rev, util.datestr(results[rev])))
910 return str(rev)
910 return str(rev)
911
911
912 raise util.Abort(_("revision matching date not found"))
912 raise util.Abort(_("revision matching date not found"))
913
913
914 def walkchangerevs(repo, match, opts, prepare):
914 def walkchangerevs(repo, match, opts, prepare):
915 '''Iterate over files and the revs in which they changed.
915 '''Iterate over files and the revs in which they changed.
916
916
917 Callers most commonly need to iterate backwards over the history
917 Callers most commonly need to iterate backwards over the history
918 in which they are interested. Doing so has awful (quadratic-looking)
918 in which they are interested. Doing so has awful (quadratic-looking)
919 performance, so we use iterators in a "windowed" way.
919 performance, so we use iterators in a "windowed" way.
920
920
921 We walk a window of revisions in the desired order. Within the
921 We walk a window of revisions in the desired order. Within the
922 window, we first walk forwards to gather data, then in the desired
922 window, we first walk forwards to gather data, then in the desired
923 order (usually backwards) to display it.
923 order (usually backwards) to display it.
924
924
925 This function returns an iterator yielding contexts. Before
925 This function returns an iterator yielding contexts. Before
926 yielding each context, the iterator will first call the prepare
926 yielding each context, the iterator will first call the prepare
927 function on each context in the window in forward order.'''
927 function on each context in the window in forward order.'''
928
928
929 def increasing_windows(start, end, windowsize=8, sizelimit=512):
929 def increasing_windows(start, end, windowsize=8, sizelimit=512):
930 if start < end:
930 if start < end:
931 while start < end:
931 while start < end:
932 yield start, min(windowsize, end - start)
932 yield start, min(windowsize, end - start)
933 start += windowsize
933 start += windowsize
934 if windowsize < sizelimit:
934 if windowsize < sizelimit:
935 windowsize *= 2
935 windowsize *= 2
936 else:
936 else:
937 while start > end:
937 while start > end:
938 yield start, min(windowsize, start - end - 1)
938 yield start, min(windowsize, start - end - 1)
939 start -= windowsize
939 start -= windowsize
940 if windowsize < sizelimit:
940 if windowsize < sizelimit:
941 windowsize *= 2
941 windowsize *= 2
942
942
943 follow = opts.get('follow') or opts.get('follow_first')
943 follow = opts.get('follow') or opts.get('follow_first')
944
944
945 if not len(repo):
945 if not len(repo):
946 return []
946 return []
947
947
948 if follow:
948 if follow:
949 defrange = '%s:0' % repo['.'].rev()
949 defrange = '%s:0' % repo['.'].rev()
950 else:
950 else:
951 defrange = '-1:0'
951 defrange = '-1:0'
952 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
952 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
953 if not revs:
953 if not revs:
954 return []
954 return []
955 wanted = set()
955 wanted = set()
956 slowpath = match.anypats() or (match.files() and opts.get('removed'))
956 slowpath = match.anypats() or (match.files() and opts.get('removed'))
957 fncache = {}
957 fncache = {}
958 change = util.cachefunc(repo.changectx)
958 change = util.cachefunc(repo.changectx)
959
959
960 # First step is to fill wanted, the set of revisions that we want to yield.
960 # First step is to fill wanted, the set of revisions that we want to yield.
961 # When it does not induce extra cost, we also fill fncache for revisions in
961 # When it does not induce extra cost, we also fill fncache for revisions in
962 # wanted: a cache of filenames that were changed (ctx.files()) and that
962 # wanted: a cache of filenames that were changed (ctx.files()) and that
963 # match the file filtering conditions.
963 # match the file filtering conditions.
964
964
965 if not slowpath and not match.files():
965 if not slowpath and not match.files():
966 # No files, no patterns. Display all revs.
966 # No files, no patterns. Display all revs.
967 wanted = set(revs)
967 wanted = set(revs)
968 copies = []
968 copies = []
969
969
970 if not slowpath:
970 if not slowpath:
971 # We only have to read through the filelog to find wanted revisions
971 # We only have to read through the filelog to find wanted revisions
972
972
973 minrev, maxrev = min(revs), max(revs)
973 minrev, maxrev = min(revs), max(revs)
974 def filerevgen(filelog, last):
974 def filerevgen(filelog, last):
975 """
975 """
976 Only files, no patterns. Check the history of each file.
976 Only files, no patterns. Check the history of each file.
977
977
978 Examines filelog entries within minrev, maxrev linkrev range
978 Examines filelog entries within minrev, maxrev linkrev range
979 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
979 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
980 tuples in backwards order
980 tuples in backwards order
981 """
981 """
982 cl_count = len(repo)
982 cl_count = len(repo)
983 revs = []
983 revs = []
984 for j in xrange(0, last + 1):
984 for j in xrange(0, last + 1):
985 linkrev = filelog.linkrev(j)
985 linkrev = filelog.linkrev(j)
986 if linkrev < minrev:
986 if linkrev < minrev:
987 continue
987 continue
988 # only yield rev for which we have the changelog, it can
988 # only yield rev for which we have the changelog, it can
989 # happen while doing "hg log" during a pull or commit
989 # happen while doing "hg log" during a pull or commit
990 if linkrev >= cl_count:
990 if linkrev >= cl_count:
991 break
991 break
992
992
993 parentlinkrevs = []
993 parentlinkrevs = []
994 for p in filelog.parentrevs(j):
994 for p in filelog.parentrevs(j):
995 if p != nullrev:
995 if p != nullrev:
996 parentlinkrevs.append(filelog.linkrev(p))
996 parentlinkrevs.append(filelog.linkrev(p))
997 n = filelog.node(j)
997 n = filelog.node(j)
998 revs.append((linkrev, parentlinkrevs,
998 revs.append((linkrev, parentlinkrevs,
999 follow and filelog.renamed(n)))
999 follow and filelog.renamed(n)))
1000
1000
1001 return reversed(revs)
1001 return reversed(revs)
1002 def iterfiles():
1002 def iterfiles():
1003 for filename in match.files():
1003 for filename in match.files():
1004 yield filename, None
1004 yield filename, None
1005 for filename_node in copies:
1005 for filename_node in copies:
1006 yield filename_node
1006 yield filename_node
1007 for file_, node in iterfiles():
1007 for file_, node in iterfiles():
1008 filelog = repo.file(file_)
1008 filelog = repo.file(file_)
1009 if not len(filelog):
1009 if not len(filelog):
1010 if node is None:
1010 if node is None:
1011 # A zero count may be a directory or deleted file, so
1011 # A zero count may be a directory or deleted file, so
1012 # try to find matching entries on the slow path.
1012 # try to find matching entries on the slow path.
1013 if follow:
1013 if follow:
1014 raise util.Abort(
1014 raise util.Abort(
1015 _('cannot follow nonexistent file: "%s"') % file_)
1015 _('cannot follow nonexistent file: "%s"') % file_)
1016 slowpath = True
1016 slowpath = True
1017 break
1017 break
1018 else:
1018 else:
1019 continue
1019 continue
1020
1020
1021 if node is None:
1021 if node is None:
1022 last = len(filelog) - 1
1022 last = len(filelog) - 1
1023 else:
1023 else:
1024 last = filelog.rev(node)
1024 last = filelog.rev(node)
1025
1025
1026
1026
1027 # keep track of all ancestors of the file
1027 # keep track of all ancestors of the file
1028 ancestors = set([filelog.linkrev(last)])
1028 ancestors = set([filelog.linkrev(last)])
1029
1029
1030 # iterate from latest to oldest revision
1030 # iterate from latest to oldest revision
1031 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1031 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1032 if not follow:
1032 if not follow:
1033 if rev > maxrev:
1033 if rev > maxrev:
1034 continue
1034 continue
1035 else:
1035 else:
1036 # Note that last might not be the first interesting
1036 # Note that last might not be the first interesting
1037 # rev to us:
1037 # rev to us:
1038 # if the file has been changed after maxrev, we'll
1038 # if the file has been changed after maxrev, we'll
1039 # have linkrev(last) > maxrev, and we still need
1039 # have linkrev(last) > maxrev, and we still need
1040 # to explore the file graph
1040 # to explore the file graph
1041 if rev not in ancestors:
1041 if rev not in ancestors:
1042 continue
1042 continue
1043 # XXX insert 1327 fix here
1043 # XXX insert 1327 fix here
1044 if flparentlinkrevs:
1044 if flparentlinkrevs:
1045 ancestors.update(flparentlinkrevs)
1045 ancestors.update(flparentlinkrevs)
1046
1046
1047 fncache.setdefault(rev, []).append(file_)
1047 fncache.setdefault(rev, []).append(file_)
1048 wanted.add(rev)
1048 wanted.add(rev)
1049 if copied:
1049 if copied:
1050 copies.append(copied)
1050 copies.append(copied)
1051 if slowpath:
1051 if slowpath:
1052 # We have to read the changelog to match filenames against
1052 # We have to read the changelog to match filenames against
1053 # changed files
1053 # changed files
1054
1054
1055 if follow:
1055 if follow:
1056 raise util.Abort(_('can only follow copies/renames for explicit '
1056 raise util.Abort(_('can only follow copies/renames for explicit '
1057 'filenames'))
1057 'filenames'))
1058
1058
1059 # The slow path checks files modified in every changeset.
1059 # The slow path checks files modified in every changeset.
1060 for i in sorted(revs):
1060 for i in sorted(revs):
1061 ctx = change(i)
1061 ctx = change(i)
1062 matches = filter(match, ctx.files())
1062 matches = filter(match, ctx.files())
1063 if matches:
1063 if matches:
1064 fncache[i] = matches
1064 fncache[i] = matches
1065 wanted.add(i)
1065 wanted.add(i)
1066
1066
1067 class followfilter(object):
1067 class followfilter(object):
1068 def __init__(self, onlyfirst=False):
1068 def __init__(self, onlyfirst=False):
1069 self.startrev = nullrev
1069 self.startrev = nullrev
1070 self.roots = set()
1070 self.roots = set()
1071 self.onlyfirst = onlyfirst
1071 self.onlyfirst = onlyfirst
1072
1072
1073 def match(self, rev):
1073 def match(self, rev):
1074 def realparents(rev):
1074 def realparents(rev):
1075 if self.onlyfirst:
1075 if self.onlyfirst:
1076 return repo.changelog.parentrevs(rev)[0:1]
1076 return repo.changelog.parentrevs(rev)[0:1]
1077 else:
1077 else:
1078 return filter(lambda x: x != nullrev,
1078 return filter(lambda x: x != nullrev,
1079 repo.changelog.parentrevs(rev))
1079 repo.changelog.parentrevs(rev))
1080
1080
1081 if self.startrev == nullrev:
1081 if self.startrev == nullrev:
1082 self.startrev = rev
1082 self.startrev = rev
1083 return True
1083 return True
1084
1084
1085 if rev > self.startrev:
1085 if rev > self.startrev:
1086 # forward: all descendants
1086 # forward: all descendants
1087 if not self.roots:
1087 if not self.roots:
1088 self.roots.add(self.startrev)
1088 self.roots.add(self.startrev)
1089 for parent in realparents(rev):
1089 for parent in realparents(rev):
1090 if parent in self.roots:
1090 if parent in self.roots:
1091 self.roots.add(rev)
1091 self.roots.add(rev)
1092 return True
1092 return True
1093 else:
1093 else:
1094 # backwards: all parents
1094 # backwards: all parents
1095 if not self.roots:
1095 if not self.roots:
1096 self.roots.update(realparents(self.startrev))
1096 self.roots.update(realparents(self.startrev))
1097 if rev in self.roots:
1097 if rev in self.roots:
1098 self.roots.remove(rev)
1098 self.roots.remove(rev)
1099 self.roots.update(realparents(rev))
1099 self.roots.update(realparents(rev))
1100 return True
1100 return True
1101
1101
1102 return False
1102 return False
1103
1103
1104 # it might be worthwhile to do this in the iterator if the rev range
1104 # it might be worthwhile to do this in the iterator if the rev range
1105 # is descending and the prune args are all within that range
1105 # is descending and the prune args are all within that range
1106 for rev in opts.get('prune', ()):
1106 for rev in opts.get('prune', ()):
1107 rev = repo.changelog.rev(repo.lookup(rev))
1107 rev = repo.changelog.rev(repo.lookup(rev))
1108 ff = followfilter()
1108 ff = followfilter()
1109 stop = min(revs[0], revs[-1])
1109 stop = min(revs[0], revs[-1])
1110 for x in xrange(rev, stop - 1, -1):
1110 for x in xrange(rev, stop - 1, -1):
1111 if ff.match(x):
1111 if ff.match(x):
1112 wanted.discard(x)
1112 wanted.discard(x)
1113
1113
1114 # Now that wanted is correctly initialized, we can iterate over the
1114 # Now that wanted is correctly initialized, we can iterate over the
1115 # revision range, yielding only revisions in wanted.
1115 # revision range, yielding only revisions in wanted.
1116 def iterate():
1116 def iterate():
1117 if follow and not match.files():
1117 if follow and not match.files():
1118 ff = followfilter(onlyfirst=opts.get('follow_first'))
1118 ff = followfilter(onlyfirst=opts.get('follow_first'))
1119 def want(rev):
1119 def want(rev):
1120 return ff.match(rev) and rev in wanted
1120 return ff.match(rev) and rev in wanted
1121 else:
1121 else:
1122 def want(rev):
1122 def want(rev):
1123 return rev in wanted
1123 return rev in wanted
1124
1124
1125 for i, window in increasing_windows(0, len(revs)):
1125 for i, window in increasing_windows(0, len(revs)):
1126 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1126 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1127 for rev in sorted(nrevs):
1127 for rev in sorted(nrevs):
1128 fns = fncache.get(rev)
1128 fns = fncache.get(rev)
1129 ctx = change(rev)
1129 ctx = change(rev)
1130 if not fns:
1130 if not fns:
1131 def fns_generator():
1131 def fns_generator():
1132 for f in ctx.files():
1132 for f in ctx.files():
1133 if match(f):
1133 if match(f):
1134 yield f
1134 yield f
1135 fns = fns_generator()
1135 fns = fns_generator()
1136 prepare(ctx, fns)
1136 prepare(ctx, fns)
1137 for rev in nrevs:
1137 for rev in nrevs:
1138 yield change(rev)
1138 yield change(rev)
1139 return iterate()
1139 return iterate()
1140
1140
1141 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1141 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1142 join = lambda f: os.path.join(prefix, f)
1142 join = lambda f: os.path.join(prefix, f)
1143 bad = []
1143 bad = []
1144 oldbad = match.bad
1144 oldbad = match.bad
1145 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1145 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1146 names = []
1146 names = []
1147 wctx = repo[None]
1147 wctx = repo[None]
1148 cca = None
1148 cca = None
1149 abort, warn = scmutil.checkportabilityalert(ui)
1149 abort, warn = scmutil.checkportabilityalert(ui)
1150 if abort or warn:
1150 if abort or warn:
1151 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1151 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1152 for f in repo.walk(match):
1152 for f in repo.walk(match):
1153 exact = match.exact(f)
1153 exact = match.exact(f)
1154 if exact or f not in repo.dirstate:
1154 if exact or f not in repo.dirstate:
1155 if cca:
1155 if cca:
1156 cca(f)
1156 cca(f)
1157 names.append(f)
1157 names.append(f)
1158 if ui.verbose or not exact:
1158 if ui.verbose or not exact:
1159 ui.status(_('adding %s\n') % match.rel(join(f)))
1159 ui.status(_('adding %s\n') % match.rel(join(f)))
1160
1160
1161 if listsubrepos:
1161 if listsubrepos:
1162 for subpath in wctx.substate:
1162 for subpath in wctx.substate:
1163 sub = wctx.sub(subpath)
1163 sub = wctx.sub(subpath)
1164 try:
1164 try:
1165 submatch = matchmod.narrowmatcher(subpath, match)
1165 submatch = matchmod.narrowmatcher(subpath, match)
1166 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1166 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1167 except error.LookupError:
1167 except error.LookupError:
1168 ui.status(_("skipping missing subrepository: %s\n")
1168 ui.status(_("skipping missing subrepository: %s\n")
1169 % join(subpath))
1169 % join(subpath))
1170
1170
1171 if not dryrun:
1171 if not dryrun:
1172 rejected = wctx.add(names, prefix)
1172 rejected = wctx.add(names, prefix)
1173 bad.extend(f for f in rejected if f in match.files())
1173 bad.extend(f for f in rejected if f in match.files())
1174 return bad
1174 return bad
1175
1175
1176 def commit(ui, repo, commitfunc, pats, opts):
1176 def commit(ui, repo, commitfunc, pats, opts):
1177 '''commit the specified files or all outstanding changes'''
1177 '''commit the specified files or all outstanding changes'''
1178 date = opts.get('date')
1178 date = opts.get('date')
1179 if date:
1179 if date:
1180 opts['date'] = util.parsedate(date)
1180 opts['date'] = util.parsedate(date)
1181 message = logmessage(ui, opts)
1181 message = logmessage(ui, opts)
1182
1182
1183 # extract addremove carefully -- this function can be called from a command
1183 # extract addremove carefully -- this function can be called from a command
1184 # that doesn't support addremove
1184 # that doesn't support addremove
1185 if opts.get('addremove'):
1185 if opts.get('addremove'):
1186 scmutil.addremove(repo, pats, opts)
1186 scmutil.addremove(repo, pats, opts)
1187
1187
1188 return commitfunc(ui, repo, message, scmutil.match(repo, pats, opts), opts)
1188 return commitfunc(ui, repo, message,
1189 scmutil.match(repo[None], pats, opts), opts)
1189
1190
1190 def commiteditor(repo, ctx, subs):
1191 def commiteditor(repo, ctx, subs):
1191 if ctx.description():
1192 if ctx.description():
1192 return ctx.description()
1193 return ctx.description()
1193 return commitforceeditor(repo, ctx, subs)
1194 return commitforceeditor(repo, ctx, subs)
1194
1195
1195 def commitforceeditor(repo, ctx, subs):
1196 def commitforceeditor(repo, ctx, subs):
1196 edittext = []
1197 edittext = []
1197 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1198 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1198 if ctx.description():
1199 if ctx.description():
1199 edittext.append(ctx.description())
1200 edittext.append(ctx.description())
1200 edittext.append("")
1201 edittext.append("")
1201 edittext.append("") # Empty line between message and comments.
1202 edittext.append("") # Empty line between message and comments.
1202 edittext.append(_("HG: Enter commit message."
1203 edittext.append(_("HG: Enter commit message."
1203 " Lines beginning with 'HG:' are removed."))
1204 " Lines beginning with 'HG:' are removed."))
1204 edittext.append(_("HG: Leave message empty to abort commit."))
1205 edittext.append(_("HG: Leave message empty to abort commit."))
1205 edittext.append("HG: --")
1206 edittext.append("HG: --")
1206 edittext.append(_("HG: user: %s") % ctx.user())
1207 edittext.append(_("HG: user: %s") % ctx.user())
1207 if ctx.p2():
1208 if ctx.p2():
1208 edittext.append(_("HG: branch merge"))
1209 edittext.append(_("HG: branch merge"))
1209 if ctx.branch():
1210 if ctx.branch():
1210 edittext.append(_("HG: branch '%s'") % ctx.branch())
1211 edittext.append(_("HG: branch '%s'") % ctx.branch())
1211 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1212 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1212 edittext.extend([_("HG: added %s") % f for f in added])
1213 edittext.extend([_("HG: added %s") % f for f in added])
1213 edittext.extend([_("HG: changed %s") % f for f in modified])
1214 edittext.extend([_("HG: changed %s") % f for f in modified])
1214 edittext.extend([_("HG: removed %s") % f for f in removed])
1215 edittext.extend([_("HG: removed %s") % f for f in removed])
1215 if not added and not modified and not removed:
1216 if not added and not modified and not removed:
1216 edittext.append(_("HG: no files changed"))
1217 edittext.append(_("HG: no files changed"))
1217 edittext.append("")
1218 edittext.append("")
1218 # run editor in the repository root
1219 # run editor in the repository root
1219 olddir = os.getcwd()
1220 olddir = os.getcwd()
1220 os.chdir(repo.root)
1221 os.chdir(repo.root)
1221 text = repo.ui.edit("\n".join(edittext), ctx.user())
1222 text = repo.ui.edit("\n".join(edittext), ctx.user())
1222 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1223 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1223 os.chdir(olddir)
1224 os.chdir(olddir)
1224
1225
1225 if not text.strip():
1226 if not text.strip():
1226 raise util.Abort(_("empty commit message"))
1227 raise util.Abort(_("empty commit message"))
1227
1228
1228 return text
1229 return text
1229
1230
1230 def command(table):
1231 def command(table):
1231 '''returns a function object bound to table which can be used as
1232 '''returns a function object bound to table which can be used as
1232 a decorator for populating table as a command table'''
1233 a decorator for populating table as a command table'''
1233
1234
1234 def cmd(name, options, synopsis=None):
1235 def cmd(name, options, synopsis=None):
1235 def decorator(func):
1236 def decorator(func):
1236 if synopsis:
1237 if synopsis:
1237 table[name] = func, options[:], synopsis
1238 table[name] = func, options[:], synopsis
1238 else:
1239 else:
1239 table[name] = func, options[:]
1240 table[name] = func, options[:]
1240 return func
1241 return func
1241 return decorator
1242 return decorator
1242
1243
1243 return cmd
1244 return cmd
@@ -1,5145 +1,5145 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, bin, nullid, nullrev, short
8 from node import hex, bin, nullid, nullrev, short
9 from lock import release
9 from lock import release
10 from i18n import _, gettext
10 from i18n import _, gettext
11 import os, re, difflib, time, tempfile, errno
11 import os, re, difflib, time, tempfile, errno
12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
13 import patch, help, url, encoding, templatekw, discovery
13 import patch, help, url, encoding, templatekw, discovery
14 import archival, changegroup, cmdutil, hbisect
14 import archival, changegroup, cmdutil, hbisect
15 import sshserver, hgweb, hgweb.server, commandserver
15 import sshserver, hgweb, hgweb.server, commandserver
16 import merge as mergemod
16 import merge as mergemod
17 import minirst, revset, fileset
17 import minirst, revset, fileset
18 import dagparser, context, simplemerge
18 import dagparser, context, simplemerge
19 import random, setdiscovery, treediscovery, dagutil
19 import random, setdiscovery, treediscovery, dagutil
20
20
21 table = {}
21 table = {}
22
22
23 command = cmdutil.command(table)
23 command = cmdutil.command(table)
24
24
25 # common command options
25 # common command options
26
26
27 globalopts = [
27 globalopts = [
28 ('R', 'repository', '',
28 ('R', 'repository', '',
29 _('repository root directory or name of overlay bundle file'),
29 _('repository root directory or name of overlay bundle file'),
30 _('REPO')),
30 _('REPO')),
31 ('', 'cwd', '',
31 ('', 'cwd', '',
32 _('change working directory'), _('DIR')),
32 _('change working directory'), _('DIR')),
33 ('y', 'noninteractive', None,
33 ('y', 'noninteractive', None,
34 _('do not prompt, assume \'yes\' for any required answers')),
34 _('do not prompt, assume \'yes\' for any required answers')),
35 ('q', 'quiet', None, _('suppress output')),
35 ('q', 'quiet', None, _('suppress output')),
36 ('v', 'verbose', None, _('enable additional output')),
36 ('v', 'verbose', None, _('enable additional output')),
37 ('', 'config', [],
37 ('', 'config', [],
38 _('set/override config option (use \'section.name=value\')'),
38 _('set/override config option (use \'section.name=value\')'),
39 _('CONFIG')),
39 _('CONFIG')),
40 ('', 'debug', None, _('enable debugging output')),
40 ('', 'debug', None, _('enable debugging output')),
41 ('', 'debugger', None, _('start debugger')),
41 ('', 'debugger', None, _('start debugger')),
42 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
42 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
43 _('ENCODE')),
43 _('ENCODE')),
44 ('', 'encodingmode', encoding.encodingmode,
44 ('', 'encodingmode', encoding.encodingmode,
45 _('set the charset encoding mode'), _('MODE')),
45 _('set the charset encoding mode'), _('MODE')),
46 ('', 'traceback', None, _('always print a traceback on exception')),
46 ('', 'traceback', None, _('always print a traceback on exception')),
47 ('', 'time', None, _('time how long the command takes')),
47 ('', 'time', None, _('time how long the command takes')),
48 ('', 'profile', None, _('print command execution profile')),
48 ('', 'profile', None, _('print command execution profile')),
49 ('', 'version', None, _('output version information and exit')),
49 ('', 'version', None, _('output version information and exit')),
50 ('h', 'help', None, _('display help and exit')),
50 ('h', 'help', None, _('display help and exit')),
51 ]
51 ]
52
52
53 dryrunopts = [('n', 'dry-run', None,
53 dryrunopts = [('n', 'dry-run', None,
54 _('do not perform actions, just print output'))]
54 _('do not perform actions, just print output'))]
55
55
56 remoteopts = [
56 remoteopts = [
57 ('e', 'ssh', '',
57 ('e', 'ssh', '',
58 _('specify ssh command to use'), _('CMD')),
58 _('specify ssh command to use'), _('CMD')),
59 ('', 'remotecmd', '',
59 ('', 'remotecmd', '',
60 _('specify hg command to run on the remote side'), _('CMD')),
60 _('specify hg command to run on the remote side'), _('CMD')),
61 ('', 'insecure', None,
61 ('', 'insecure', None,
62 _('do not verify server certificate (ignoring web.cacerts config)')),
62 _('do not verify server certificate (ignoring web.cacerts config)')),
63 ]
63 ]
64
64
65 walkopts = [
65 walkopts = [
66 ('I', 'include', [],
66 ('I', 'include', [],
67 _('include names matching the given patterns'), _('PATTERN')),
67 _('include names matching the given patterns'), _('PATTERN')),
68 ('X', 'exclude', [],
68 ('X', 'exclude', [],
69 _('exclude names matching the given patterns'), _('PATTERN')),
69 _('exclude names matching the given patterns'), _('PATTERN')),
70 ]
70 ]
71
71
72 commitopts = [
72 commitopts = [
73 ('m', 'message', '',
73 ('m', 'message', '',
74 _('use text as commit message'), _('TEXT')),
74 _('use text as commit message'), _('TEXT')),
75 ('l', 'logfile', '',
75 ('l', 'logfile', '',
76 _('read commit message from file'), _('FILE')),
76 _('read commit message from file'), _('FILE')),
77 ]
77 ]
78
78
79 commitopts2 = [
79 commitopts2 = [
80 ('d', 'date', '',
80 ('d', 'date', '',
81 _('record the specified date as commit date'), _('DATE')),
81 _('record the specified date as commit date'), _('DATE')),
82 ('u', 'user', '',
82 ('u', 'user', '',
83 _('record the specified user as committer'), _('USER')),
83 _('record the specified user as committer'), _('USER')),
84 ]
84 ]
85
85
86 templateopts = [
86 templateopts = [
87 ('', 'style', '',
87 ('', 'style', '',
88 _('display using template map file'), _('STYLE')),
88 _('display using template map file'), _('STYLE')),
89 ('', 'template', '',
89 ('', 'template', '',
90 _('display with template'), _('TEMPLATE')),
90 _('display with template'), _('TEMPLATE')),
91 ]
91 ]
92
92
93 logopts = [
93 logopts = [
94 ('p', 'patch', None, _('show patch')),
94 ('p', 'patch', None, _('show patch')),
95 ('g', 'git', None, _('use git extended diff format')),
95 ('g', 'git', None, _('use git extended diff format')),
96 ('l', 'limit', '',
96 ('l', 'limit', '',
97 _('limit number of changes displayed'), _('NUM')),
97 _('limit number of changes displayed'), _('NUM')),
98 ('M', 'no-merges', None, _('do not show merges')),
98 ('M', 'no-merges', None, _('do not show merges')),
99 ('', 'stat', None, _('output diffstat-style summary of changes')),
99 ('', 'stat', None, _('output diffstat-style summary of changes')),
100 ] + templateopts
100 ] + templateopts
101
101
102 diffopts = [
102 diffopts = [
103 ('a', 'text', None, _('treat all files as text')),
103 ('a', 'text', None, _('treat all files as text')),
104 ('g', 'git', None, _('use git extended diff format')),
104 ('g', 'git', None, _('use git extended diff format')),
105 ('', 'nodates', None, _('omit dates from diff headers'))
105 ('', 'nodates', None, _('omit dates from diff headers'))
106 ]
106 ]
107
107
108 diffopts2 = [
108 diffopts2 = [
109 ('p', 'show-function', None, _('show which function each change is in')),
109 ('p', 'show-function', None, _('show which function each change is in')),
110 ('', 'reverse', None, _('produce a diff that undoes the changes')),
110 ('', 'reverse', None, _('produce a diff that undoes the changes')),
111 ('w', 'ignore-all-space', None,
111 ('w', 'ignore-all-space', None,
112 _('ignore white space when comparing lines')),
112 _('ignore white space when comparing lines')),
113 ('b', 'ignore-space-change', None,
113 ('b', 'ignore-space-change', None,
114 _('ignore changes in the amount of white space')),
114 _('ignore changes in the amount of white space')),
115 ('B', 'ignore-blank-lines', None,
115 ('B', 'ignore-blank-lines', None,
116 _('ignore changes whose lines are all blank')),
116 _('ignore changes whose lines are all blank')),
117 ('U', 'unified', '',
117 ('U', 'unified', '',
118 _('number of lines of context to show'), _('NUM')),
118 _('number of lines of context to show'), _('NUM')),
119 ('', 'stat', None, _('output diffstat-style summary of changes')),
119 ('', 'stat', None, _('output diffstat-style summary of changes')),
120 ]
120 ]
121
121
122 similarityopts = [
122 similarityopts = [
123 ('s', 'similarity', '',
123 ('s', 'similarity', '',
124 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
124 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
125 ]
125 ]
126
126
127 subrepoopts = [
127 subrepoopts = [
128 ('S', 'subrepos', None,
128 ('S', 'subrepos', None,
129 _('recurse into subrepositories'))
129 _('recurse into subrepositories'))
130 ]
130 ]
131
131
132 # Commands start here, listed alphabetically
132 # Commands start here, listed alphabetically
133
133
134 @command('^add',
134 @command('^add',
135 walkopts + subrepoopts + dryrunopts,
135 walkopts + subrepoopts + dryrunopts,
136 _('[OPTION]... [FILE]...'))
136 _('[OPTION]... [FILE]...'))
137 def add(ui, repo, *pats, **opts):
137 def add(ui, repo, *pats, **opts):
138 """add the specified files on the next commit
138 """add the specified files on the next commit
139
139
140 Schedule files to be version controlled and added to the
140 Schedule files to be version controlled and added to the
141 repository.
141 repository.
142
142
143 The files will be added to the repository at the next commit. To
143 The files will be added to the repository at the next commit. To
144 undo an add before that, see :hg:`forget`.
144 undo an add before that, see :hg:`forget`.
145
145
146 If no names are given, add all files to the repository.
146 If no names are given, add all files to the repository.
147
147
148 .. container:: verbose
148 .. container:: verbose
149
149
150 An example showing how new (unknown) files are added
150 An example showing how new (unknown) files are added
151 automatically by :hg:`add`::
151 automatically by :hg:`add`::
152
152
153 $ ls
153 $ ls
154 foo.c
154 foo.c
155 $ hg status
155 $ hg status
156 ? foo.c
156 ? foo.c
157 $ hg add
157 $ hg add
158 adding foo.c
158 adding foo.c
159 $ hg status
159 $ hg status
160 A foo.c
160 A foo.c
161
161
162 Returns 0 if all files are successfully added.
162 Returns 0 if all files are successfully added.
163 """
163 """
164
164
165 m = scmutil.match(repo, pats, opts)
165 m = scmutil.match(repo[None], pats, opts)
166 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
166 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
167 opts.get('subrepos'), prefix="")
167 opts.get('subrepos'), prefix="")
168 return rejected and 1 or 0
168 return rejected and 1 or 0
169
169
170 @command('addremove',
170 @command('addremove',
171 similarityopts + walkopts + dryrunopts,
171 similarityopts + walkopts + dryrunopts,
172 _('[OPTION]... [FILE]...'))
172 _('[OPTION]... [FILE]...'))
173 def addremove(ui, repo, *pats, **opts):
173 def addremove(ui, repo, *pats, **opts):
174 """add all new files, delete all missing files
174 """add all new files, delete all missing files
175
175
176 Add all new files and remove all missing files from the
176 Add all new files and remove all missing files from the
177 repository.
177 repository.
178
178
179 New files are ignored if they match any of the patterns in
179 New files are ignored if they match any of the patterns in
180 ``.hgignore``. As with add, these changes take effect at the next
180 ``.hgignore``. As with add, these changes take effect at the next
181 commit.
181 commit.
182
182
183 Use the -s/--similarity option to detect renamed files. With a
183 Use the -s/--similarity option to detect renamed files. With a
184 parameter greater than 0, this compares every removed file with
184 parameter greater than 0, this compares every removed file with
185 every added file and records those similar enough as renames. This
185 every added file and records those similar enough as renames. This
186 option takes a percentage between 0 (disabled) and 100 (files must
186 option takes a percentage between 0 (disabled) and 100 (files must
187 be identical) as its parameter. Detecting renamed files this way
187 be identical) as its parameter. Detecting renamed files this way
188 can be expensive. After using this option, :hg:`status -C` can be
188 can be expensive. After using this option, :hg:`status -C` can be
189 used to check which files were identified as moved or renamed.
189 used to check which files were identified as moved or renamed.
190
190
191 Returns 0 if all files are successfully added.
191 Returns 0 if all files are successfully added.
192 """
192 """
193 try:
193 try:
194 sim = float(opts.get('similarity') or 100)
194 sim = float(opts.get('similarity') or 100)
195 except ValueError:
195 except ValueError:
196 raise util.Abort(_('similarity must be a number'))
196 raise util.Abort(_('similarity must be a number'))
197 if sim < 0 or sim > 100:
197 if sim < 0 or sim > 100:
198 raise util.Abort(_('similarity must be between 0 and 100'))
198 raise util.Abort(_('similarity must be between 0 and 100'))
199 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
199 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
200
200
201 @command('^annotate|blame',
201 @command('^annotate|blame',
202 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
202 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
203 ('', 'follow', None,
203 ('', 'follow', None,
204 _('follow copies/renames and list the filename (DEPRECATED)')),
204 _('follow copies/renames and list the filename (DEPRECATED)')),
205 ('', 'no-follow', None, _("don't follow copies and renames")),
205 ('', 'no-follow', None, _("don't follow copies and renames")),
206 ('a', 'text', None, _('treat all files as text')),
206 ('a', 'text', None, _('treat all files as text')),
207 ('u', 'user', None, _('list the author (long with -v)')),
207 ('u', 'user', None, _('list the author (long with -v)')),
208 ('f', 'file', None, _('list the filename')),
208 ('f', 'file', None, _('list the filename')),
209 ('d', 'date', None, _('list the date (short with -q)')),
209 ('d', 'date', None, _('list the date (short with -q)')),
210 ('n', 'number', None, _('list the revision number (default)')),
210 ('n', 'number', None, _('list the revision number (default)')),
211 ('c', 'changeset', None, _('list the changeset')),
211 ('c', 'changeset', None, _('list the changeset')),
212 ('l', 'line-number', None, _('show line number at the first appearance'))
212 ('l', 'line-number', None, _('show line number at the first appearance'))
213 ] + walkopts,
213 ] + walkopts,
214 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
214 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
215 def annotate(ui, repo, *pats, **opts):
215 def annotate(ui, repo, *pats, **opts):
216 """show changeset information by line for each file
216 """show changeset information by line for each file
217
217
218 List changes in files, showing the revision id responsible for
218 List changes in files, showing the revision id responsible for
219 each line
219 each line
220
220
221 This command is useful for discovering when a change was made and
221 This command is useful for discovering when a change was made and
222 by whom.
222 by whom.
223
223
224 Without the -a/--text option, annotate will avoid processing files
224 Without the -a/--text option, annotate will avoid processing files
225 it detects as binary. With -a, annotate will annotate the file
225 it detects as binary. With -a, annotate will annotate the file
226 anyway, although the results will probably be neither useful
226 anyway, although the results will probably be neither useful
227 nor desirable.
227 nor desirable.
228
228
229 Returns 0 on success.
229 Returns 0 on success.
230 """
230 """
231 if opts.get('follow'):
231 if opts.get('follow'):
232 # --follow is deprecated and now just an alias for -f/--file
232 # --follow is deprecated and now just an alias for -f/--file
233 # to mimic the behavior of Mercurial before version 1.5
233 # to mimic the behavior of Mercurial before version 1.5
234 opts['file'] = True
234 opts['file'] = True
235
235
236 datefunc = ui.quiet and util.shortdate or util.datestr
236 datefunc = ui.quiet and util.shortdate or util.datestr
237 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
237 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
238
238
239 if not pats:
239 if not pats:
240 raise util.Abort(_('at least one filename or pattern is required'))
240 raise util.Abort(_('at least one filename or pattern is required'))
241
241
242 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
242 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
243 ('number', ' ', lambda x: str(x[0].rev())),
243 ('number', ' ', lambda x: str(x[0].rev())),
244 ('changeset', ' ', lambda x: short(x[0].node())),
244 ('changeset', ' ', lambda x: short(x[0].node())),
245 ('date', ' ', getdate),
245 ('date', ' ', getdate),
246 ('file', ' ', lambda x: x[0].path()),
246 ('file', ' ', lambda x: x[0].path()),
247 ('line_number', ':', lambda x: str(x[1])),
247 ('line_number', ':', lambda x: str(x[1])),
248 ]
248 ]
249
249
250 if (not opts.get('user') and not opts.get('changeset')
250 if (not opts.get('user') and not opts.get('changeset')
251 and not opts.get('date') and not opts.get('file')):
251 and not opts.get('date') and not opts.get('file')):
252 opts['number'] = True
252 opts['number'] = True
253
253
254 linenumber = opts.get('line_number') is not None
254 linenumber = opts.get('line_number') is not None
255 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
255 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
256 raise util.Abort(_('at least one of -n/-c is required for -l'))
256 raise util.Abort(_('at least one of -n/-c is required for -l'))
257
257
258 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
258 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
259 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
259 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
260
260
261 def bad(x, y):
261 def bad(x, y):
262 raise util.Abort("%s: %s" % (x, y))
262 raise util.Abort("%s: %s" % (x, y))
263
263
264 ctx = scmutil.revsingle(repo, opts.get('rev'))
264 ctx = scmutil.revsingle(repo, opts.get('rev'))
265 m = scmutil.match(repo, pats, opts)
265 m = scmutil.match(ctx, pats, opts)
266 m.bad = bad
266 m.bad = bad
267 follow = not opts.get('no_follow')
267 follow = not opts.get('no_follow')
268 for abs in ctx.walk(m):
268 for abs in ctx.walk(m):
269 fctx = ctx[abs]
269 fctx = ctx[abs]
270 if not opts.get('text') and util.binary(fctx.data()):
270 if not opts.get('text') and util.binary(fctx.data()):
271 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
271 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
272 continue
272 continue
273
273
274 lines = fctx.annotate(follow=follow, linenumber=linenumber)
274 lines = fctx.annotate(follow=follow, linenumber=linenumber)
275 pieces = []
275 pieces = []
276
276
277 for f, sep in funcmap:
277 for f, sep in funcmap:
278 l = [f(n) for n, dummy in lines]
278 l = [f(n) for n, dummy in lines]
279 if l:
279 if l:
280 sized = [(x, encoding.colwidth(x)) for x in l]
280 sized = [(x, encoding.colwidth(x)) for x in l]
281 ml = max([w for x, w in sized])
281 ml = max([w for x, w in sized])
282 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
282 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
283 for x, w in sized])
283 for x, w in sized])
284
284
285 if pieces:
285 if pieces:
286 for p, l in zip(zip(*pieces), lines):
286 for p, l in zip(zip(*pieces), lines):
287 ui.write("%s: %s" % ("".join(p), l[1]))
287 ui.write("%s: %s" % ("".join(p), l[1]))
288
288
289 @command('archive',
289 @command('archive',
290 [('', 'no-decode', None, _('do not pass files through decoders')),
290 [('', 'no-decode', None, _('do not pass files through decoders')),
291 ('p', 'prefix', '', _('directory prefix for files in archive'),
291 ('p', 'prefix', '', _('directory prefix for files in archive'),
292 _('PREFIX')),
292 _('PREFIX')),
293 ('r', 'rev', '', _('revision to distribute'), _('REV')),
293 ('r', 'rev', '', _('revision to distribute'), _('REV')),
294 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
294 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
295 ] + subrepoopts + walkopts,
295 ] + subrepoopts + walkopts,
296 _('[OPTION]... DEST'))
296 _('[OPTION]... DEST'))
297 def archive(ui, repo, dest, **opts):
297 def archive(ui, repo, dest, **opts):
298 '''create an unversioned archive of a repository revision
298 '''create an unversioned archive of a repository revision
299
299
300 By default, the revision used is the parent of the working
300 By default, the revision used is the parent of the working
301 directory; use -r/--rev to specify a different revision.
301 directory; use -r/--rev to specify a different revision.
302
302
303 The archive type is automatically detected based on file
303 The archive type is automatically detected based on file
304 extension (or override using -t/--type).
304 extension (or override using -t/--type).
305
305
306 Valid types are:
306 Valid types are:
307
307
308 :``files``: a directory full of files (default)
308 :``files``: a directory full of files (default)
309 :``tar``: tar archive, uncompressed
309 :``tar``: tar archive, uncompressed
310 :``tbz2``: tar archive, compressed using bzip2
310 :``tbz2``: tar archive, compressed using bzip2
311 :``tgz``: tar archive, compressed using gzip
311 :``tgz``: tar archive, compressed using gzip
312 :``uzip``: zip archive, uncompressed
312 :``uzip``: zip archive, uncompressed
313 :``zip``: zip archive, compressed using deflate
313 :``zip``: zip archive, compressed using deflate
314
314
315 The exact name of the destination archive or directory is given
315 The exact name of the destination archive or directory is given
316 using a format string; see :hg:`help export` for details.
316 using a format string; see :hg:`help export` for details.
317
317
318 Each member added to an archive file has a directory prefix
318 Each member added to an archive file has a directory prefix
319 prepended. Use -p/--prefix to specify a format string for the
319 prepended. Use -p/--prefix to specify a format string for the
320 prefix. The default is the basename of the archive, with suffixes
320 prefix. The default is the basename of the archive, with suffixes
321 removed.
321 removed.
322
322
323 Returns 0 on success.
323 Returns 0 on success.
324 '''
324 '''
325
325
326 ctx = scmutil.revsingle(repo, opts.get('rev'))
326 ctx = scmutil.revsingle(repo, opts.get('rev'))
327 if not ctx:
327 if not ctx:
328 raise util.Abort(_('no working directory: please specify a revision'))
328 raise util.Abort(_('no working directory: please specify a revision'))
329 node = ctx.node()
329 node = ctx.node()
330 dest = cmdutil.makefilename(repo, dest, node)
330 dest = cmdutil.makefilename(repo, dest, node)
331 if os.path.realpath(dest) == repo.root:
331 if os.path.realpath(dest) == repo.root:
332 raise util.Abort(_('repository root cannot be destination'))
332 raise util.Abort(_('repository root cannot be destination'))
333
333
334 kind = opts.get('type') or archival.guesskind(dest) or 'files'
334 kind = opts.get('type') or archival.guesskind(dest) or 'files'
335 prefix = opts.get('prefix')
335 prefix = opts.get('prefix')
336
336
337 if dest == '-':
337 if dest == '-':
338 if kind == 'files':
338 if kind == 'files':
339 raise util.Abort(_('cannot archive plain files to stdout'))
339 raise util.Abort(_('cannot archive plain files to stdout'))
340 dest = ui.fout
340 dest = ui.fout
341 if not prefix:
341 if not prefix:
342 prefix = os.path.basename(repo.root) + '-%h'
342 prefix = os.path.basename(repo.root) + '-%h'
343
343
344 prefix = cmdutil.makefilename(repo, prefix, node)
344 prefix = cmdutil.makefilename(repo, prefix, node)
345 matchfn = scmutil.match(repo, [], opts)
345 matchfn = scmutil.match(ctx, [], opts)
346 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
346 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
347 matchfn, prefix, subrepos=opts.get('subrepos'))
347 matchfn, prefix, subrepos=opts.get('subrepos'))
348
348
349 @command('backout',
349 @command('backout',
350 [('', 'merge', None, _('merge with old dirstate parent after backout')),
350 [('', 'merge', None, _('merge with old dirstate parent after backout')),
351 ('', 'parent', '', _('parent to choose when backing out merge'), _('REV')),
351 ('', 'parent', '', _('parent to choose when backing out merge'), _('REV')),
352 ('t', 'tool', '', _('specify merge tool')),
352 ('t', 'tool', '', _('specify merge tool')),
353 ('r', 'rev', '', _('revision to backout'), _('REV')),
353 ('r', 'rev', '', _('revision to backout'), _('REV')),
354 ] + walkopts + commitopts + commitopts2,
354 ] + walkopts + commitopts + commitopts2,
355 _('[OPTION]... [-r] REV'))
355 _('[OPTION]... [-r] REV'))
356 def backout(ui, repo, node=None, rev=None, **opts):
356 def backout(ui, repo, node=None, rev=None, **opts):
357 '''reverse effect of earlier changeset
357 '''reverse effect of earlier changeset
358
358
359 Prepare a new changeset with the effect of REV undone in the
359 Prepare a new changeset with the effect of REV undone in the
360 current working directory.
360 current working directory.
361
361
362 If REV is the parent of the working directory, then this new changeset
362 If REV is the parent of the working directory, then this new changeset
363 is committed automatically. Otherwise, hg needs to merge the
363 is committed automatically. Otherwise, hg needs to merge the
364 changes and the merged result is left uncommitted.
364 changes and the merged result is left uncommitted.
365
365
366 By default, the pending changeset will have one parent,
366 By default, the pending changeset will have one parent,
367 maintaining a linear history. With --merge, the pending changeset
367 maintaining a linear history. With --merge, the pending changeset
368 will instead have two parents: the old parent of the working
368 will instead have two parents: the old parent of the working
369 directory and a new child of REV that simply undoes REV.
369 directory and a new child of REV that simply undoes REV.
370
370
371 Before version 1.7, the behavior without --merge was equivalent to
371 Before version 1.7, the behavior without --merge was equivalent to
372 specifying --merge followed by :hg:`update --clean .` to cancel
372 specifying --merge followed by :hg:`update --clean .` to cancel
373 the merge and leave the child of REV as a head to be merged
373 the merge and leave the child of REV as a head to be merged
374 separately.
374 separately.
375
375
376 See :hg:`help dates` for a list of formats valid for -d/--date.
376 See :hg:`help dates` for a list of formats valid for -d/--date.
377
377
378 Returns 0 on success.
378 Returns 0 on success.
379 '''
379 '''
380 if rev and node:
380 if rev and node:
381 raise util.Abort(_("please specify just one revision"))
381 raise util.Abort(_("please specify just one revision"))
382
382
383 if not rev:
383 if not rev:
384 rev = node
384 rev = node
385
385
386 if not rev:
386 if not rev:
387 raise util.Abort(_("please specify a revision to backout"))
387 raise util.Abort(_("please specify a revision to backout"))
388
388
389 date = opts.get('date')
389 date = opts.get('date')
390 if date:
390 if date:
391 opts['date'] = util.parsedate(date)
391 opts['date'] = util.parsedate(date)
392
392
393 cmdutil.bailifchanged(repo)
393 cmdutil.bailifchanged(repo)
394 node = scmutil.revsingle(repo, rev).node()
394 node = scmutil.revsingle(repo, rev).node()
395
395
396 op1, op2 = repo.dirstate.parents()
396 op1, op2 = repo.dirstate.parents()
397 a = repo.changelog.ancestor(op1, node)
397 a = repo.changelog.ancestor(op1, node)
398 if a != node:
398 if a != node:
399 raise util.Abort(_('cannot backout change on a different branch'))
399 raise util.Abort(_('cannot backout change on a different branch'))
400
400
401 p1, p2 = repo.changelog.parents(node)
401 p1, p2 = repo.changelog.parents(node)
402 if p1 == nullid:
402 if p1 == nullid:
403 raise util.Abort(_('cannot backout a change with no parents'))
403 raise util.Abort(_('cannot backout a change with no parents'))
404 if p2 != nullid:
404 if p2 != nullid:
405 if not opts.get('parent'):
405 if not opts.get('parent'):
406 raise util.Abort(_('cannot backout a merge changeset without '
406 raise util.Abort(_('cannot backout a merge changeset without '
407 '--parent'))
407 '--parent'))
408 p = repo.lookup(opts['parent'])
408 p = repo.lookup(opts['parent'])
409 if p not in (p1, p2):
409 if p not in (p1, p2):
410 raise util.Abort(_('%s is not a parent of %s') %
410 raise util.Abort(_('%s is not a parent of %s') %
411 (short(p), short(node)))
411 (short(p), short(node)))
412 parent = p
412 parent = p
413 else:
413 else:
414 if opts.get('parent'):
414 if opts.get('parent'):
415 raise util.Abort(_('cannot use --parent on non-merge changeset'))
415 raise util.Abort(_('cannot use --parent on non-merge changeset'))
416 parent = p1
416 parent = p1
417
417
418 # the backout should appear on the same branch
418 # the backout should appear on the same branch
419 branch = repo.dirstate.branch()
419 branch = repo.dirstate.branch()
420 hg.clean(repo, node, show_stats=False)
420 hg.clean(repo, node, show_stats=False)
421 repo.dirstate.setbranch(branch)
421 repo.dirstate.setbranch(branch)
422 revert_opts = opts.copy()
422 revert_opts = opts.copy()
423 revert_opts['date'] = None
423 revert_opts['date'] = None
424 revert_opts['all'] = True
424 revert_opts['all'] = True
425 revert_opts['rev'] = hex(parent)
425 revert_opts['rev'] = hex(parent)
426 revert_opts['no_backup'] = None
426 revert_opts['no_backup'] = None
427 revert(ui, repo, **revert_opts)
427 revert(ui, repo, **revert_opts)
428 if not opts.get('merge') and op1 != node:
428 if not opts.get('merge') and op1 != node:
429 try:
429 try:
430 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
430 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
431 return hg.update(repo, op1)
431 return hg.update(repo, op1)
432 finally:
432 finally:
433 ui.setconfig('ui', 'forcemerge', '')
433 ui.setconfig('ui', 'forcemerge', '')
434
434
435 commit_opts = opts.copy()
435 commit_opts = opts.copy()
436 commit_opts['addremove'] = False
436 commit_opts['addremove'] = False
437 if not commit_opts['message'] and not commit_opts['logfile']:
437 if not commit_opts['message'] and not commit_opts['logfile']:
438 # we don't translate commit messages
438 # we don't translate commit messages
439 commit_opts['message'] = "Backed out changeset %s" % short(node)
439 commit_opts['message'] = "Backed out changeset %s" % short(node)
440 commit_opts['force_editor'] = True
440 commit_opts['force_editor'] = True
441 commit(ui, repo, **commit_opts)
441 commit(ui, repo, **commit_opts)
442 def nice(node):
442 def nice(node):
443 return '%d:%s' % (repo.changelog.rev(node), short(node))
443 return '%d:%s' % (repo.changelog.rev(node), short(node))
444 ui.status(_('changeset %s backs out changeset %s\n') %
444 ui.status(_('changeset %s backs out changeset %s\n') %
445 (nice(repo.changelog.tip()), nice(node)))
445 (nice(repo.changelog.tip()), nice(node)))
446 if opts.get('merge') and op1 != node:
446 if opts.get('merge') and op1 != node:
447 hg.clean(repo, op1, show_stats=False)
447 hg.clean(repo, op1, show_stats=False)
448 ui.status(_('merging with changeset %s\n')
448 ui.status(_('merging with changeset %s\n')
449 % nice(repo.changelog.tip()))
449 % nice(repo.changelog.tip()))
450 try:
450 try:
451 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
451 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
452 return hg.merge(repo, hex(repo.changelog.tip()))
452 return hg.merge(repo, hex(repo.changelog.tip()))
453 finally:
453 finally:
454 ui.setconfig('ui', 'forcemerge', '')
454 ui.setconfig('ui', 'forcemerge', '')
455 return 0
455 return 0
456
456
457 @command('bisect',
457 @command('bisect',
458 [('r', 'reset', False, _('reset bisect state')),
458 [('r', 'reset', False, _('reset bisect state')),
459 ('g', 'good', False, _('mark changeset good')),
459 ('g', 'good', False, _('mark changeset good')),
460 ('b', 'bad', False, _('mark changeset bad')),
460 ('b', 'bad', False, _('mark changeset bad')),
461 ('s', 'skip', False, _('skip testing changeset')),
461 ('s', 'skip', False, _('skip testing changeset')),
462 ('e', 'extend', False, _('extend the bisect range')),
462 ('e', 'extend', False, _('extend the bisect range')),
463 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
463 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
464 ('U', 'noupdate', False, _('do not update to target'))],
464 ('U', 'noupdate', False, _('do not update to target'))],
465 _("[-gbsr] [-U] [-c CMD] [REV]"))
465 _("[-gbsr] [-U] [-c CMD] [REV]"))
466 def bisect(ui, repo, rev=None, extra=None, command=None,
466 def bisect(ui, repo, rev=None, extra=None, command=None,
467 reset=None, good=None, bad=None, skip=None, extend=None,
467 reset=None, good=None, bad=None, skip=None, extend=None,
468 noupdate=None):
468 noupdate=None):
469 """subdivision search of changesets
469 """subdivision search of changesets
470
470
471 This command helps to find changesets which introduce problems. To
471 This command helps to find changesets which introduce problems. To
472 use, mark the earliest changeset you know exhibits the problem as
472 use, mark the earliest changeset you know exhibits the problem as
473 bad, then mark the latest changeset which is free from the problem
473 bad, then mark the latest changeset which is free from the problem
474 as good. Bisect will update your working directory to a revision
474 as good. Bisect will update your working directory to a revision
475 for testing (unless the -U/--noupdate option is specified). Once
475 for testing (unless the -U/--noupdate option is specified). Once
476 you have performed tests, mark the working directory as good or
476 you have performed tests, mark the working directory as good or
477 bad, and bisect will either update to another candidate changeset
477 bad, and bisect will either update to another candidate changeset
478 or announce that it has found the bad revision.
478 or announce that it has found the bad revision.
479
479
480 As a shortcut, you can also use the revision argument to mark a
480 As a shortcut, you can also use the revision argument to mark a
481 revision as good or bad without checking it out first.
481 revision as good or bad without checking it out first.
482
482
483 If you supply a command, it will be used for automatic bisection.
483 If you supply a command, it will be used for automatic bisection.
484 Its exit status will be used to mark revisions as good or bad:
484 Its exit status will be used to mark revisions as good or bad:
485 status 0 means good, 125 means to skip the revision, 127
485 status 0 means good, 125 means to skip the revision, 127
486 (command not found) will abort the bisection, and any other
486 (command not found) will abort the bisection, and any other
487 non-zero exit status means the revision is bad.
487 non-zero exit status means the revision is bad.
488
488
489 Returns 0 on success.
489 Returns 0 on success.
490 """
490 """
491 def extendbisectrange(nodes, good):
491 def extendbisectrange(nodes, good):
492 # bisect is incomplete when it ends on a merge node and
492 # bisect is incomplete when it ends on a merge node and
493 # one of the parent was not checked.
493 # one of the parent was not checked.
494 parents = repo[nodes[0]].parents()
494 parents = repo[nodes[0]].parents()
495 if len(parents) > 1:
495 if len(parents) > 1:
496 side = good and state['bad'] or state['good']
496 side = good and state['bad'] or state['good']
497 num = len(set(i.node() for i in parents) & set(side))
497 num = len(set(i.node() for i in parents) & set(side))
498 if num == 1:
498 if num == 1:
499 return parents[0].ancestor(parents[1])
499 return parents[0].ancestor(parents[1])
500 return None
500 return None
501
501
502 def print_result(nodes, good):
502 def print_result(nodes, good):
503 displayer = cmdutil.show_changeset(ui, repo, {})
503 displayer = cmdutil.show_changeset(ui, repo, {})
504 if len(nodes) == 1:
504 if len(nodes) == 1:
505 # narrowed it down to a single revision
505 # narrowed it down to a single revision
506 if good:
506 if good:
507 ui.write(_("The first good revision is:\n"))
507 ui.write(_("The first good revision is:\n"))
508 else:
508 else:
509 ui.write(_("The first bad revision is:\n"))
509 ui.write(_("The first bad revision is:\n"))
510 displayer.show(repo[nodes[0]])
510 displayer.show(repo[nodes[0]])
511 extendnode = extendbisectrange(nodes, good)
511 extendnode = extendbisectrange(nodes, good)
512 if extendnode is not None:
512 if extendnode is not None:
513 ui.write(_('Not all ancestors of this changeset have been'
513 ui.write(_('Not all ancestors of this changeset have been'
514 ' checked.\nUse bisect --extend to continue the '
514 ' checked.\nUse bisect --extend to continue the '
515 'bisection from\nthe common ancestor, %s.\n')
515 'bisection from\nthe common ancestor, %s.\n')
516 % extendnode)
516 % extendnode)
517 else:
517 else:
518 # multiple possible revisions
518 # multiple possible revisions
519 if good:
519 if good:
520 ui.write(_("Due to skipped revisions, the first "
520 ui.write(_("Due to skipped revisions, the first "
521 "good revision could be any of:\n"))
521 "good revision could be any of:\n"))
522 else:
522 else:
523 ui.write(_("Due to skipped revisions, the first "
523 ui.write(_("Due to skipped revisions, the first "
524 "bad revision could be any of:\n"))
524 "bad revision could be any of:\n"))
525 for n in nodes:
525 for n in nodes:
526 displayer.show(repo[n])
526 displayer.show(repo[n])
527 displayer.close()
527 displayer.close()
528
528
529 def check_state(state, interactive=True):
529 def check_state(state, interactive=True):
530 if not state['good'] or not state['bad']:
530 if not state['good'] or not state['bad']:
531 if (good or bad or skip or reset) and interactive:
531 if (good or bad or skip or reset) and interactive:
532 return
532 return
533 if not state['good']:
533 if not state['good']:
534 raise util.Abort(_('cannot bisect (no known good revisions)'))
534 raise util.Abort(_('cannot bisect (no known good revisions)'))
535 else:
535 else:
536 raise util.Abort(_('cannot bisect (no known bad revisions)'))
536 raise util.Abort(_('cannot bisect (no known bad revisions)'))
537 return True
537 return True
538
538
539 # backward compatibility
539 # backward compatibility
540 if rev in "good bad reset init".split():
540 if rev in "good bad reset init".split():
541 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
541 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
542 cmd, rev, extra = rev, extra, None
542 cmd, rev, extra = rev, extra, None
543 if cmd == "good":
543 if cmd == "good":
544 good = True
544 good = True
545 elif cmd == "bad":
545 elif cmd == "bad":
546 bad = True
546 bad = True
547 else:
547 else:
548 reset = True
548 reset = True
549 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
549 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
550 raise util.Abort(_('incompatible arguments'))
550 raise util.Abort(_('incompatible arguments'))
551
551
552 if reset:
552 if reset:
553 p = repo.join("bisect.state")
553 p = repo.join("bisect.state")
554 if os.path.exists(p):
554 if os.path.exists(p):
555 os.unlink(p)
555 os.unlink(p)
556 return
556 return
557
557
558 state = hbisect.load_state(repo)
558 state = hbisect.load_state(repo)
559
559
560 if command:
560 if command:
561 changesets = 1
561 changesets = 1
562 try:
562 try:
563 while changesets:
563 while changesets:
564 # update state
564 # update state
565 status = util.system(command)
565 status = util.system(command)
566 if status == 125:
566 if status == 125:
567 transition = "skip"
567 transition = "skip"
568 elif status == 0:
568 elif status == 0:
569 transition = "good"
569 transition = "good"
570 # status < 0 means process was killed
570 # status < 0 means process was killed
571 elif status == 127:
571 elif status == 127:
572 raise util.Abort(_("failed to execute %s") % command)
572 raise util.Abort(_("failed to execute %s") % command)
573 elif status < 0:
573 elif status < 0:
574 raise util.Abort(_("%s killed") % command)
574 raise util.Abort(_("%s killed") % command)
575 else:
575 else:
576 transition = "bad"
576 transition = "bad"
577 ctx = scmutil.revsingle(repo, rev)
577 ctx = scmutil.revsingle(repo, rev)
578 rev = None # clear for future iterations
578 rev = None # clear for future iterations
579 state[transition].append(ctx.node())
579 state[transition].append(ctx.node())
580 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
580 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
581 check_state(state, interactive=False)
581 check_state(state, interactive=False)
582 # bisect
582 # bisect
583 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
583 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
584 # update to next check
584 # update to next check
585 cmdutil.bailifchanged(repo)
585 cmdutil.bailifchanged(repo)
586 hg.clean(repo, nodes[0], show_stats=False)
586 hg.clean(repo, nodes[0], show_stats=False)
587 finally:
587 finally:
588 hbisect.save_state(repo, state)
588 hbisect.save_state(repo, state)
589 print_result(nodes, good)
589 print_result(nodes, good)
590 return
590 return
591
591
592 # update state
592 # update state
593
593
594 if rev:
594 if rev:
595 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
595 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
596 else:
596 else:
597 nodes = [repo.lookup('.')]
597 nodes = [repo.lookup('.')]
598
598
599 if good or bad or skip:
599 if good or bad or skip:
600 if good:
600 if good:
601 state['good'] += nodes
601 state['good'] += nodes
602 elif bad:
602 elif bad:
603 state['bad'] += nodes
603 state['bad'] += nodes
604 elif skip:
604 elif skip:
605 state['skip'] += nodes
605 state['skip'] += nodes
606 hbisect.save_state(repo, state)
606 hbisect.save_state(repo, state)
607
607
608 if not check_state(state):
608 if not check_state(state):
609 return
609 return
610
610
611 # actually bisect
611 # actually bisect
612 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
612 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
613 if extend:
613 if extend:
614 if not changesets:
614 if not changesets:
615 extendnode = extendbisectrange(nodes, good)
615 extendnode = extendbisectrange(nodes, good)
616 if extendnode is not None:
616 if extendnode is not None:
617 ui.write(_("Extending search to changeset %d:%s\n"
617 ui.write(_("Extending search to changeset %d:%s\n"
618 % (extendnode.rev(), extendnode)))
618 % (extendnode.rev(), extendnode)))
619 if noupdate:
619 if noupdate:
620 return
620 return
621 cmdutil.bailifchanged(repo)
621 cmdutil.bailifchanged(repo)
622 return hg.clean(repo, extendnode.node())
622 return hg.clean(repo, extendnode.node())
623 raise util.Abort(_("nothing to extend"))
623 raise util.Abort(_("nothing to extend"))
624
624
625 if changesets == 0:
625 if changesets == 0:
626 print_result(nodes, good)
626 print_result(nodes, good)
627 else:
627 else:
628 assert len(nodes) == 1 # only a single node can be tested next
628 assert len(nodes) == 1 # only a single node can be tested next
629 node = nodes[0]
629 node = nodes[0]
630 # compute the approximate number of remaining tests
630 # compute the approximate number of remaining tests
631 tests, size = 0, 2
631 tests, size = 0, 2
632 while size <= changesets:
632 while size <= changesets:
633 tests, size = tests + 1, size * 2
633 tests, size = tests + 1, size * 2
634 rev = repo.changelog.rev(node)
634 rev = repo.changelog.rev(node)
635 ui.write(_("Testing changeset %d:%s "
635 ui.write(_("Testing changeset %d:%s "
636 "(%d changesets remaining, ~%d tests)\n")
636 "(%d changesets remaining, ~%d tests)\n")
637 % (rev, short(node), changesets, tests))
637 % (rev, short(node), changesets, tests))
638 if not noupdate:
638 if not noupdate:
639 cmdutil.bailifchanged(repo)
639 cmdutil.bailifchanged(repo)
640 return hg.clean(repo, node)
640 return hg.clean(repo, node)
641
641
642 @command('bookmarks',
642 @command('bookmarks',
643 [('f', 'force', False, _('force')),
643 [('f', 'force', False, _('force')),
644 ('r', 'rev', '', _('revision'), _('REV')),
644 ('r', 'rev', '', _('revision'), _('REV')),
645 ('d', 'delete', False, _('delete a given bookmark')),
645 ('d', 'delete', False, _('delete a given bookmark')),
646 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
646 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
647 ('i', 'inactive', False, _('do not mark a new bookmark active'))],
647 ('i', 'inactive', False, _('do not mark a new bookmark active'))],
648 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
648 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
649 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
649 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
650 rename=None, inactive=False):
650 rename=None, inactive=False):
651 '''track a line of development with movable markers
651 '''track a line of development with movable markers
652
652
653 Bookmarks are pointers to certain commits that move when
653 Bookmarks are pointers to certain commits that move when
654 committing. Bookmarks are local. They can be renamed, copied and
654 committing. Bookmarks are local. They can be renamed, copied and
655 deleted. It is possible to use bookmark names in :hg:`merge` and
655 deleted. It is possible to use bookmark names in :hg:`merge` and
656 :hg:`update` to merge and update respectively to a given bookmark.
656 :hg:`update` to merge and update respectively to a given bookmark.
657
657
658 You can use :hg:`bookmark NAME` to set a bookmark on the working
658 You can use :hg:`bookmark NAME` to set a bookmark on the working
659 directory's parent revision with the given name. If you specify
659 directory's parent revision with the given name. If you specify
660 a revision using -r REV (where REV may be an existing bookmark),
660 a revision using -r REV (where REV may be an existing bookmark),
661 the bookmark is assigned to that revision.
661 the bookmark is assigned to that revision.
662
662
663 Bookmarks can be pushed and pulled between repositories (see :hg:`help
663 Bookmarks can be pushed and pulled between repositories (see :hg:`help
664 push` and :hg:`help pull`). This requires both the local and remote
664 push` and :hg:`help pull`). This requires both the local and remote
665 repositories to support bookmarks. For versions prior to 1.8, this means
665 repositories to support bookmarks. For versions prior to 1.8, this means
666 the bookmarks extension must be enabled.
666 the bookmarks extension must be enabled.
667 '''
667 '''
668 hexfn = ui.debugflag and hex or short
668 hexfn = ui.debugflag and hex or short
669 marks = repo._bookmarks
669 marks = repo._bookmarks
670 cur = repo.changectx('.').node()
670 cur = repo.changectx('.').node()
671
671
672 if rename:
672 if rename:
673 if rename not in marks:
673 if rename not in marks:
674 raise util.Abort(_("bookmark '%s' does not exist") % rename)
674 raise util.Abort(_("bookmark '%s' does not exist") % rename)
675 if mark in marks and not force:
675 if mark in marks and not force:
676 raise util.Abort(_("bookmark '%s' already exists "
676 raise util.Abort(_("bookmark '%s' already exists "
677 "(use -f to force)") % mark)
677 "(use -f to force)") % mark)
678 if mark is None:
678 if mark is None:
679 raise util.Abort(_("new bookmark name required"))
679 raise util.Abort(_("new bookmark name required"))
680 marks[mark] = marks[rename]
680 marks[mark] = marks[rename]
681 if repo._bookmarkcurrent == rename and not inactive:
681 if repo._bookmarkcurrent == rename and not inactive:
682 bookmarks.setcurrent(repo, mark)
682 bookmarks.setcurrent(repo, mark)
683 del marks[rename]
683 del marks[rename]
684 bookmarks.write(repo)
684 bookmarks.write(repo)
685 return
685 return
686
686
687 if delete:
687 if delete:
688 if mark is None:
688 if mark is None:
689 raise util.Abort(_("bookmark name required"))
689 raise util.Abort(_("bookmark name required"))
690 if mark not in marks:
690 if mark not in marks:
691 raise util.Abort(_("bookmark '%s' does not exist") % mark)
691 raise util.Abort(_("bookmark '%s' does not exist") % mark)
692 if mark == repo._bookmarkcurrent:
692 if mark == repo._bookmarkcurrent:
693 bookmarks.setcurrent(repo, None)
693 bookmarks.setcurrent(repo, None)
694 del marks[mark]
694 del marks[mark]
695 bookmarks.write(repo)
695 bookmarks.write(repo)
696 return
696 return
697
697
698 if mark is not None:
698 if mark is not None:
699 if "\n" in mark:
699 if "\n" in mark:
700 raise util.Abort(_("bookmark name cannot contain newlines"))
700 raise util.Abort(_("bookmark name cannot contain newlines"))
701 mark = mark.strip()
701 mark = mark.strip()
702 if not mark:
702 if not mark:
703 raise util.Abort(_("bookmark names cannot consist entirely of "
703 raise util.Abort(_("bookmark names cannot consist entirely of "
704 "whitespace"))
704 "whitespace"))
705 if inactive and mark == repo._bookmarkcurrent:
705 if inactive and mark == repo._bookmarkcurrent:
706 bookmarks.setcurrent(repo, None)
706 bookmarks.setcurrent(repo, None)
707 return
707 return
708 if mark in marks and not force:
708 if mark in marks and not force:
709 raise util.Abort(_("bookmark '%s' already exists "
709 raise util.Abort(_("bookmark '%s' already exists "
710 "(use -f to force)") % mark)
710 "(use -f to force)") % mark)
711 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
711 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
712 and not force):
712 and not force):
713 raise util.Abort(
713 raise util.Abort(
714 _("a bookmark cannot have the name of an existing branch"))
714 _("a bookmark cannot have the name of an existing branch"))
715 if rev:
715 if rev:
716 marks[mark] = repo.lookup(rev)
716 marks[mark] = repo.lookup(rev)
717 else:
717 else:
718 marks[mark] = repo.changectx('.').node()
718 marks[mark] = repo.changectx('.').node()
719 if not inactive and repo.changectx('.').node() == marks[mark]:
719 if not inactive and repo.changectx('.').node() == marks[mark]:
720 bookmarks.setcurrent(repo, mark)
720 bookmarks.setcurrent(repo, mark)
721 bookmarks.write(repo)
721 bookmarks.write(repo)
722 return
722 return
723
723
724 if mark is None:
724 if mark is None:
725 if rev:
725 if rev:
726 raise util.Abort(_("bookmark name required"))
726 raise util.Abort(_("bookmark name required"))
727 if len(marks) == 0:
727 if len(marks) == 0:
728 ui.status(_("no bookmarks set\n"))
728 ui.status(_("no bookmarks set\n"))
729 else:
729 else:
730 for bmark, n in sorted(marks.iteritems()):
730 for bmark, n in sorted(marks.iteritems()):
731 current = repo._bookmarkcurrent
731 current = repo._bookmarkcurrent
732 if bmark == current and n == cur:
732 if bmark == current and n == cur:
733 prefix, label = '*', 'bookmarks.current'
733 prefix, label = '*', 'bookmarks.current'
734 else:
734 else:
735 prefix, label = ' ', ''
735 prefix, label = ' ', ''
736
736
737 if ui.quiet:
737 if ui.quiet:
738 ui.write("%s\n" % bmark, label=label)
738 ui.write("%s\n" % bmark, label=label)
739 else:
739 else:
740 ui.write(" %s %-25s %d:%s\n" % (
740 ui.write(" %s %-25s %d:%s\n" % (
741 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
741 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
742 label=label)
742 label=label)
743 return
743 return
744
744
745 @command('branch',
745 @command('branch',
746 [('f', 'force', None,
746 [('f', 'force', None,
747 _('set branch name even if it shadows an existing branch')),
747 _('set branch name even if it shadows an existing branch')),
748 ('C', 'clean', None, _('reset branch name to parent branch name'))],
748 ('C', 'clean', None, _('reset branch name to parent branch name'))],
749 _('[-fC] [NAME]'))
749 _('[-fC] [NAME]'))
750 def branch(ui, repo, label=None, **opts):
750 def branch(ui, repo, label=None, **opts):
751 """set or show the current branch name
751 """set or show the current branch name
752
752
753 With no argument, show the current branch name. With one argument,
753 With no argument, show the current branch name. With one argument,
754 set the working directory branch name (the branch will not exist
754 set the working directory branch name (the branch will not exist
755 in the repository until the next commit). Standard practice
755 in the repository until the next commit). Standard practice
756 recommends that primary development take place on the 'default'
756 recommends that primary development take place on the 'default'
757 branch.
757 branch.
758
758
759 Unless -f/--force is specified, branch will not let you set a
759 Unless -f/--force is specified, branch will not let you set a
760 branch name that already exists, even if it's inactive.
760 branch name that already exists, even if it's inactive.
761
761
762 Use -C/--clean to reset the working directory branch to that of
762 Use -C/--clean to reset the working directory branch to that of
763 the parent of the working directory, negating a previous branch
763 the parent of the working directory, negating a previous branch
764 change.
764 change.
765
765
766 Use the command :hg:`update` to switch to an existing branch. Use
766 Use the command :hg:`update` to switch to an existing branch. Use
767 :hg:`commit --close-branch` to mark this branch as closed.
767 :hg:`commit --close-branch` to mark this branch as closed.
768
768
769 .. note::
769 .. note::
770
770
771 Branch names are permanent. Use :hg:`bookmark` to create a
771 Branch names are permanent. Use :hg:`bookmark` to create a
772 light-weight bookmark instead. See :hg:`help glossary` for more
772 light-weight bookmark instead. See :hg:`help glossary` for more
773 information about named branches and bookmarks.
773 information about named branches and bookmarks.
774
774
775 Returns 0 on success.
775 Returns 0 on success.
776 """
776 """
777
777
778 if opts.get('clean'):
778 if opts.get('clean'):
779 label = repo[None].p1().branch()
779 label = repo[None].p1().branch()
780 repo.dirstate.setbranch(label)
780 repo.dirstate.setbranch(label)
781 ui.status(_('reset working directory to branch %s\n') % label)
781 ui.status(_('reset working directory to branch %s\n') % label)
782 elif label:
782 elif label:
783 if not opts.get('force') and label in repo.branchtags():
783 if not opts.get('force') and label in repo.branchtags():
784 if label not in [p.branch() for p in repo.parents()]:
784 if label not in [p.branch() for p in repo.parents()]:
785 raise util.Abort(_('a branch of the same name already exists'),
785 raise util.Abort(_('a branch of the same name already exists'),
786 # i18n: "it" refers to an existing branch
786 # i18n: "it" refers to an existing branch
787 hint=_("use 'hg update' to switch to it"))
787 hint=_("use 'hg update' to switch to it"))
788 repo.dirstate.setbranch(label)
788 repo.dirstate.setbranch(label)
789 ui.status(_('marked working directory as branch %s\n') % label)
789 ui.status(_('marked working directory as branch %s\n') % label)
790 else:
790 else:
791 ui.write("%s\n" % repo.dirstate.branch())
791 ui.write("%s\n" % repo.dirstate.branch())
792
792
793 @command('branches',
793 @command('branches',
794 [('a', 'active', False, _('show only branches that have unmerged heads')),
794 [('a', 'active', False, _('show only branches that have unmerged heads')),
795 ('c', 'closed', False, _('show normal and closed branches'))],
795 ('c', 'closed', False, _('show normal and closed branches'))],
796 _('[-ac]'))
796 _('[-ac]'))
797 def branches(ui, repo, active=False, closed=False):
797 def branches(ui, repo, active=False, closed=False):
798 """list repository named branches
798 """list repository named branches
799
799
800 List the repository's named branches, indicating which ones are
800 List the repository's named branches, indicating which ones are
801 inactive. If -c/--closed is specified, also list branches which have
801 inactive. If -c/--closed is specified, also list branches which have
802 been marked closed (see :hg:`commit --close-branch`).
802 been marked closed (see :hg:`commit --close-branch`).
803
803
804 If -a/--active is specified, only show active branches. A branch
804 If -a/--active is specified, only show active branches. A branch
805 is considered active if it contains repository heads.
805 is considered active if it contains repository heads.
806
806
807 Use the command :hg:`update` to switch to an existing branch.
807 Use the command :hg:`update` to switch to an existing branch.
808
808
809 Returns 0.
809 Returns 0.
810 """
810 """
811
811
812 hexfunc = ui.debugflag and hex or short
812 hexfunc = ui.debugflag and hex or short
813 activebranches = [repo[n].branch() for n in repo.heads()]
813 activebranches = [repo[n].branch() for n in repo.heads()]
814 def testactive(tag, node):
814 def testactive(tag, node):
815 realhead = tag in activebranches
815 realhead = tag in activebranches
816 open = node in repo.branchheads(tag, closed=False)
816 open = node in repo.branchheads(tag, closed=False)
817 return realhead and open
817 return realhead and open
818 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
818 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
819 for tag, node in repo.branchtags().items()],
819 for tag, node in repo.branchtags().items()],
820 reverse=True)
820 reverse=True)
821
821
822 for isactive, node, tag in branches:
822 for isactive, node, tag in branches:
823 if (not active) or isactive:
823 if (not active) or isactive:
824 if ui.quiet:
824 if ui.quiet:
825 ui.write("%s\n" % tag)
825 ui.write("%s\n" % tag)
826 else:
826 else:
827 hn = repo.lookup(node)
827 hn = repo.lookup(node)
828 if isactive:
828 if isactive:
829 label = 'branches.active'
829 label = 'branches.active'
830 notice = ''
830 notice = ''
831 elif hn not in repo.branchheads(tag, closed=False):
831 elif hn not in repo.branchheads(tag, closed=False):
832 if not closed:
832 if not closed:
833 continue
833 continue
834 label = 'branches.closed'
834 label = 'branches.closed'
835 notice = _(' (closed)')
835 notice = _(' (closed)')
836 else:
836 else:
837 label = 'branches.inactive'
837 label = 'branches.inactive'
838 notice = _(' (inactive)')
838 notice = _(' (inactive)')
839 if tag == repo.dirstate.branch():
839 if tag == repo.dirstate.branch():
840 label = 'branches.current'
840 label = 'branches.current'
841 rev = str(node).rjust(31 - encoding.colwidth(tag))
841 rev = str(node).rjust(31 - encoding.colwidth(tag))
842 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
842 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
843 tag = ui.label(tag, label)
843 tag = ui.label(tag, label)
844 ui.write("%s %s%s\n" % (tag, rev, notice))
844 ui.write("%s %s%s\n" % (tag, rev, notice))
845
845
846 @command('bundle',
846 @command('bundle',
847 [('f', 'force', None, _('run even when the destination is unrelated')),
847 [('f', 'force', None, _('run even when the destination is unrelated')),
848 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
848 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
849 _('REV')),
849 _('REV')),
850 ('b', 'branch', [], _('a specific branch you would like to bundle'),
850 ('b', 'branch', [], _('a specific branch you would like to bundle'),
851 _('BRANCH')),
851 _('BRANCH')),
852 ('', 'base', [],
852 ('', 'base', [],
853 _('a base changeset assumed to be available at the destination'),
853 _('a base changeset assumed to be available at the destination'),
854 _('REV')),
854 _('REV')),
855 ('a', 'all', None, _('bundle all changesets in the repository')),
855 ('a', 'all', None, _('bundle all changesets in the repository')),
856 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
856 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
857 ] + remoteopts,
857 ] + remoteopts,
858 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
858 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
859 def bundle(ui, repo, fname, dest=None, **opts):
859 def bundle(ui, repo, fname, dest=None, **opts):
860 """create a changegroup file
860 """create a changegroup file
861
861
862 Generate a compressed changegroup file collecting changesets not
862 Generate a compressed changegroup file collecting changesets not
863 known to be in another repository.
863 known to be in another repository.
864
864
865 If you omit the destination repository, then hg assumes the
865 If you omit the destination repository, then hg assumes the
866 destination will have all the nodes you specify with --base
866 destination will have all the nodes you specify with --base
867 parameters. To create a bundle containing all changesets, use
867 parameters. To create a bundle containing all changesets, use
868 -a/--all (or --base null).
868 -a/--all (or --base null).
869
869
870 You can change compression method with the -t/--type option.
870 You can change compression method with the -t/--type option.
871 The available compression methods are: none, bzip2, and
871 The available compression methods are: none, bzip2, and
872 gzip (by default, bundles are compressed using bzip2).
872 gzip (by default, bundles are compressed using bzip2).
873
873
874 The bundle file can then be transferred using conventional means
874 The bundle file can then be transferred using conventional means
875 and applied to another repository with the unbundle or pull
875 and applied to another repository with the unbundle or pull
876 command. This is useful when direct push and pull are not
876 command. This is useful when direct push and pull are not
877 available or when exporting an entire repository is undesirable.
877 available or when exporting an entire repository is undesirable.
878
878
879 Applying bundles preserves all changeset contents including
879 Applying bundles preserves all changeset contents including
880 permissions, copy/rename information, and revision history.
880 permissions, copy/rename information, and revision history.
881
881
882 Returns 0 on success, 1 if no changes found.
882 Returns 0 on success, 1 if no changes found.
883 """
883 """
884 revs = None
884 revs = None
885 if 'rev' in opts:
885 if 'rev' in opts:
886 revs = scmutil.revrange(repo, opts['rev'])
886 revs = scmutil.revrange(repo, opts['rev'])
887
887
888 if opts.get('all'):
888 if opts.get('all'):
889 base = ['null']
889 base = ['null']
890 else:
890 else:
891 base = scmutil.revrange(repo, opts.get('base'))
891 base = scmutil.revrange(repo, opts.get('base'))
892 if base:
892 if base:
893 if dest:
893 if dest:
894 raise util.Abort(_("--base is incompatible with specifying "
894 raise util.Abort(_("--base is incompatible with specifying "
895 "a destination"))
895 "a destination"))
896 common = [repo.lookup(rev) for rev in base]
896 common = [repo.lookup(rev) for rev in base]
897 heads = revs and map(repo.lookup, revs) or revs
897 heads = revs and map(repo.lookup, revs) or revs
898 else:
898 else:
899 dest = ui.expandpath(dest or 'default-push', dest or 'default')
899 dest = ui.expandpath(dest or 'default-push', dest or 'default')
900 dest, branches = hg.parseurl(dest, opts.get('branch'))
900 dest, branches = hg.parseurl(dest, opts.get('branch'))
901 other = hg.peer(repo, opts, dest)
901 other = hg.peer(repo, opts, dest)
902 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
902 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
903 heads = revs and map(repo.lookup, revs) or revs
903 heads = revs and map(repo.lookup, revs) or revs
904 common, outheads = discovery.findcommonoutgoing(repo, other,
904 common, outheads = discovery.findcommonoutgoing(repo, other,
905 onlyheads=heads,
905 onlyheads=heads,
906 force=opts.get('force'))
906 force=opts.get('force'))
907
907
908 cg = repo.getbundle('bundle', common=common, heads=heads)
908 cg = repo.getbundle('bundle', common=common, heads=heads)
909 if not cg:
909 if not cg:
910 ui.status(_("no changes found\n"))
910 ui.status(_("no changes found\n"))
911 return 1
911 return 1
912
912
913 bundletype = opts.get('type', 'bzip2').lower()
913 bundletype = opts.get('type', 'bzip2').lower()
914 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
914 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
915 bundletype = btypes.get(bundletype)
915 bundletype = btypes.get(bundletype)
916 if bundletype not in changegroup.bundletypes:
916 if bundletype not in changegroup.bundletypes:
917 raise util.Abort(_('unknown bundle type specified with --type'))
917 raise util.Abort(_('unknown bundle type specified with --type'))
918
918
919 changegroup.writebundle(cg, fname, bundletype)
919 changegroup.writebundle(cg, fname, bundletype)
920
920
921 @command('cat',
921 @command('cat',
922 [('o', 'output', '',
922 [('o', 'output', '',
923 _('print output to file with formatted name'), _('FORMAT')),
923 _('print output to file with formatted name'), _('FORMAT')),
924 ('r', 'rev', '', _('print the given revision'), _('REV')),
924 ('r', 'rev', '', _('print the given revision'), _('REV')),
925 ('', 'decode', None, _('apply any matching decode filter')),
925 ('', 'decode', None, _('apply any matching decode filter')),
926 ] + walkopts,
926 ] + walkopts,
927 _('[OPTION]... FILE...'))
927 _('[OPTION]... FILE...'))
928 def cat(ui, repo, file1, *pats, **opts):
928 def cat(ui, repo, file1, *pats, **opts):
929 """output the current or given revision of files
929 """output the current or given revision of files
930
930
931 Print the specified files as they were at the given revision. If
931 Print the specified files as they were at the given revision. If
932 no revision is given, the parent of the working directory is used,
932 no revision is given, the parent of the working directory is used,
933 or tip if no revision is checked out.
933 or tip if no revision is checked out.
934
934
935 Output may be to a file, in which case the name of the file is
935 Output may be to a file, in which case the name of the file is
936 given using a format string. The formatting rules are the same as
936 given using a format string. The formatting rules are the same as
937 for the export command, with the following additions:
937 for the export command, with the following additions:
938
938
939 :``%s``: basename of file being printed
939 :``%s``: basename of file being printed
940 :``%d``: dirname of file being printed, or '.' if in repository root
940 :``%d``: dirname of file being printed, or '.' if in repository root
941 :``%p``: root-relative path name of file being printed
941 :``%p``: root-relative path name of file being printed
942
942
943 Returns 0 on success.
943 Returns 0 on success.
944 """
944 """
945 ctx = scmutil.revsingle(repo, opts.get('rev'))
945 ctx = scmutil.revsingle(repo, opts.get('rev'))
946 err = 1
946 err = 1
947 m = scmutil.match(repo, (file1,) + pats, opts)
947 m = scmutil.match(ctx, (file1,) + pats, opts)
948 for abs in ctx.walk(m):
948 for abs in ctx.walk(m):
949 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
949 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
950 pathname=abs)
950 pathname=abs)
951 data = ctx[abs].data()
951 data = ctx[abs].data()
952 if opts.get('decode'):
952 if opts.get('decode'):
953 data = repo.wwritedata(abs, data)
953 data = repo.wwritedata(abs, data)
954 fp.write(data)
954 fp.write(data)
955 fp.close()
955 fp.close()
956 err = 0
956 err = 0
957 return err
957 return err
958
958
959 @command('^clone',
959 @command('^clone',
960 [('U', 'noupdate', None,
960 [('U', 'noupdate', None,
961 _('the clone will include an empty working copy (only a repository)')),
961 _('the clone will include an empty working copy (only a repository)')),
962 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
962 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
963 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
963 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
964 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
964 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
965 ('', 'pull', None, _('use pull protocol to copy metadata')),
965 ('', 'pull', None, _('use pull protocol to copy metadata')),
966 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
966 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
967 ] + remoteopts,
967 ] + remoteopts,
968 _('[OPTION]... SOURCE [DEST]'))
968 _('[OPTION]... SOURCE [DEST]'))
969 def clone(ui, source, dest=None, **opts):
969 def clone(ui, source, dest=None, **opts):
970 """make a copy of an existing repository
970 """make a copy of an existing repository
971
971
972 Create a copy of an existing repository in a new directory.
972 Create a copy of an existing repository in a new directory.
973
973
974 If no destination directory name is specified, it defaults to the
974 If no destination directory name is specified, it defaults to the
975 basename of the source.
975 basename of the source.
976
976
977 The location of the source is added to the new repository's
977 The location of the source is added to the new repository's
978 ``.hg/hgrc`` file, as the default to be used for future pulls.
978 ``.hg/hgrc`` file, as the default to be used for future pulls.
979
979
980 See :hg:`help urls` for valid source format details.
980 See :hg:`help urls` for valid source format details.
981
981
982 It is possible to specify an ``ssh://`` URL as the destination, but no
982 It is possible to specify an ``ssh://`` URL as the destination, but no
983 ``.hg/hgrc`` and working directory will be created on the remote side.
983 ``.hg/hgrc`` and working directory will be created on the remote side.
984 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
984 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
985
985
986 A set of changesets (tags, or branch names) to pull may be specified
986 A set of changesets (tags, or branch names) to pull may be specified
987 by listing each changeset (tag, or branch name) with -r/--rev.
987 by listing each changeset (tag, or branch name) with -r/--rev.
988 If -r/--rev is used, the cloned repository will contain only a subset
988 If -r/--rev is used, the cloned repository will contain only a subset
989 of the changesets of the source repository. Only the set of changesets
989 of the changesets of the source repository. Only the set of changesets
990 defined by all -r/--rev options (including all their ancestors)
990 defined by all -r/--rev options (including all their ancestors)
991 will be pulled into the destination repository.
991 will be pulled into the destination repository.
992 No subsequent changesets (including subsequent tags) will be present
992 No subsequent changesets (including subsequent tags) will be present
993 in the destination.
993 in the destination.
994
994
995 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
995 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
996 local source repositories.
996 local source repositories.
997
997
998 For efficiency, hardlinks are used for cloning whenever the source
998 For efficiency, hardlinks are used for cloning whenever the source
999 and destination are on the same filesystem (note this applies only
999 and destination are on the same filesystem (note this applies only
1000 to the repository data, not to the working directory). Some
1000 to the repository data, not to the working directory). Some
1001 filesystems, such as AFS, implement hardlinking incorrectly, but
1001 filesystems, such as AFS, implement hardlinking incorrectly, but
1002 do not report errors. In these cases, use the --pull option to
1002 do not report errors. In these cases, use the --pull option to
1003 avoid hardlinking.
1003 avoid hardlinking.
1004
1004
1005 In some cases, you can clone repositories and the working directory
1005 In some cases, you can clone repositories and the working directory
1006 using full hardlinks with ::
1006 using full hardlinks with ::
1007
1007
1008 $ cp -al REPO REPOCLONE
1008 $ cp -al REPO REPOCLONE
1009
1009
1010 This is the fastest way to clone, but it is not always safe. The
1010 This is the fastest way to clone, but it is not always safe. The
1011 operation is not atomic (making sure REPO is not modified during
1011 operation is not atomic (making sure REPO is not modified during
1012 the operation is up to you) and you have to make sure your editor
1012 the operation is up to you) and you have to make sure your editor
1013 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
1013 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
1014 this is not compatible with certain extensions that place their
1014 this is not compatible with certain extensions that place their
1015 metadata under the .hg directory, such as mq.
1015 metadata under the .hg directory, such as mq.
1016
1016
1017 Mercurial will update the working directory to the first applicable
1017 Mercurial will update the working directory to the first applicable
1018 revision from this list:
1018 revision from this list:
1019
1019
1020 a) null if -U or the source repository has no changesets
1020 a) null if -U or the source repository has no changesets
1021 b) if -u . and the source repository is local, the first parent of
1021 b) if -u . and the source repository is local, the first parent of
1022 the source repository's working directory
1022 the source repository's working directory
1023 c) the changeset specified with -u (if a branch name, this means the
1023 c) the changeset specified with -u (if a branch name, this means the
1024 latest head of that branch)
1024 latest head of that branch)
1025 d) the changeset specified with -r
1025 d) the changeset specified with -r
1026 e) the tipmost head specified with -b
1026 e) the tipmost head specified with -b
1027 f) the tipmost head specified with the url#branch source syntax
1027 f) the tipmost head specified with the url#branch source syntax
1028 g) the tipmost head of the default branch
1028 g) the tipmost head of the default branch
1029 h) tip
1029 h) tip
1030
1030
1031 Returns 0 on success.
1031 Returns 0 on success.
1032 """
1032 """
1033 if opts.get('noupdate') and opts.get('updaterev'):
1033 if opts.get('noupdate') and opts.get('updaterev'):
1034 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1034 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1035
1035
1036 r = hg.clone(ui, opts, source, dest,
1036 r = hg.clone(ui, opts, source, dest,
1037 pull=opts.get('pull'),
1037 pull=opts.get('pull'),
1038 stream=opts.get('uncompressed'),
1038 stream=opts.get('uncompressed'),
1039 rev=opts.get('rev'),
1039 rev=opts.get('rev'),
1040 update=opts.get('updaterev') or not opts.get('noupdate'),
1040 update=opts.get('updaterev') or not opts.get('noupdate'),
1041 branch=opts.get('branch'))
1041 branch=opts.get('branch'))
1042
1042
1043 return r is None
1043 return r is None
1044
1044
1045 @command('^commit|ci',
1045 @command('^commit|ci',
1046 [('A', 'addremove', None,
1046 [('A', 'addremove', None,
1047 _('mark new/missing files as added/removed before committing')),
1047 _('mark new/missing files as added/removed before committing')),
1048 ('', 'close-branch', None,
1048 ('', 'close-branch', None,
1049 _('mark a branch as closed, hiding it from the branch list')),
1049 _('mark a branch as closed, hiding it from the branch list')),
1050 ] + walkopts + commitopts + commitopts2,
1050 ] + walkopts + commitopts + commitopts2,
1051 _('[OPTION]... [FILE]...'))
1051 _('[OPTION]... [FILE]...'))
1052 def commit(ui, repo, *pats, **opts):
1052 def commit(ui, repo, *pats, **opts):
1053 """commit the specified files or all outstanding changes
1053 """commit the specified files or all outstanding changes
1054
1054
1055 Commit changes to the given files into the repository. Unlike a
1055 Commit changes to the given files into the repository. Unlike a
1056 centralized SCM, this operation is a local operation. See
1056 centralized SCM, this operation is a local operation. See
1057 :hg:`push` for a way to actively distribute your changes.
1057 :hg:`push` for a way to actively distribute your changes.
1058
1058
1059 If a list of files is omitted, all changes reported by :hg:`status`
1059 If a list of files is omitted, all changes reported by :hg:`status`
1060 will be committed.
1060 will be committed.
1061
1061
1062 If you are committing the result of a merge, do not provide any
1062 If you are committing the result of a merge, do not provide any
1063 filenames or -I/-X filters.
1063 filenames or -I/-X filters.
1064
1064
1065 If no commit message is specified, Mercurial starts your
1065 If no commit message is specified, Mercurial starts your
1066 configured editor where you can enter a message. In case your
1066 configured editor where you can enter a message. In case your
1067 commit fails, you will find a backup of your message in
1067 commit fails, you will find a backup of your message in
1068 ``.hg/last-message.txt``.
1068 ``.hg/last-message.txt``.
1069
1069
1070 See :hg:`help dates` for a list of formats valid for -d/--date.
1070 See :hg:`help dates` for a list of formats valid for -d/--date.
1071
1071
1072 Returns 0 on success, 1 if nothing changed.
1072 Returns 0 on success, 1 if nothing changed.
1073 """
1073 """
1074 extra = {}
1074 extra = {}
1075 if opts.get('close_branch'):
1075 if opts.get('close_branch'):
1076 if repo['.'].node() not in repo.branchheads():
1076 if repo['.'].node() not in repo.branchheads():
1077 # The topo heads set is included in the branch heads set of the
1077 # The topo heads set is included in the branch heads set of the
1078 # current branch, so it's sufficient to test branchheads
1078 # current branch, so it's sufficient to test branchheads
1079 raise util.Abort(_('can only close branch heads'))
1079 raise util.Abort(_('can only close branch heads'))
1080 extra['close'] = 1
1080 extra['close'] = 1
1081 e = cmdutil.commiteditor
1081 e = cmdutil.commiteditor
1082 if opts.get('force_editor'):
1082 if opts.get('force_editor'):
1083 e = cmdutil.commitforceeditor
1083 e = cmdutil.commitforceeditor
1084
1084
1085 def commitfunc(ui, repo, message, match, opts):
1085 def commitfunc(ui, repo, message, match, opts):
1086 return repo.commit(message, opts.get('user'), opts.get('date'), match,
1086 return repo.commit(message, opts.get('user'), opts.get('date'), match,
1087 editor=e, extra=extra)
1087 editor=e, extra=extra)
1088
1088
1089 branch = repo[None].branch()
1089 branch = repo[None].branch()
1090 bheads = repo.branchheads(branch)
1090 bheads = repo.branchheads(branch)
1091
1091
1092 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1092 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1093 if not node:
1093 if not node:
1094 stat = repo.status(match=scmutil.match(repo, pats, opts))
1094 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1095 if stat[3]:
1095 if stat[3]:
1096 ui.status(_("nothing changed (%d missing files, see 'hg status')\n")
1096 ui.status(_("nothing changed (%d missing files, see 'hg status')\n")
1097 % len(stat[3]))
1097 % len(stat[3]))
1098 else:
1098 else:
1099 ui.status(_("nothing changed\n"))
1099 ui.status(_("nothing changed\n"))
1100 return 1
1100 return 1
1101
1101
1102 ctx = repo[node]
1102 ctx = repo[node]
1103 parents = ctx.parents()
1103 parents = ctx.parents()
1104
1104
1105 if bheads and not [x for x in parents
1105 if bheads and not [x for x in parents
1106 if x.node() in bheads and x.branch() == branch]:
1106 if x.node() in bheads and x.branch() == branch]:
1107 ui.status(_('created new head\n'))
1107 ui.status(_('created new head\n'))
1108 # The message is not printed for initial roots. For the other
1108 # The message is not printed for initial roots. For the other
1109 # changesets, it is printed in the following situations:
1109 # changesets, it is printed in the following situations:
1110 #
1110 #
1111 # Par column: for the 2 parents with ...
1111 # Par column: for the 2 parents with ...
1112 # N: null or no parent
1112 # N: null or no parent
1113 # B: parent is on another named branch
1113 # B: parent is on another named branch
1114 # C: parent is a regular non head changeset
1114 # C: parent is a regular non head changeset
1115 # H: parent was a branch head of the current branch
1115 # H: parent was a branch head of the current branch
1116 # Msg column: whether we print "created new head" message
1116 # Msg column: whether we print "created new head" message
1117 # In the following, it is assumed that there already exists some
1117 # In the following, it is assumed that there already exists some
1118 # initial branch heads of the current branch, otherwise nothing is
1118 # initial branch heads of the current branch, otherwise nothing is
1119 # printed anyway.
1119 # printed anyway.
1120 #
1120 #
1121 # Par Msg Comment
1121 # Par Msg Comment
1122 # NN y additional topo root
1122 # NN y additional topo root
1123 #
1123 #
1124 # BN y additional branch root
1124 # BN y additional branch root
1125 # CN y additional topo head
1125 # CN y additional topo head
1126 # HN n usual case
1126 # HN n usual case
1127 #
1127 #
1128 # BB y weird additional branch root
1128 # BB y weird additional branch root
1129 # CB y branch merge
1129 # CB y branch merge
1130 # HB n merge with named branch
1130 # HB n merge with named branch
1131 #
1131 #
1132 # CC y additional head from merge
1132 # CC y additional head from merge
1133 # CH n merge with a head
1133 # CH n merge with a head
1134 #
1134 #
1135 # HH n head merge: head count decreases
1135 # HH n head merge: head count decreases
1136
1136
1137 if not opts.get('close_branch'):
1137 if not opts.get('close_branch'):
1138 for r in parents:
1138 for r in parents:
1139 if r.extra().get('close') and r.branch() == branch:
1139 if r.extra().get('close') and r.branch() == branch:
1140 ui.status(_('reopening closed branch head %d\n') % r)
1140 ui.status(_('reopening closed branch head %d\n') % r)
1141
1141
1142 if ui.debugflag:
1142 if ui.debugflag:
1143 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1143 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1144 elif ui.verbose:
1144 elif ui.verbose:
1145 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1145 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1146
1146
1147 @command('copy|cp',
1147 @command('copy|cp',
1148 [('A', 'after', None, _('record a copy that has already occurred')),
1148 [('A', 'after', None, _('record a copy that has already occurred')),
1149 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1149 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1150 ] + walkopts + dryrunopts,
1150 ] + walkopts + dryrunopts,
1151 _('[OPTION]... [SOURCE]... DEST'))
1151 _('[OPTION]... [SOURCE]... DEST'))
1152 def copy(ui, repo, *pats, **opts):
1152 def copy(ui, repo, *pats, **opts):
1153 """mark files as copied for the next commit
1153 """mark files as copied for the next commit
1154
1154
1155 Mark dest as having copies of source files. If dest is a
1155 Mark dest as having copies of source files. If dest is a
1156 directory, copies are put in that directory. If dest is a file,
1156 directory, copies are put in that directory. If dest is a file,
1157 the source must be a single file.
1157 the source must be a single file.
1158
1158
1159 By default, this command copies the contents of files as they
1159 By default, this command copies the contents of files as they
1160 exist in the working directory. If invoked with -A/--after, the
1160 exist in the working directory. If invoked with -A/--after, the
1161 operation is recorded, but no copying is performed.
1161 operation is recorded, but no copying is performed.
1162
1162
1163 This command takes effect with the next commit. To undo a copy
1163 This command takes effect with the next commit. To undo a copy
1164 before that, see :hg:`revert`.
1164 before that, see :hg:`revert`.
1165
1165
1166 Returns 0 on success, 1 if errors are encountered.
1166 Returns 0 on success, 1 if errors are encountered.
1167 """
1167 """
1168 wlock = repo.wlock(False)
1168 wlock = repo.wlock(False)
1169 try:
1169 try:
1170 return cmdutil.copy(ui, repo, pats, opts)
1170 return cmdutil.copy(ui, repo, pats, opts)
1171 finally:
1171 finally:
1172 wlock.release()
1172 wlock.release()
1173
1173
1174 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1174 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1175 def debugancestor(ui, repo, *args):
1175 def debugancestor(ui, repo, *args):
1176 """find the ancestor revision of two revisions in a given index"""
1176 """find the ancestor revision of two revisions in a given index"""
1177 if len(args) == 3:
1177 if len(args) == 3:
1178 index, rev1, rev2 = args
1178 index, rev1, rev2 = args
1179 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1179 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1180 lookup = r.lookup
1180 lookup = r.lookup
1181 elif len(args) == 2:
1181 elif len(args) == 2:
1182 if not repo:
1182 if not repo:
1183 raise util.Abort(_("there is no Mercurial repository here "
1183 raise util.Abort(_("there is no Mercurial repository here "
1184 "(.hg not found)"))
1184 "(.hg not found)"))
1185 rev1, rev2 = args
1185 rev1, rev2 = args
1186 r = repo.changelog
1186 r = repo.changelog
1187 lookup = repo.lookup
1187 lookup = repo.lookup
1188 else:
1188 else:
1189 raise util.Abort(_('either two or three arguments required'))
1189 raise util.Abort(_('either two or three arguments required'))
1190 a = r.ancestor(lookup(rev1), lookup(rev2))
1190 a = r.ancestor(lookup(rev1), lookup(rev2))
1191 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1191 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1192
1192
1193 @command('debugbuilddag',
1193 @command('debugbuilddag',
1194 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1194 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1195 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1195 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1196 ('n', 'new-file', None, _('add new file at each rev'))],
1196 ('n', 'new-file', None, _('add new file at each rev'))],
1197 _('[OPTION]... [TEXT]'))
1197 _('[OPTION]... [TEXT]'))
1198 def debugbuilddag(ui, repo, text=None,
1198 def debugbuilddag(ui, repo, text=None,
1199 mergeable_file=False,
1199 mergeable_file=False,
1200 overwritten_file=False,
1200 overwritten_file=False,
1201 new_file=False):
1201 new_file=False):
1202 """builds a repo with a given DAG from scratch in the current empty repo
1202 """builds a repo with a given DAG from scratch in the current empty repo
1203
1203
1204 The description of the DAG is read from stdin if not given on the
1204 The description of the DAG is read from stdin if not given on the
1205 command line.
1205 command line.
1206
1206
1207 Elements:
1207 Elements:
1208
1208
1209 - "+n" is a linear run of n nodes based on the current default parent
1209 - "+n" is a linear run of n nodes based on the current default parent
1210 - "." is a single node based on the current default parent
1210 - "." is a single node based on the current default parent
1211 - "$" resets the default parent to null (implied at the start);
1211 - "$" resets the default parent to null (implied at the start);
1212 otherwise the default parent is always the last node created
1212 otherwise the default parent is always the last node created
1213 - "<p" sets the default parent to the backref p
1213 - "<p" sets the default parent to the backref p
1214 - "*p" is a fork at parent p, which is a backref
1214 - "*p" is a fork at parent p, which is a backref
1215 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1215 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1216 - "/p2" is a merge of the preceding node and p2
1216 - "/p2" is a merge of the preceding node and p2
1217 - ":tag" defines a local tag for the preceding node
1217 - ":tag" defines a local tag for the preceding node
1218 - "@branch" sets the named branch for subsequent nodes
1218 - "@branch" sets the named branch for subsequent nodes
1219 - "#...\\n" is a comment up to the end of the line
1219 - "#...\\n" is a comment up to the end of the line
1220
1220
1221 Whitespace between the above elements is ignored.
1221 Whitespace between the above elements is ignored.
1222
1222
1223 A backref is either
1223 A backref is either
1224
1224
1225 - a number n, which references the node curr-n, where curr is the current
1225 - a number n, which references the node curr-n, where curr is the current
1226 node, or
1226 node, or
1227 - the name of a local tag you placed earlier using ":tag", or
1227 - the name of a local tag you placed earlier using ":tag", or
1228 - empty to denote the default parent.
1228 - empty to denote the default parent.
1229
1229
1230 All string valued-elements are either strictly alphanumeric, or must
1230 All string valued-elements are either strictly alphanumeric, or must
1231 be enclosed in double quotes ("..."), with "\\" as escape character.
1231 be enclosed in double quotes ("..."), with "\\" as escape character.
1232 """
1232 """
1233
1233
1234 if text is None:
1234 if text is None:
1235 ui.status(_("reading DAG from stdin\n"))
1235 ui.status(_("reading DAG from stdin\n"))
1236 text = ui.fin.read()
1236 text = ui.fin.read()
1237
1237
1238 cl = repo.changelog
1238 cl = repo.changelog
1239 if len(cl) > 0:
1239 if len(cl) > 0:
1240 raise util.Abort(_('repository is not empty'))
1240 raise util.Abort(_('repository is not empty'))
1241
1241
1242 # determine number of revs in DAG
1242 # determine number of revs in DAG
1243 total = 0
1243 total = 0
1244 for type, data in dagparser.parsedag(text):
1244 for type, data in dagparser.parsedag(text):
1245 if type == 'n':
1245 if type == 'n':
1246 total += 1
1246 total += 1
1247
1247
1248 if mergeable_file:
1248 if mergeable_file:
1249 linesperrev = 2
1249 linesperrev = 2
1250 # make a file with k lines per rev
1250 # make a file with k lines per rev
1251 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1251 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1252 initialmergedlines.append("")
1252 initialmergedlines.append("")
1253
1253
1254 tags = []
1254 tags = []
1255
1255
1256 tr = repo.transaction("builddag")
1256 tr = repo.transaction("builddag")
1257 try:
1257 try:
1258
1258
1259 at = -1
1259 at = -1
1260 atbranch = 'default'
1260 atbranch = 'default'
1261 nodeids = []
1261 nodeids = []
1262 ui.progress(_('building'), 0, unit=_('revisions'), total=total)
1262 ui.progress(_('building'), 0, unit=_('revisions'), total=total)
1263 for type, data in dagparser.parsedag(text):
1263 for type, data in dagparser.parsedag(text):
1264 if type == 'n':
1264 if type == 'n':
1265 ui.note('node %s\n' % str(data))
1265 ui.note('node %s\n' % str(data))
1266 id, ps = data
1266 id, ps = data
1267
1267
1268 files = []
1268 files = []
1269 fctxs = {}
1269 fctxs = {}
1270
1270
1271 p2 = None
1271 p2 = None
1272 if mergeable_file:
1272 if mergeable_file:
1273 fn = "mf"
1273 fn = "mf"
1274 p1 = repo[ps[0]]
1274 p1 = repo[ps[0]]
1275 if len(ps) > 1:
1275 if len(ps) > 1:
1276 p2 = repo[ps[1]]
1276 p2 = repo[ps[1]]
1277 pa = p1.ancestor(p2)
1277 pa = p1.ancestor(p2)
1278 base, local, other = [x[fn].data() for x in pa, p1, p2]
1278 base, local, other = [x[fn].data() for x in pa, p1, p2]
1279 m3 = simplemerge.Merge3Text(base, local, other)
1279 m3 = simplemerge.Merge3Text(base, local, other)
1280 ml = [l.strip() for l in m3.merge_lines()]
1280 ml = [l.strip() for l in m3.merge_lines()]
1281 ml.append("")
1281 ml.append("")
1282 elif at > 0:
1282 elif at > 0:
1283 ml = p1[fn].data().split("\n")
1283 ml = p1[fn].data().split("\n")
1284 else:
1284 else:
1285 ml = initialmergedlines
1285 ml = initialmergedlines
1286 ml[id * linesperrev] += " r%i" % id
1286 ml[id * linesperrev] += " r%i" % id
1287 mergedtext = "\n".join(ml)
1287 mergedtext = "\n".join(ml)
1288 files.append(fn)
1288 files.append(fn)
1289 fctxs[fn] = context.memfilectx(fn, mergedtext)
1289 fctxs[fn] = context.memfilectx(fn, mergedtext)
1290
1290
1291 if overwritten_file:
1291 if overwritten_file:
1292 fn = "of"
1292 fn = "of"
1293 files.append(fn)
1293 files.append(fn)
1294 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1294 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1295
1295
1296 if new_file:
1296 if new_file:
1297 fn = "nf%i" % id
1297 fn = "nf%i" % id
1298 files.append(fn)
1298 files.append(fn)
1299 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1299 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1300 if len(ps) > 1:
1300 if len(ps) > 1:
1301 if not p2:
1301 if not p2:
1302 p2 = repo[ps[1]]
1302 p2 = repo[ps[1]]
1303 for fn in p2:
1303 for fn in p2:
1304 if fn.startswith("nf"):
1304 if fn.startswith("nf"):
1305 files.append(fn)
1305 files.append(fn)
1306 fctxs[fn] = p2[fn]
1306 fctxs[fn] = p2[fn]
1307
1307
1308 def fctxfn(repo, cx, path):
1308 def fctxfn(repo, cx, path):
1309 return fctxs.get(path)
1309 return fctxs.get(path)
1310
1310
1311 if len(ps) == 0 or ps[0] < 0:
1311 if len(ps) == 0 or ps[0] < 0:
1312 pars = [None, None]
1312 pars = [None, None]
1313 elif len(ps) == 1:
1313 elif len(ps) == 1:
1314 pars = [nodeids[ps[0]], None]
1314 pars = [nodeids[ps[0]], None]
1315 else:
1315 else:
1316 pars = [nodeids[p] for p in ps]
1316 pars = [nodeids[p] for p in ps]
1317 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1317 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1318 date=(id, 0),
1318 date=(id, 0),
1319 user="debugbuilddag",
1319 user="debugbuilddag",
1320 extra={'branch': atbranch})
1320 extra={'branch': atbranch})
1321 nodeid = repo.commitctx(cx)
1321 nodeid = repo.commitctx(cx)
1322 nodeids.append(nodeid)
1322 nodeids.append(nodeid)
1323 at = id
1323 at = id
1324 elif type == 'l':
1324 elif type == 'l':
1325 id, name = data
1325 id, name = data
1326 ui.note('tag %s\n' % name)
1326 ui.note('tag %s\n' % name)
1327 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1327 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1328 elif type == 'a':
1328 elif type == 'a':
1329 ui.note('branch %s\n' % data)
1329 ui.note('branch %s\n' % data)
1330 atbranch = data
1330 atbranch = data
1331 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1331 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1332 tr.close()
1332 tr.close()
1333 finally:
1333 finally:
1334 ui.progress(_('building'), None)
1334 ui.progress(_('building'), None)
1335 tr.release()
1335 tr.release()
1336
1336
1337 if tags:
1337 if tags:
1338 repo.opener.write("localtags", "".join(tags))
1338 repo.opener.write("localtags", "".join(tags))
1339
1339
1340 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1340 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1341 def debugbundle(ui, bundlepath, all=None, **opts):
1341 def debugbundle(ui, bundlepath, all=None, **opts):
1342 """lists the contents of a bundle"""
1342 """lists the contents of a bundle"""
1343 f = url.open(ui, bundlepath)
1343 f = url.open(ui, bundlepath)
1344 try:
1344 try:
1345 gen = changegroup.readbundle(f, bundlepath)
1345 gen = changegroup.readbundle(f, bundlepath)
1346 if all:
1346 if all:
1347 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1347 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1348
1348
1349 def showchunks(named):
1349 def showchunks(named):
1350 ui.write("\n%s\n" % named)
1350 ui.write("\n%s\n" % named)
1351 chain = None
1351 chain = None
1352 while True:
1352 while True:
1353 chunkdata = gen.deltachunk(chain)
1353 chunkdata = gen.deltachunk(chain)
1354 if not chunkdata:
1354 if not chunkdata:
1355 break
1355 break
1356 node = chunkdata['node']
1356 node = chunkdata['node']
1357 p1 = chunkdata['p1']
1357 p1 = chunkdata['p1']
1358 p2 = chunkdata['p2']
1358 p2 = chunkdata['p2']
1359 cs = chunkdata['cs']
1359 cs = chunkdata['cs']
1360 deltabase = chunkdata['deltabase']
1360 deltabase = chunkdata['deltabase']
1361 delta = chunkdata['delta']
1361 delta = chunkdata['delta']
1362 ui.write("%s %s %s %s %s %s\n" %
1362 ui.write("%s %s %s %s %s %s\n" %
1363 (hex(node), hex(p1), hex(p2),
1363 (hex(node), hex(p1), hex(p2),
1364 hex(cs), hex(deltabase), len(delta)))
1364 hex(cs), hex(deltabase), len(delta)))
1365 chain = node
1365 chain = node
1366
1366
1367 chunkdata = gen.changelogheader()
1367 chunkdata = gen.changelogheader()
1368 showchunks("changelog")
1368 showchunks("changelog")
1369 chunkdata = gen.manifestheader()
1369 chunkdata = gen.manifestheader()
1370 showchunks("manifest")
1370 showchunks("manifest")
1371 while True:
1371 while True:
1372 chunkdata = gen.filelogheader()
1372 chunkdata = gen.filelogheader()
1373 if not chunkdata:
1373 if not chunkdata:
1374 break
1374 break
1375 fname = chunkdata['filename']
1375 fname = chunkdata['filename']
1376 showchunks(fname)
1376 showchunks(fname)
1377 else:
1377 else:
1378 chunkdata = gen.changelogheader()
1378 chunkdata = gen.changelogheader()
1379 chain = None
1379 chain = None
1380 while True:
1380 while True:
1381 chunkdata = gen.deltachunk(chain)
1381 chunkdata = gen.deltachunk(chain)
1382 if not chunkdata:
1382 if not chunkdata:
1383 break
1383 break
1384 node = chunkdata['node']
1384 node = chunkdata['node']
1385 ui.write("%s\n" % hex(node))
1385 ui.write("%s\n" % hex(node))
1386 chain = node
1386 chain = node
1387 finally:
1387 finally:
1388 f.close()
1388 f.close()
1389
1389
1390 @command('debugcheckstate', [], '')
1390 @command('debugcheckstate', [], '')
1391 def debugcheckstate(ui, repo):
1391 def debugcheckstate(ui, repo):
1392 """validate the correctness of the current dirstate"""
1392 """validate the correctness of the current dirstate"""
1393 parent1, parent2 = repo.dirstate.parents()
1393 parent1, parent2 = repo.dirstate.parents()
1394 m1 = repo[parent1].manifest()
1394 m1 = repo[parent1].manifest()
1395 m2 = repo[parent2].manifest()
1395 m2 = repo[parent2].manifest()
1396 errors = 0
1396 errors = 0
1397 for f in repo.dirstate:
1397 for f in repo.dirstate:
1398 state = repo.dirstate[f]
1398 state = repo.dirstate[f]
1399 if state in "nr" and f not in m1:
1399 if state in "nr" and f not in m1:
1400 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1400 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1401 errors += 1
1401 errors += 1
1402 if state in "a" and f in m1:
1402 if state in "a" and f in m1:
1403 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1403 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1404 errors += 1
1404 errors += 1
1405 if state in "m" and f not in m1 and f not in m2:
1405 if state in "m" and f not in m1 and f not in m2:
1406 ui.warn(_("%s in state %s, but not in either manifest\n") %
1406 ui.warn(_("%s in state %s, but not in either manifest\n") %
1407 (f, state))
1407 (f, state))
1408 errors += 1
1408 errors += 1
1409 for f in m1:
1409 for f in m1:
1410 state = repo.dirstate[f]
1410 state = repo.dirstate[f]
1411 if state not in "nrm":
1411 if state not in "nrm":
1412 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1412 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1413 errors += 1
1413 errors += 1
1414 if errors:
1414 if errors:
1415 error = _(".hg/dirstate inconsistent with current parent's manifest")
1415 error = _(".hg/dirstate inconsistent with current parent's manifest")
1416 raise util.Abort(error)
1416 raise util.Abort(error)
1417
1417
1418 @command('debugcommands', [], _('[COMMAND]'))
1418 @command('debugcommands', [], _('[COMMAND]'))
1419 def debugcommands(ui, cmd='', *args):
1419 def debugcommands(ui, cmd='', *args):
1420 """list all available commands and options"""
1420 """list all available commands and options"""
1421 for cmd, vals in sorted(table.iteritems()):
1421 for cmd, vals in sorted(table.iteritems()):
1422 cmd = cmd.split('|')[0].strip('^')
1422 cmd = cmd.split('|')[0].strip('^')
1423 opts = ', '.join([i[1] for i in vals[1]])
1423 opts = ', '.join([i[1] for i in vals[1]])
1424 ui.write('%s: %s\n' % (cmd, opts))
1424 ui.write('%s: %s\n' % (cmd, opts))
1425
1425
1426 @command('debugcomplete',
1426 @command('debugcomplete',
1427 [('o', 'options', None, _('show the command options'))],
1427 [('o', 'options', None, _('show the command options'))],
1428 _('[-o] CMD'))
1428 _('[-o] CMD'))
1429 def debugcomplete(ui, cmd='', **opts):
1429 def debugcomplete(ui, cmd='', **opts):
1430 """returns the completion list associated with the given command"""
1430 """returns the completion list associated with the given command"""
1431
1431
1432 if opts.get('options'):
1432 if opts.get('options'):
1433 options = []
1433 options = []
1434 otables = [globalopts]
1434 otables = [globalopts]
1435 if cmd:
1435 if cmd:
1436 aliases, entry = cmdutil.findcmd(cmd, table, False)
1436 aliases, entry = cmdutil.findcmd(cmd, table, False)
1437 otables.append(entry[1])
1437 otables.append(entry[1])
1438 for t in otables:
1438 for t in otables:
1439 for o in t:
1439 for o in t:
1440 if "(DEPRECATED)" in o[3]:
1440 if "(DEPRECATED)" in o[3]:
1441 continue
1441 continue
1442 if o[0]:
1442 if o[0]:
1443 options.append('-%s' % o[0])
1443 options.append('-%s' % o[0])
1444 options.append('--%s' % o[1])
1444 options.append('--%s' % o[1])
1445 ui.write("%s\n" % "\n".join(options))
1445 ui.write("%s\n" % "\n".join(options))
1446 return
1446 return
1447
1447
1448 cmdlist = cmdutil.findpossible(cmd, table)
1448 cmdlist = cmdutil.findpossible(cmd, table)
1449 if ui.verbose:
1449 if ui.verbose:
1450 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1450 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1451 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1451 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1452
1452
1453 @command('debugdag',
1453 @command('debugdag',
1454 [('t', 'tags', None, _('use tags as labels')),
1454 [('t', 'tags', None, _('use tags as labels')),
1455 ('b', 'branches', None, _('annotate with branch names')),
1455 ('b', 'branches', None, _('annotate with branch names')),
1456 ('', 'dots', None, _('use dots for runs')),
1456 ('', 'dots', None, _('use dots for runs')),
1457 ('s', 'spaces', None, _('separate elements by spaces'))],
1457 ('s', 'spaces', None, _('separate elements by spaces'))],
1458 _('[OPTION]... [FILE [REV]...]'))
1458 _('[OPTION]... [FILE [REV]...]'))
1459 def debugdag(ui, repo, file_=None, *revs, **opts):
1459 def debugdag(ui, repo, file_=None, *revs, **opts):
1460 """format the changelog or an index DAG as a concise textual description
1460 """format the changelog or an index DAG as a concise textual description
1461
1461
1462 If you pass a revlog index, the revlog's DAG is emitted. If you list
1462 If you pass a revlog index, the revlog's DAG is emitted. If you list
1463 revision numbers, they get labelled in the output as rN.
1463 revision numbers, they get labelled in the output as rN.
1464
1464
1465 Otherwise, the changelog DAG of the current repo is emitted.
1465 Otherwise, the changelog DAG of the current repo is emitted.
1466 """
1466 """
1467 spaces = opts.get('spaces')
1467 spaces = opts.get('spaces')
1468 dots = opts.get('dots')
1468 dots = opts.get('dots')
1469 if file_:
1469 if file_:
1470 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1470 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1471 revs = set((int(r) for r in revs))
1471 revs = set((int(r) for r in revs))
1472 def events():
1472 def events():
1473 for r in rlog:
1473 for r in rlog:
1474 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1474 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1475 if r in revs:
1475 if r in revs:
1476 yield 'l', (r, "r%i" % r)
1476 yield 'l', (r, "r%i" % r)
1477 elif repo:
1477 elif repo:
1478 cl = repo.changelog
1478 cl = repo.changelog
1479 tags = opts.get('tags')
1479 tags = opts.get('tags')
1480 branches = opts.get('branches')
1480 branches = opts.get('branches')
1481 if tags:
1481 if tags:
1482 labels = {}
1482 labels = {}
1483 for l, n in repo.tags().items():
1483 for l, n in repo.tags().items():
1484 labels.setdefault(cl.rev(n), []).append(l)
1484 labels.setdefault(cl.rev(n), []).append(l)
1485 def events():
1485 def events():
1486 b = "default"
1486 b = "default"
1487 for r in cl:
1487 for r in cl:
1488 if branches:
1488 if branches:
1489 newb = cl.read(cl.node(r))[5]['branch']
1489 newb = cl.read(cl.node(r))[5]['branch']
1490 if newb != b:
1490 if newb != b:
1491 yield 'a', newb
1491 yield 'a', newb
1492 b = newb
1492 b = newb
1493 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1493 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1494 if tags:
1494 if tags:
1495 ls = labels.get(r)
1495 ls = labels.get(r)
1496 if ls:
1496 if ls:
1497 for l in ls:
1497 for l in ls:
1498 yield 'l', (r, l)
1498 yield 'l', (r, l)
1499 else:
1499 else:
1500 raise util.Abort(_('need repo for changelog dag'))
1500 raise util.Abort(_('need repo for changelog dag'))
1501
1501
1502 for line in dagparser.dagtextlines(events(),
1502 for line in dagparser.dagtextlines(events(),
1503 addspaces=spaces,
1503 addspaces=spaces,
1504 wraplabels=True,
1504 wraplabels=True,
1505 wrapannotations=True,
1505 wrapannotations=True,
1506 wrapnonlinear=dots,
1506 wrapnonlinear=dots,
1507 usedots=dots,
1507 usedots=dots,
1508 maxlinewidth=70):
1508 maxlinewidth=70):
1509 ui.write(line)
1509 ui.write(line)
1510 ui.write("\n")
1510 ui.write("\n")
1511
1511
1512 @command('debugdata',
1512 @command('debugdata',
1513 [('c', 'changelog', False, _('open changelog')),
1513 [('c', 'changelog', False, _('open changelog')),
1514 ('m', 'manifest', False, _('open manifest'))],
1514 ('m', 'manifest', False, _('open manifest'))],
1515 _('-c|-m|FILE REV'))
1515 _('-c|-m|FILE REV'))
1516 def debugdata(ui, repo, file_, rev = None, **opts):
1516 def debugdata(ui, repo, file_, rev = None, **opts):
1517 """dump the contents of a data file revision"""
1517 """dump the contents of a data file revision"""
1518 if opts.get('changelog') or opts.get('manifest'):
1518 if opts.get('changelog') or opts.get('manifest'):
1519 file_, rev = None, file_
1519 file_, rev = None, file_
1520 elif rev is None:
1520 elif rev is None:
1521 raise error.CommandError('debugdata', _('invalid arguments'))
1521 raise error.CommandError('debugdata', _('invalid arguments'))
1522 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1522 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1523 try:
1523 try:
1524 ui.write(r.revision(r.lookup(rev)))
1524 ui.write(r.revision(r.lookup(rev)))
1525 except KeyError:
1525 except KeyError:
1526 raise util.Abort(_('invalid revision identifier %s') % rev)
1526 raise util.Abort(_('invalid revision identifier %s') % rev)
1527
1527
1528 @command('debugdate',
1528 @command('debugdate',
1529 [('e', 'extended', None, _('try extended date formats'))],
1529 [('e', 'extended', None, _('try extended date formats'))],
1530 _('[-e] DATE [RANGE]'))
1530 _('[-e] DATE [RANGE]'))
1531 def debugdate(ui, date, range=None, **opts):
1531 def debugdate(ui, date, range=None, **opts):
1532 """parse and display a date"""
1532 """parse and display a date"""
1533 if opts["extended"]:
1533 if opts["extended"]:
1534 d = util.parsedate(date, util.extendeddateformats)
1534 d = util.parsedate(date, util.extendeddateformats)
1535 else:
1535 else:
1536 d = util.parsedate(date)
1536 d = util.parsedate(date)
1537 ui.write("internal: %s %s\n" % d)
1537 ui.write("internal: %s %s\n" % d)
1538 ui.write("standard: %s\n" % util.datestr(d))
1538 ui.write("standard: %s\n" % util.datestr(d))
1539 if range:
1539 if range:
1540 m = util.matchdate(range)
1540 m = util.matchdate(range)
1541 ui.write("match: %s\n" % m(d[0]))
1541 ui.write("match: %s\n" % m(d[0]))
1542
1542
1543 @command('debugdiscovery',
1543 @command('debugdiscovery',
1544 [('', 'old', None, _('use old-style discovery')),
1544 [('', 'old', None, _('use old-style discovery')),
1545 ('', 'nonheads', None,
1545 ('', 'nonheads', None,
1546 _('use old-style discovery with non-heads included')),
1546 _('use old-style discovery with non-heads included')),
1547 ] + remoteopts,
1547 ] + remoteopts,
1548 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1548 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1549 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1549 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1550 """runs the changeset discovery protocol in isolation"""
1550 """runs the changeset discovery protocol in isolation"""
1551 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
1551 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
1552 remote = hg.peer(repo, opts, remoteurl)
1552 remote = hg.peer(repo, opts, remoteurl)
1553 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1553 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1554
1554
1555 # make sure tests are repeatable
1555 # make sure tests are repeatable
1556 random.seed(12323)
1556 random.seed(12323)
1557
1557
1558 def doit(localheads, remoteheads):
1558 def doit(localheads, remoteheads):
1559 if opts.get('old'):
1559 if opts.get('old'):
1560 if localheads:
1560 if localheads:
1561 raise util.Abort('cannot use localheads with old style discovery')
1561 raise util.Abort('cannot use localheads with old style discovery')
1562 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1562 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1563 force=True)
1563 force=True)
1564 common = set(common)
1564 common = set(common)
1565 if not opts.get('nonheads'):
1565 if not opts.get('nonheads'):
1566 ui.write("unpruned common: %s\n" % " ".join([short(n)
1566 ui.write("unpruned common: %s\n" % " ".join([short(n)
1567 for n in common]))
1567 for n in common]))
1568 dag = dagutil.revlogdag(repo.changelog)
1568 dag = dagutil.revlogdag(repo.changelog)
1569 all = dag.ancestorset(dag.internalizeall(common))
1569 all = dag.ancestorset(dag.internalizeall(common))
1570 common = dag.externalizeall(dag.headsetofconnecteds(all))
1570 common = dag.externalizeall(dag.headsetofconnecteds(all))
1571 else:
1571 else:
1572 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1572 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1573 common = set(common)
1573 common = set(common)
1574 rheads = set(hds)
1574 rheads = set(hds)
1575 lheads = set(repo.heads())
1575 lheads = set(repo.heads())
1576 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1576 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1577 if lheads <= common:
1577 if lheads <= common:
1578 ui.write("local is subset\n")
1578 ui.write("local is subset\n")
1579 elif rheads <= common:
1579 elif rheads <= common:
1580 ui.write("remote is subset\n")
1580 ui.write("remote is subset\n")
1581
1581
1582 serverlogs = opts.get('serverlog')
1582 serverlogs = opts.get('serverlog')
1583 if serverlogs:
1583 if serverlogs:
1584 for filename in serverlogs:
1584 for filename in serverlogs:
1585 logfile = open(filename, 'r')
1585 logfile = open(filename, 'r')
1586 try:
1586 try:
1587 line = logfile.readline()
1587 line = logfile.readline()
1588 while line:
1588 while line:
1589 parts = line.strip().split(';')
1589 parts = line.strip().split(';')
1590 op = parts[1]
1590 op = parts[1]
1591 if op == 'cg':
1591 if op == 'cg':
1592 pass
1592 pass
1593 elif op == 'cgss':
1593 elif op == 'cgss':
1594 doit(parts[2].split(' '), parts[3].split(' '))
1594 doit(parts[2].split(' '), parts[3].split(' '))
1595 elif op == 'unb':
1595 elif op == 'unb':
1596 doit(parts[3].split(' '), parts[2].split(' '))
1596 doit(parts[3].split(' '), parts[2].split(' '))
1597 line = logfile.readline()
1597 line = logfile.readline()
1598 finally:
1598 finally:
1599 logfile.close()
1599 logfile.close()
1600
1600
1601 else:
1601 else:
1602 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1602 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1603 opts.get('remote_head'))
1603 opts.get('remote_head'))
1604 localrevs = opts.get('local_head')
1604 localrevs = opts.get('local_head')
1605 doit(localrevs, remoterevs)
1605 doit(localrevs, remoterevs)
1606
1606
1607 @command('debugfileset', [], ('REVSPEC'))
1607 @command('debugfileset', [], ('REVSPEC'))
1608 def debugfileset(ui, repo, expr):
1608 def debugfileset(ui, repo, expr):
1609 '''parse and apply a fileset specification'''
1609 '''parse and apply a fileset specification'''
1610 if ui.verbose:
1610 if ui.verbose:
1611 tree = fileset.parse(expr)[0]
1611 tree = fileset.parse(expr)[0]
1612 ui.note(tree, "\n")
1612 ui.note(tree, "\n")
1613 matcher = lambda x: scmutil.match(repo, x, default='glob')
1613 matcher = lambda x: scmutil.match(repo[None], x, default='glob')
1614
1614
1615 for f in fileset.getfileset(repo[None], matcher, expr):
1615 for f in fileset.getfileset(repo[None], matcher, expr):
1616 ui.write("%s\n" % f)
1616 ui.write("%s\n" % f)
1617
1617
1618 @command('debugfsinfo', [], _('[PATH]'))
1618 @command('debugfsinfo', [], _('[PATH]'))
1619 def debugfsinfo(ui, path = "."):
1619 def debugfsinfo(ui, path = "."):
1620 """show information detected about current filesystem"""
1620 """show information detected about current filesystem"""
1621 util.writefile('.debugfsinfo', '')
1621 util.writefile('.debugfsinfo', '')
1622 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1622 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1623 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1623 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1624 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1624 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1625 and 'yes' or 'no'))
1625 and 'yes' or 'no'))
1626 os.unlink('.debugfsinfo')
1626 os.unlink('.debugfsinfo')
1627
1627
1628 @command('debuggetbundle',
1628 @command('debuggetbundle',
1629 [('H', 'head', [], _('id of head node'), _('ID')),
1629 [('H', 'head', [], _('id of head node'), _('ID')),
1630 ('C', 'common', [], _('id of common node'), _('ID')),
1630 ('C', 'common', [], _('id of common node'), _('ID')),
1631 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1631 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1632 _('REPO FILE [-H|-C ID]...'))
1632 _('REPO FILE [-H|-C ID]...'))
1633 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1633 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1634 """retrieves a bundle from a repo
1634 """retrieves a bundle from a repo
1635
1635
1636 Every ID must be a full-length hex node id string. Saves the bundle to the
1636 Every ID must be a full-length hex node id string. Saves the bundle to the
1637 given file.
1637 given file.
1638 """
1638 """
1639 repo = hg.peer(ui, opts, repopath)
1639 repo = hg.peer(ui, opts, repopath)
1640 if not repo.capable('getbundle'):
1640 if not repo.capable('getbundle'):
1641 raise util.Abort("getbundle() not supported by target repository")
1641 raise util.Abort("getbundle() not supported by target repository")
1642 args = {}
1642 args = {}
1643 if common:
1643 if common:
1644 args['common'] = [bin(s) for s in common]
1644 args['common'] = [bin(s) for s in common]
1645 if head:
1645 if head:
1646 args['heads'] = [bin(s) for s in head]
1646 args['heads'] = [bin(s) for s in head]
1647 bundle = repo.getbundle('debug', **args)
1647 bundle = repo.getbundle('debug', **args)
1648
1648
1649 bundletype = opts.get('type', 'bzip2').lower()
1649 bundletype = opts.get('type', 'bzip2').lower()
1650 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1650 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1651 bundletype = btypes.get(bundletype)
1651 bundletype = btypes.get(bundletype)
1652 if bundletype not in changegroup.bundletypes:
1652 if bundletype not in changegroup.bundletypes:
1653 raise util.Abort(_('unknown bundle type specified with --type'))
1653 raise util.Abort(_('unknown bundle type specified with --type'))
1654 changegroup.writebundle(bundle, bundlepath, bundletype)
1654 changegroup.writebundle(bundle, bundlepath, bundletype)
1655
1655
1656 @command('debugignore', [], '')
1656 @command('debugignore', [], '')
1657 def debugignore(ui, repo, *values, **opts):
1657 def debugignore(ui, repo, *values, **opts):
1658 """display the combined ignore pattern"""
1658 """display the combined ignore pattern"""
1659 ignore = repo.dirstate._ignore
1659 ignore = repo.dirstate._ignore
1660 if hasattr(ignore, 'includepat'):
1660 if hasattr(ignore, 'includepat'):
1661 ui.write("%s\n" % ignore.includepat)
1661 ui.write("%s\n" % ignore.includepat)
1662 else:
1662 else:
1663 raise util.Abort(_("no ignore patterns found"))
1663 raise util.Abort(_("no ignore patterns found"))
1664
1664
1665 @command('debugindex',
1665 @command('debugindex',
1666 [('c', 'changelog', False, _('open changelog')),
1666 [('c', 'changelog', False, _('open changelog')),
1667 ('m', 'manifest', False, _('open manifest')),
1667 ('m', 'manifest', False, _('open manifest')),
1668 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1668 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1669 _('[-f FORMAT] -c|-m|FILE'))
1669 _('[-f FORMAT] -c|-m|FILE'))
1670 def debugindex(ui, repo, file_ = None, **opts):
1670 def debugindex(ui, repo, file_ = None, **opts):
1671 """dump the contents of an index file"""
1671 """dump the contents of an index file"""
1672 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1672 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1673 format = opts.get('format', 0)
1673 format = opts.get('format', 0)
1674 if format not in (0, 1):
1674 if format not in (0, 1):
1675 raise util.Abort(_("unknown format %d") % format)
1675 raise util.Abort(_("unknown format %d") % format)
1676
1676
1677 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1677 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1678 if generaldelta:
1678 if generaldelta:
1679 basehdr = ' delta'
1679 basehdr = ' delta'
1680 else:
1680 else:
1681 basehdr = ' base'
1681 basehdr = ' base'
1682
1682
1683 if format == 0:
1683 if format == 0:
1684 ui.write(" rev offset length " + basehdr + " linkrev"
1684 ui.write(" rev offset length " + basehdr + " linkrev"
1685 " nodeid p1 p2\n")
1685 " nodeid p1 p2\n")
1686 elif format == 1:
1686 elif format == 1:
1687 ui.write(" rev flag offset length"
1687 ui.write(" rev flag offset length"
1688 " size " + basehdr + " link p1 p2 nodeid\n")
1688 " size " + basehdr + " link p1 p2 nodeid\n")
1689
1689
1690 for i in r:
1690 for i in r:
1691 node = r.node(i)
1691 node = r.node(i)
1692 if generaldelta:
1692 if generaldelta:
1693 base = r.deltaparent(i)
1693 base = r.deltaparent(i)
1694 else:
1694 else:
1695 base = r.chainbase(i)
1695 base = r.chainbase(i)
1696 if format == 0:
1696 if format == 0:
1697 try:
1697 try:
1698 pp = r.parents(node)
1698 pp = r.parents(node)
1699 except:
1699 except:
1700 pp = [nullid, nullid]
1700 pp = [nullid, nullid]
1701 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1701 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1702 i, r.start(i), r.length(i), base, r.linkrev(i),
1702 i, r.start(i), r.length(i), base, r.linkrev(i),
1703 short(node), short(pp[0]), short(pp[1])))
1703 short(node), short(pp[0]), short(pp[1])))
1704 elif format == 1:
1704 elif format == 1:
1705 pr = r.parentrevs(i)
1705 pr = r.parentrevs(i)
1706 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1706 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1707 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1707 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1708 base, r.linkrev(i), pr[0], pr[1], short(node)))
1708 base, r.linkrev(i), pr[0], pr[1], short(node)))
1709
1709
1710 @command('debugindexdot', [], _('FILE'))
1710 @command('debugindexdot', [], _('FILE'))
1711 def debugindexdot(ui, repo, file_):
1711 def debugindexdot(ui, repo, file_):
1712 """dump an index DAG as a graphviz dot file"""
1712 """dump an index DAG as a graphviz dot file"""
1713 r = None
1713 r = None
1714 if repo:
1714 if repo:
1715 filelog = repo.file(file_)
1715 filelog = repo.file(file_)
1716 if len(filelog):
1716 if len(filelog):
1717 r = filelog
1717 r = filelog
1718 if not r:
1718 if not r:
1719 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1719 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1720 ui.write("digraph G {\n")
1720 ui.write("digraph G {\n")
1721 for i in r:
1721 for i in r:
1722 node = r.node(i)
1722 node = r.node(i)
1723 pp = r.parents(node)
1723 pp = r.parents(node)
1724 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1724 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1725 if pp[1] != nullid:
1725 if pp[1] != nullid:
1726 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1726 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1727 ui.write("}\n")
1727 ui.write("}\n")
1728
1728
1729 @command('debuginstall', [], '')
1729 @command('debuginstall', [], '')
1730 def debuginstall(ui):
1730 def debuginstall(ui):
1731 '''test Mercurial installation
1731 '''test Mercurial installation
1732
1732
1733 Returns 0 on success.
1733 Returns 0 on success.
1734 '''
1734 '''
1735
1735
1736 def writetemp(contents):
1736 def writetemp(contents):
1737 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1737 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1738 f = os.fdopen(fd, "wb")
1738 f = os.fdopen(fd, "wb")
1739 f.write(contents)
1739 f.write(contents)
1740 f.close()
1740 f.close()
1741 return name
1741 return name
1742
1742
1743 problems = 0
1743 problems = 0
1744
1744
1745 # encoding
1745 # encoding
1746 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1746 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1747 try:
1747 try:
1748 encoding.fromlocal("test")
1748 encoding.fromlocal("test")
1749 except util.Abort, inst:
1749 except util.Abort, inst:
1750 ui.write(" %s\n" % inst)
1750 ui.write(" %s\n" % inst)
1751 ui.write(_(" (check that your locale is properly set)\n"))
1751 ui.write(_(" (check that your locale is properly set)\n"))
1752 problems += 1
1752 problems += 1
1753
1753
1754 # compiled modules
1754 # compiled modules
1755 ui.status(_("Checking installed modules (%s)...\n")
1755 ui.status(_("Checking installed modules (%s)...\n")
1756 % os.path.dirname(__file__))
1756 % os.path.dirname(__file__))
1757 try:
1757 try:
1758 import bdiff, mpatch, base85, osutil
1758 import bdiff, mpatch, base85, osutil
1759 except Exception, inst:
1759 except Exception, inst:
1760 ui.write(" %s\n" % inst)
1760 ui.write(" %s\n" % inst)
1761 ui.write(_(" One or more extensions could not be found"))
1761 ui.write(_(" One or more extensions could not be found"))
1762 ui.write(_(" (check that you compiled the extensions)\n"))
1762 ui.write(_(" (check that you compiled the extensions)\n"))
1763 problems += 1
1763 problems += 1
1764
1764
1765 # templates
1765 # templates
1766 ui.status(_("Checking templates...\n"))
1766 ui.status(_("Checking templates...\n"))
1767 try:
1767 try:
1768 import templater
1768 import templater
1769 templater.templater(templater.templatepath("map-cmdline.default"))
1769 templater.templater(templater.templatepath("map-cmdline.default"))
1770 except Exception, inst:
1770 except Exception, inst:
1771 ui.write(" %s\n" % inst)
1771 ui.write(" %s\n" % inst)
1772 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1772 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1773 problems += 1
1773 problems += 1
1774
1774
1775 # editor
1775 # editor
1776 ui.status(_("Checking commit editor...\n"))
1776 ui.status(_("Checking commit editor...\n"))
1777 editor = ui.geteditor()
1777 editor = ui.geteditor()
1778 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
1778 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
1779 if not cmdpath:
1779 if not cmdpath:
1780 if editor == 'vi':
1780 if editor == 'vi':
1781 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1781 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1782 ui.write(_(" (specify a commit editor in your configuration"
1782 ui.write(_(" (specify a commit editor in your configuration"
1783 " file)\n"))
1783 " file)\n"))
1784 else:
1784 else:
1785 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1785 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1786 ui.write(_(" (specify a commit editor in your configuration"
1786 ui.write(_(" (specify a commit editor in your configuration"
1787 " file)\n"))
1787 " file)\n"))
1788 problems += 1
1788 problems += 1
1789
1789
1790 # check username
1790 # check username
1791 ui.status(_("Checking username...\n"))
1791 ui.status(_("Checking username...\n"))
1792 try:
1792 try:
1793 ui.username()
1793 ui.username()
1794 except util.Abort, e:
1794 except util.Abort, e:
1795 ui.write(" %s\n" % e)
1795 ui.write(" %s\n" % e)
1796 ui.write(_(" (specify a username in your configuration file)\n"))
1796 ui.write(_(" (specify a username in your configuration file)\n"))
1797 problems += 1
1797 problems += 1
1798
1798
1799 if not problems:
1799 if not problems:
1800 ui.status(_("No problems detected\n"))
1800 ui.status(_("No problems detected\n"))
1801 else:
1801 else:
1802 ui.write(_("%s problems detected,"
1802 ui.write(_("%s problems detected,"
1803 " please check your install!\n") % problems)
1803 " please check your install!\n") % problems)
1804
1804
1805 return problems
1805 return problems
1806
1806
1807 @command('debugknown', [], _('REPO ID...'))
1807 @command('debugknown', [], _('REPO ID...'))
1808 def debugknown(ui, repopath, *ids, **opts):
1808 def debugknown(ui, repopath, *ids, **opts):
1809 """test whether node ids are known to a repo
1809 """test whether node ids are known to a repo
1810
1810
1811 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
1811 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
1812 indicating unknown/known.
1812 indicating unknown/known.
1813 """
1813 """
1814 repo = hg.peer(ui, opts, repopath)
1814 repo = hg.peer(ui, opts, repopath)
1815 if not repo.capable('known'):
1815 if not repo.capable('known'):
1816 raise util.Abort("known() not supported by target repository")
1816 raise util.Abort("known() not supported by target repository")
1817 flags = repo.known([bin(s) for s in ids])
1817 flags = repo.known([bin(s) for s in ids])
1818 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1818 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1819
1819
1820 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
1820 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
1821 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1821 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1822 '''access the pushkey key/value protocol
1822 '''access the pushkey key/value protocol
1823
1823
1824 With two args, list the keys in the given namespace.
1824 With two args, list the keys in the given namespace.
1825
1825
1826 With five args, set a key to new if it currently is set to old.
1826 With five args, set a key to new if it currently is set to old.
1827 Reports success or failure.
1827 Reports success or failure.
1828 '''
1828 '''
1829
1829
1830 target = hg.peer(ui, {}, repopath)
1830 target = hg.peer(ui, {}, repopath)
1831 if keyinfo:
1831 if keyinfo:
1832 key, old, new = keyinfo
1832 key, old, new = keyinfo
1833 r = target.pushkey(namespace, key, old, new)
1833 r = target.pushkey(namespace, key, old, new)
1834 ui.status(str(r) + '\n')
1834 ui.status(str(r) + '\n')
1835 return not r
1835 return not r
1836 else:
1836 else:
1837 for k, v in target.listkeys(namespace).iteritems():
1837 for k, v in target.listkeys(namespace).iteritems():
1838 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1838 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1839 v.encode('string-escape')))
1839 v.encode('string-escape')))
1840
1840
1841 @command('debugrebuildstate',
1841 @command('debugrebuildstate',
1842 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
1842 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
1843 _('[-r REV] [REV]'))
1843 _('[-r REV] [REV]'))
1844 def debugrebuildstate(ui, repo, rev="tip"):
1844 def debugrebuildstate(ui, repo, rev="tip"):
1845 """rebuild the dirstate as it would look like for the given revision"""
1845 """rebuild the dirstate as it would look like for the given revision"""
1846 ctx = scmutil.revsingle(repo, rev)
1846 ctx = scmutil.revsingle(repo, rev)
1847 wlock = repo.wlock()
1847 wlock = repo.wlock()
1848 try:
1848 try:
1849 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1849 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1850 finally:
1850 finally:
1851 wlock.release()
1851 wlock.release()
1852
1852
1853 @command('debugrename',
1853 @command('debugrename',
1854 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1854 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1855 _('[-r REV] FILE'))
1855 _('[-r REV] FILE'))
1856 def debugrename(ui, repo, file1, *pats, **opts):
1856 def debugrename(ui, repo, file1, *pats, **opts):
1857 """dump rename information"""
1857 """dump rename information"""
1858
1858
1859 ctx = scmutil.revsingle(repo, opts.get('rev'))
1859 ctx = scmutil.revsingle(repo, opts.get('rev'))
1860 m = scmutil.match(repo, (file1,) + pats, opts)
1860 m = scmutil.match(ctx, (file1,) + pats, opts)
1861 for abs in ctx.walk(m):
1861 for abs in ctx.walk(m):
1862 fctx = ctx[abs]
1862 fctx = ctx[abs]
1863 o = fctx.filelog().renamed(fctx.filenode())
1863 o = fctx.filelog().renamed(fctx.filenode())
1864 rel = m.rel(abs)
1864 rel = m.rel(abs)
1865 if o:
1865 if o:
1866 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1866 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1867 else:
1867 else:
1868 ui.write(_("%s not renamed\n") % rel)
1868 ui.write(_("%s not renamed\n") % rel)
1869
1869
1870 @command('debugrevlog',
1870 @command('debugrevlog',
1871 [('c', 'changelog', False, _('open changelog')),
1871 [('c', 'changelog', False, _('open changelog')),
1872 ('m', 'manifest', False, _('open manifest')),
1872 ('m', 'manifest', False, _('open manifest')),
1873 ('d', 'dump', False, _('dump index data'))],
1873 ('d', 'dump', False, _('dump index data'))],
1874 _('-c|-m|FILE'))
1874 _('-c|-m|FILE'))
1875 def debugrevlog(ui, repo, file_ = None, **opts):
1875 def debugrevlog(ui, repo, file_ = None, **opts):
1876 """show data and statistics about a revlog"""
1876 """show data and statistics about a revlog"""
1877 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1877 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1878
1878
1879 if opts.get("dump"):
1879 if opts.get("dump"):
1880 numrevs = len(r)
1880 numrevs = len(r)
1881 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
1881 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
1882 " rawsize totalsize compression heads\n")
1882 " rawsize totalsize compression heads\n")
1883 ts = 0
1883 ts = 0
1884 heads = set()
1884 heads = set()
1885 for rev in xrange(numrevs):
1885 for rev in xrange(numrevs):
1886 dbase = r.deltaparent(rev)
1886 dbase = r.deltaparent(rev)
1887 if dbase == -1:
1887 if dbase == -1:
1888 dbase = rev
1888 dbase = rev
1889 cbase = r.chainbase(rev)
1889 cbase = r.chainbase(rev)
1890 p1, p2 = r.parentrevs(rev)
1890 p1, p2 = r.parentrevs(rev)
1891 rs = r.rawsize(rev)
1891 rs = r.rawsize(rev)
1892 ts = ts + rs
1892 ts = ts + rs
1893 heads -= set(r.parentrevs(rev))
1893 heads -= set(r.parentrevs(rev))
1894 heads.add(rev)
1894 heads.add(rev)
1895 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
1895 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
1896 (rev, p1, p2, r.start(rev), r.end(rev),
1896 (rev, p1, p2, r.start(rev), r.end(rev),
1897 r.start(dbase), r.start(cbase),
1897 r.start(dbase), r.start(cbase),
1898 r.start(p1), r.start(p2),
1898 r.start(p1), r.start(p2),
1899 rs, ts, ts / r.end(rev), len(heads)))
1899 rs, ts, ts / r.end(rev), len(heads)))
1900 return 0
1900 return 0
1901
1901
1902 v = r.version
1902 v = r.version
1903 format = v & 0xFFFF
1903 format = v & 0xFFFF
1904 flags = []
1904 flags = []
1905 gdelta = False
1905 gdelta = False
1906 if v & revlog.REVLOGNGINLINEDATA:
1906 if v & revlog.REVLOGNGINLINEDATA:
1907 flags.append('inline')
1907 flags.append('inline')
1908 if v & revlog.REVLOGGENERALDELTA:
1908 if v & revlog.REVLOGGENERALDELTA:
1909 gdelta = True
1909 gdelta = True
1910 flags.append('generaldelta')
1910 flags.append('generaldelta')
1911 if not flags:
1911 if not flags:
1912 flags = ['(none)']
1912 flags = ['(none)']
1913
1913
1914 nummerges = 0
1914 nummerges = 0
1915 numfull = 0
1915 numfull = 0
1916 numprev = 0
1916 numprev = 0
1917 nump1 = 0
1917 nump1 = 0
1918 nump2 = 0
1918 nump2 = 0
1919 numother = 0
1919 numother = 0
1920 nump1prev = 0
1920 nump1prev = 0
1921 nump2prev = 0
1921 nump2prev = 0
1922 chainlengths = []
1922 chainlengths = []
1923
1923
1924 datasize = [None, 0, 0L]
1924 datasize = [None, 0, 0L]
1925 fullsize = [None, 0, 0L]
1925 fullsize = [None, 0, 0L]
1926 deltasize = [None, 0, 0L]
1926 deltasize = [None, 0, 0L]
1927
1927
1928 def addsize(size, l):
1928 def addsize(size, l):
1929 if l[0] is None or size < l[0]:
1929 if l[0] is None or size < l[0]:
1930 l[0] = size
1930 l[0] = size
1931 if size > l[1]:
1931 if size > l[1]:
1932 l[1] = size
1932 l[1] = size
1933 l[2] += size
1933 l[2] += size
1934
1934
1935 numrevs = len(r)
1935 numrevs = len(r)
1936 for rev in xrange(numrevs):
1936 for rev in xrange(numrevs):
1937 p1, p2 = r.parentrevs(rev)
1937 p1, p2 = r.parentrevs(rev)
1938 delta = r.deltaparent(rev)
1938 delta = r.deltaparent(rev)
1939 if format > 0:
1939 if format > 0:
1940 addsize(r.rawsize(rev), datasize)
1940 addsize(r.rawsize(rev), datasize)
1941 if p2 != nullrev:
1941 if p2 != nullrev:
1942 nummerges += 1
1942 nummerges += 1
1943 size = r.length(rev)
1943 size = r.length(rev)
1944 if delta == nullrev:
1944 if delta == nullrev:
1945 chainlengths.append(0)
1945 chainlengths.append(0)
1946 numfull += 1
1946 numfull += 1
1947 addsize(size, fullsize)
1947 addsize(size, fullsize)
1948 else:
1948 else:
1949 chainlengths.append(chainlengths[delta] + 1)
1949 chainlengths.append(chainlengths[delta] + 1)
1950 addsize(size, deltasize)
1950 addsize(size, deltasize)
1951 if delta == rev - 1:
1951 if delta == rev - 1:
1952 numprev += 1
1952 numprev += 1
1953 if delta == p1:
1953 if delta == p1:
1954 nump1prev += 1
1954 nump1prev += 1
1955 elif delta == p2:
1955 elif delta == p2:
1956 nump2prev += 1
1956 nump2prev += 1
1957 elif delta == p1:
1957 elif delta == p1:
1958 nump1 += 1
1958 nump1 += 1
1959 elif delta == p2:
1959 elif delta == p2:
1960 nump2 += 1
1960 nump2 += 1
1961 elif delta != nullrev:
1961 elif delta != nullrev:
1962 numother += 1
1962 numother += 1
1963
1963
1964 numdeltas = numrevs - numfull
1964 numdeltas = numrevs - numfull
1965 numoprev = numprev - nump1prev - nump2prev
1965 numoprev = numprev - nump1prev - nump2prev
1966 totalrawsize = datasize[2]
1966 totalrawsize = datasize[2]
1967 datasize[2] /= numrevs
1967 datasize[2] /= numrevs
1968 fulltotal = fullsize[2]
1968 fulltotal = fullsize[2]
1969 fullsize[2] /= numfull
1969 fullsize[2] /= numfull
1970 deltatotal = deltasize[2]
1970 deltatotal = deltasize[2]
1971 deltasize[2] /= numrevs - numfull
1971 deltasize[2] /= numrevs - numfull
1972 totalsize = fulltotal + deltatotal
1972 totalsize = fulltotal + deltatotal
1973 avgchainlen = sum(chainlengths) / numrevs
1973 avgchainlen = sum(chainlengths) / numrevs
1974 compratio = totalrawsize / totalsize
1974 compratio = totalrawsize / totalsize
1975
1975
1976 basedfmtstr = '%%%dd\n'
1976 basedfmtstr = '%%%dd\n'
1977 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
1977 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
1978
1978
1979 def dfmtstr(max):
1979 def dfmtstr(max):
1980 return basedfmtstr % len(str(max))
1980 return basedfmtstr % len(str(max))
1981 def pcfmtstr(max, padding=0):
1981 def pcfmtstr(max, padding=0):
1982 return basepcfmtstr % (len(str(max)), ' ' * padding)
1982 return basepcfmtstr % (len(str(max)), ' ' * padding)
1983
1983
1984 def pcfmt(value, total):
1984 def pcfmt(value, total):
1985 return (value, 100 * float(value) / total)
1985 return (value, 100 * float(value) / total)
1986
1986
1987 ui.write('format : %d\n' % format)
1987 ui.write('format : %d\n' % format)
1988 ui.write('flags : %s\n' % ', '.join(flags))
1988 ui.write('flags : %s\n' % ', '.join(flags))
1989
1989
1990 ui.write('\n')
1990 ui.write('\n')
1991 fmt = pcfmtstr(totalsize)
1991 fmt = pcfmtstr(totalsize)
1992 fmt2 = dfmtstr(totalsize)
1992 fmt2 = dfmtstr(totalsize)
1993 ui.write('revisions : ' + fmt2 % numrevs)
1993 ui.write('revisions : ' + fmt2 % numrevs)
1994 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
1994 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
1995 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
1995 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
1996 ui.write('revisions : ' + fmt2 % numrevs)
1996 ui.write('revisions : ' + fmt2 % numrevs)
1997 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
1997 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
1998 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
1998 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
1999 ui.write('revision size : ' + fmt2 % totalsize)
1999 ui.write('revision size : ' + fmt2 % totalsize)
2000 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2000 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2001 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2001 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2002
2002
2003 ui.write('\n')
2003 ui.write('\n')
2004 fmt = dfmtstr(max(avgchainlen, compratio))
2004 fmt = dfmtstr(max(avgchainlen, compratio))
2005 ui.write('avg chain length : ' + fmt % avgchainlen)
2005 ui.write('avg chain length : ' + fmt % avgchainlen)
2006 ui.write('compression ratio : ' + fmt % compratio)
2006 ui.write('compression ratio : ' + fmt % compratio)
2007
2007
2008 if format > 0:
2008 if format > 0:
2009 ui.write('\n')
2009 ui.write('\n')
2010 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2010 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2011 % tuple(datasize))
2011 % tuple(datasize))
2012 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2012 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2013 % tuple(fullsize))
2013 % tuple(fullsize))
2014 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2014 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2015 % tuple(deltasize))
2015 % tuple(deltasize))
2016
2016
2017 if numdeltas > 0:
2017 if numdeltas > 0:
2018 ui.write('\n')
2018 ui.write('\n')
2019 fmt = pcfmtstr(numdeltas)
2019 fmt = pcfmtstr(numdeltas)
2020 fmt2 = pcfmtstr(numdeltas, 4)
2020 fmt2 = pcfmtstr(numdeltas, 4)
2021 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2021 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2022 if numprev > 0:
2022 if numprev > 0:
2023 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev))
2023 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev))
2024 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev))
2024 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev))
2025 ui.write(' other : ' + fmt2 % pcfmt(numoprev, numprev))
2025 ui.write(' other : ' + fmt2 % pcfmt(numoprev, numprev))
2026 if gdelta:
2026 if gdelta:
2027 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2027 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2028 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2028 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2029 ui.write('deltas against other : ' + fmt % pcfmt(numother, numdeltas))
2029 ui.write('deltas against other : ' + fmt % pcfmt(numother, numdeltas))
2030
2030
2031 @command('debugrevspec', [], ('REVSPEC'))
2031 @command('debugrevspec', [], ('REVSPEC'))
2032 def debugrevspec(ui, repo, expr):
2032 def debugrevspec(ui, repo, expr):
2033 '''parse and apply a revision specification'''
2033 '''parse and apply a revision specification'''
2034 if ui.verbose:
2034 if ui.verbose:
2035 tree = revset.parse(expr)[0]
2035 tree = revset.parse(expr)[0]
2036 ui.note(tree, "\n")
2036 ui.note(tree, "\n")
2037 newtree = revset.findaliases(ui, tree)
2037 newtree = revset.findaliases(ui, tree)
2038 if newtree != tree:
2038 if newtree != tree:
2039 ui.note(newtree, "\n")
2039 ui.note(newtree, "\n")
2040 func = revset.match(ui, expr)
2040 func = revset.match(ui, expr)
2041 for c in func(repo, range(len(repo))):
2041 for c in func(repo, range(len(repo))):
2042 ui.write("%s\n" % c)
2042 ui.write("%s\n" % c)
2043
2043
2044 @command('debugsetparents', [], _('REV1 [REV2]'))
2044 @command('debugsetparents', [], _('REV1 [REV2]'))
2045 def debugsetparents(ui, repo, rev1, rev2=None):
2045 def debugsetparents(ui, repo, rev1, rev2=None):
2046 """manually set the parents of the current working directory
2046 """manually set the parents of the current working directory
2047
2047
2048 This is useful for writing repository conversion tools, but should
2048 This is useful for writing repository conversion tools, but should
2049 be used with care.
2049 be used with care.
2050
2050
2051 Returns 0 on success.
2051 Returns 0 on success.
2052 """
2052 """
2053
2053
2054 r1 = scmutil.revsingle(repo, rev1).node()
2054 r1 = scmutil.revsingle(repo, rev1).node()
2055 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2055 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2056
2056
2057 wlock = repo.wlock()
2057 wlock = repo.wlock()
2058 try:
2058 try:
2059 repo.dirstate.setparents(r1, r2)
2059 repo.dirstate.setparents(r1, r2)
2060 finally:
2060 finally:
2061 wlock.release()
2061 wlock.release()
2062
2062
2063 @command('debugstate',
2063 @command('debugstate',
2064 [('', 'nodates', None, _('do not display the saved mtime')),
2064 [('', 'nodates', None, _('do not display the saved mtime')),
2065 ('', 'datesort', None, _('sort by saved mtime'))],
2065 ('', 'datesort', None, _('sort by saved mtime'))],
2066 _('[OPTION]...'))
2066 _('[OPTION]...'))
2067 def debugstate(ui, repo, nodates=None, datesort=None):
2067 def debugstate(ui, repo, nodates=None, datesort=None):
2068 """show the contents of the current dirstate"""
2068 """show the contents of the current dirstate"""
2069 timestr = ""
2069 timestr = ""
2070 showdate = not nodates
2070 showdate = not nodates
2071 if datesort:
2071 if datesort:
2072 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2072 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2073 else:
2073 else:
2074 keyfunc = None # sort by filename
2074 keyfunc = None # sort by filename
2075 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2075 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2076 if showdate:
2076 if showdate:
2077 if ent[3] == -1:
2077 if ent[3] == -1:
2078 # Pad or slice to locale representation
2078 # Pad or slice to locale representation
2079 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2079 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2080 time.localtime(0)))
2080 time.localtime(0)))
2081 timestr = 'unset'
2081 timestr = 'unset'
2082 timestr = (timestr[:locale_len] +
2082 timestr = (timestr[:locale_len] +
2083 ' ' * (locale_len - len(timestr)))
2083 ' ' * (locale_len - len(timestr)))
2084 else:
2084 else:
2085 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2085 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2086 time.localtime(ent[3]))
2086 time.localtime(ent[3]))
2087 if ent[1] & 020000:
2087 if ent[1] & 020000:
2088 mode = 'lnk'
2088 mode = 'lnk'
2089 else:
2089 else:
2090 mode = '%3o' % (ent[1] & 0777)
2090 mode = '%3o' % (ent[1] & 0777)
2091 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2091 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2092 for f in repo.dirstate.copies():
2092 for f in repo.dirstate.copies():
2093 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2093 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2094
2094
2095 @command('debugsub',
2095 @command('debugsub',
2096 [('r', 'rev', '',
2096 [('r', 'rev', '',
2097 _('revision to check'), _('REV'))],
2097 _('revision to check'), _('REV'))],
2098 _('[-r REV] [REV]'))
2098 _('[-r REV] [REV]'))
2099 def debugsub(ui, repo, rev=None):
2099 def debugsub(ui, repo, rev=None):
2100 ctx = scmutil.revsingle(repo, rev, None)
2100 ctx = scmutil.revsingle(repo, rev, None)
2101 for k, v in sorted(ctx.substate.items()):
2101 for k, v in sorted(ctx.substate.items()):
2102 ui.write('path %s\n' % k)
2102 ui.write('path %s\n' % k)
2103 ui.write(' source %s\n' % v[0])
2103 ui.write(' source %s\n' % v[0])
2104 ui.write(' revision %s\n' % v[1])
2104 ui.write(' revision %s\n' % v[1])
2105
2105
2106 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2106 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2107 def debugwalk(ui, repo, *pats, **opts):
2107 def debugwalk(ui, repo, *pats, **opts):
2108 """show how files match on given patterns"""
2108 """show how files match on given patterns"""
2109 m = scmutil.match(repo, pats, opts)
2109 m = scmutil.match(repo[None], pats, opts)
2110 items = list(repo.walk(m))
2110 items = list(repo.walk(m))
2111 if not items:
2111 if not items:
2112 return
2112 return
2113 fmt = 'f %%-%ds %%-%ds %%s' % (
2113 fmt = 'f %%-%ds %%-%ds %%s' % (
2114 max([len(abs) for abs in items]),
2114 max([len(abs) for abs in items]),
2115 max([len(m.rel(abs)) for abs in items]))
2115 max([len(m.rel(abs)) for abs in items]))
2116 for abs in items:
2116 for abs in items:
2117 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
2117 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
2118 ui.write("%s\n" % line.rstrip())
2118 ui.write("%s\n" % line.rstrip())
2119
2119
2120 @command('debugwireargs',
2120 @command('debugwireargs',
2121 [('', 'three', '', 'three'),
2121 [('', 'three', '', 'three'),
2122 ('', 'four', '', 'four'),
2122 ('', 'four', '', 'four'),
2123 ('', 'five', '', 'five'),
2123 ('', 'five', '', 'five'),
2124 ] + remoteopts,
2124 ] + remoteopts,
2125 _('REPO [OPTIONS]... [ONE [TWO]]'))
2125 _('REPO [OPTIONS]... [ONE [TWO]]'))
2126 def debugwireargs(ui, repopath, *vals, **opts):
2126 def debugwireargs(ui, repopath, *vals, **opts):
2127 repo = hg.peer(ui, opts, repopath)
2127 repo = hg.peer(ui, opts, repopath)
2128 for opt in remoteopts:
2128 for opt in remoteopts:
2129 del opts[opt[1]]
2129 del opts[opt[1]]
2130 args = {}
2130 args = {}
2131 for k, v in opts.iteritems():
2131 for k, v in opts.iteritems():
2132 if v:
2132 if v:
2133 args[k] = v
2133 args[k] = v
2134 # run twice to check that we don't mess up the stream for the next command
2134 # run twice to check that we don't mess up the stream for the next command
2135 res1 = repo.debugwireargs(*vals, **args)
2135 res1 = repo.debugwireargs(*vals, **args)
2136 res2 = repo.debugwireargs(*vals, **args)
2136 res2 = repo.debugwireargs(*vals, **args)
2137 ui.write("%s\n" % res1)
2137 ui.write("%s\n" % res1)
2138 if res1 != res2:
2138 if res1 != res2:
2139 ui.warn("%s\n" % res2)
2139 ui.warn("%s\n" % res2)
2140
2140
2141 @command('^diff',
2141 @command('^diff',
2142 [('r', 'rev', [], _('revision'), _('REV')),
2142 [('r', 'rev', [], _('revision'), _('REV')),
2143 ('c', 'change', '', _('change made by revision'), _('REV'))
2143 ('c', 'change', '', _('change made by revision'), _('REV'))
2144 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2144 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2145 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2145 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2146 def diff(ui, repo, *pats, **opts):
2146 def diff(ui, repo, *pats, **opts):
2147 """diff repository (or selected files)
2147 """diff repository (or selected files)
2148
2148
2149 Show differences between revisions for the specified files.
2149 Show differences between revisions for the specified files.
2150
2150
2151 Differences between files are shown using the unified diff format.
2151 Differences between files are shown using the unified diff format.
2152
2152
2153 .. note::
2153 .. note::
2154 diff may generate unexpected results for merges, as it will
2154 diff may generate unexpected results for merges, as it will
2155 default to comparing against the working directory's first
2155 default to comparing against the working directory's first
2156 parent changeset if no revisions are specified.
2156 parent changeset if no revisions are specified.
2157
2157
2158 When two revision arguments are given, then changes are shown
2158 When two revision arguments are given, then changes are shown
2159 between those revisions. If only one revision is specified then
2159 between those revisions. If only one revision is specified then
2160 that revision is compared to the working directory, and, when no
2160 that revision is compared to the working directory, and, when no
2161 revisions are specified, the working directory files are compared
2161 revisions are specified, the working directory files are compared
2162 to its parent.
2162 to its parent.
2163
2163
2164 Alternatively you can specify -c/--change with a revision to see
2164 Alternatively you can specify -c/--change with a revision to see
2165 the changes in that changeset relative to its first parent.
2165 the changes in that changeset relative to its first parent.
2166
2166
2167 Without the -a/--text option, diff will avoid generating diffs of
2167 Without the -a/--text option, diff will avoid generating diffs of
2168 files it detects as binary. With -a, diff will generate a diff
2168 files it detects as binary. With -a, diff will generate a diff
2169 anyway, probably with undesirable results.
2169 anyway, probably with undesirable results.
2170
2170
2171 Use the -g/--git option to generate diffs in the git extended diff
2171 Use the -g/--git option to generate diffs in the git extended diff
2172 format. For more information, read :hg:`help diffs`.
2172 format. For more information, read :hg:`help diffs`.
2173
2173
2174 Returns 0 on success.
2174 Returns 0 on success.
2175 """
2175 """
2176
2176
2177 revs = opts.get('rev')
2177 revs = opts.get('rev')
2178 change = opts.get('change')
2178 change = opts.get('change')
2179 stat = opts.get('stat')
2179 stat = opts.get('stat')
2180 reverse = opts.get('reverse')
2180 reverse = opts.get('reverse')
2181
2181
2182 if revs and change:
2182 if revs and change:
2183 msg = _('cannot specify --rev and --change at the same time')
2183 msg = _('cannot specify --rev and --change at the same time')
2184 raise util.Abort(msg)
2184 raise util.Abort(msg)
2185 elif change:
2185 elif change:
2186 node2 = scmutil.revsingle(repo, change, None).node()
2186 node2 = scmutil.revsingle(repo, change, None).node()
2187 node1 = repo[node2].p1().node()
2187 node1 = repo[node2].p1().node()
2188 else:
2188 else:
2189 node1, node2 = scmutil.revpair(repo, revs)
2189 node1, node2 = scmutil.revpair(repo, revs)
2190
2190
2191 if reverse:
2191 if reverse:
2192 node1, node2 = node2, node1
2192 node1, node2 = node2, node1
2193
2193
2194 diffopts = patch.diffopts(ui, opts)
2194 diffopts = patch.diffopts(ui, opts)
2195 m = scmutil.match(repo, pats, opts)
2195 m = scmutil.match(repo[node2], pats, opts)
2196 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2196 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2197 listsubrepos=opts.get('subrepos'))
2197 listsubrepos=opts.get('subrepos'))
2198
2198
2199 @command('^export',
2199 @command('^export',
2200 [('o', 'output', '',
2200 [('o', 'output', '',
2201 _('print output to file with formatted name'), _('FORMAT')),
2201 _('print output to file with formatted name'), _('FORMAT')),
2202 ('', 'switch-parent', None, _('diff against the second parent')),
2202 ('', 'switch-parent', None, _('diff against the second parent')),
2203 ('r', 'rev', [], _('revisions to export'), _('REV')),
2203 ('r', 'rev', [], _('revisions to export'), _('REV')),
2204 ] + diffopts,
2204 ] + diffopts,
2205 _('[OPTION]... [-o OUTFILESPEC] REV...'))
2205 _('[OPTION]... [-o OUTFILESPEC] REV...'))
2206 def export(ui, repo, *changesets, **opts):
2206 def export(ui, repo, *changesets, **opts):
2207 """dump the header and diffs for one or more changesets
2207 """dump the header and diffs for one or more changesets
2208
2208
2209 Print the changeset header and diffs for one or more revisions.
2209 Print the changeset header and diffs for one or more revisions.
2210
2210
2211 The information shown in the changeset header is: author, date,
2211 The information shown in the changeset header is: author, date,
2212 branch name (if non-default), changeset hash, parent(s) and commit
2212 branch name (if non-default), changeset hash, parent(s) and commit
2213 comment.
2213 comment.
2214
2214
2215 .. note::
2215 .. note::
2216 export may generate unexpected diff output for merge
2216 export may generate unexpected diff output for merge
2217 changesets, as it will compare the merge changeset against its
2217 changesets, as it will compare the merge changeset against its
2218 first parent only.
2218 first parent only.
2219
2219
2220 Output may be to a file, in which case the name of the file is
2220 Output may be to a file, in which case the name of the file is
2221 given using a format string. The formatting rules are as follows:
2221 given using a format string. The formatting rules are as follows:
2222
2222
2223 :``%%``: literal "%" character
2223 :``%%``: literal "%" character
2224 :``%H``: changeset hash (40 hexadecimal digits)
2224 :``%H``: changeset hash (40 hexadecimal digits)
2225 :``%N``: number of patches being generated
2225 :``%N``: number of patches being generated
2226 :``%R``: changeset revision number
2226 :``%R``: changeset revision number
2227 :``%b``: basename of the exporting repository
2227 :``%b``: basename of the exporting repository
2228 :``%h``: short-form changeset hash (12 hexadecimal digits)
2228 :``%h``: short-form changeset hash (12 hexadecimal digits)
2229 :``%n``: zero-padded sequence number, starting at 1
2229 :``%n``: zero-padded sequence number, starting at 1
2230 :``%r``: zero-padded changeset revision number
2230 :``%r``: zero-padded changeset revision number
2231
2231
2232 Without the -a/--text option, export will avoid generating diffs
2232 Without the -a/--text option, export will avoid generating diffs
2233 of files it detects as binary. With -a, export will generate a
2233 of files it detects as binary. With -a, export will generate a
2234 diff anyway, probably with undesirable results.
2234 diff anyway, probably with undesirable results.
2235
2235
2236 Use the -g/--git option to generate diffs in the git extended diff
2236 Use the -g/--git option to generate diffs in the git extended diff
2237 format. See :hg:`help diffs` for more information.
2237 format. See :hg:`help diffs` for more information.
2238
2238
2239 With the --switch-parent option, the diff will be against the
2239 With the --switch-parent option, the diff will be against the
2240 second parent. It can be useful to review a merge.
2240 second parent. It can be useful to review a merge.
2241
2241
2242 Returns 0 on success.
2242 Returns 0 on success.
2243 """
2243 """
2244 changesets += tuple(opts.get('rev', []))
2244 changesets += tuple(opts.get('rev', []))
2245 if not changesets:
2245 if not changesets:
2246 raise util.Abort(_("export requires at least one changeset"))
2246 raise util.Abort(_("export requires at least one changeset"))
2247 revs = scmutil.revrange(repo, changesets)
2247 revs = scmutil.revrange(repo, changesets)
2248 if len(revs) > 1:
2248 if len(revs) > 1:
2249 ui.note(_('exporting patches:\n'))
2249 ui.note(_('exporting patches:\n'))
2250 else:
2250 else:
2251 ui.note(_('exporting patch:\n'))
2251 ui.note(_('exporting patch:\n'))
2252 cmdutil.export(repo, revs, template=opts.get('output'),
2252 cmdutil.export(repo, revs, template=opts.get('output'),
2253 switch_parent=opts.get('switch_parent'),
2253 switch_parent=opts.get('switch_parent'),
2254 opts=patch.diffopts(ui, opts))
2254 opts=patch.diffopts(ui, opts))
2255
2255
2256 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2256 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2257 def forget(ui, repo, *pats, **opts):
2257 def forget(ui, repo, *pats, **opts):
2258 """forget the specified files on the next commit
2258 """forget the specified files on the next commit
2259
2259
2260 Mark the specified files so they will no longer be tracked
2260 Mark the specified files so they will no longer be tracked
2261 after the next commit.
2261 after the next commit.
2262
2262
2263 This only removes files from the current branch, not from the
2263 This only removes files from the current branch, not from the
2264 entire project history, and it does not delete them from the
2264 entire project history, and it does not delete them from the
2265 working directory.
2265 working directory.
2266
2266
2267 To undo a forget before the next commit, see :hg:`add`.
2267 To undo a forget before the next commit, see :hg:`add`.
2268
2268
2269 Returns 0 on success.
2269 Returns 0 on success.
2270 """
2270 """
2271
2271
2272 if not pats:
2272 if not pats:
2273 raise util.Abort(_('no files specified'))
2273 raise util.Abort(_('no files specified'))
2274
2274
2275 m = scmutil.match(repo, pats, opts)
2275 m = scmutil.match(repo[None], pats, opts)
2276 s = repo.status(match=m, clean=True)
2276 s = repo.status(match=m, clean=True)
2277 forget = sorted(s[0] + s[1] + s[3] + s[6])
2277 forget = sorted(s[0] + s[1] + s[3] + s[6])
2278 errs = 0
2278 errs = 0
2279
2279
2280 for f in m.files():
2280 for f in m.files():
2281 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2281 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2282 if os.path.exists(m.rel(f)):
2282 if os.path.exists(m.rel(f)):
2283 ui.warn(_('not removing %s: file is already untracked\n')
2283 ui.warn(_('not removing %s: file is already untracked\n')
2284 % m.rel(f))
2284 % m.rel(f))
2285 errs = 1
2285 errs = 1
2286
2286
2287 for f in forget:
2287 for f in forget:
2288 if ui.verbose or not m.exact(f):
2288 if ui.verbose or not m.exact(f):
2289 ui.status(_('removing %s\n') % m.rel(f))
2289 ui.status(_('removing %s\n') % m.rel(f))
2290
2290
2291 repo[None].forget(forget)
2291 repo[None].forget(forget)
2292 return errs
2292 return errs
2293
2293
2294 @command('grep',
2294 @command('grep',
2295 [('0', 'print0', None, _('end fields with NUL')),
2295 [('0', 'print0', None, _('end fields with NUL')),
2296 ('', 'all', None, _('print all revisions that match')),
2296 ('', 'all', None, _('print all revisions that match')),
2297 ('a', 'text', None, _('treat all files as text')),
2297 ('a', 'text', None, _('treat all files as text')),
2298 ('f', 'follow', None,
2298 ('f', 'follow', None,
2299 _('follow changeset history,'
2299 _('follow changeset history,'
2300 ' or file history across copies and renames')),
2300 ' or file history across copies and renames')),
2301 ('i', 'ignore-case', None, _('ignore case when matching')),
2301 ('i', 'ignore-case', None, _('ignore case when matching')),
2302 ('l', 'files-with-matches', None,
2302 ('l', 'files-with-matches', None,
2303 _('print only filenames and revisions that match')),
2303 _('print only filenames and revisions that match')),
2304 ('n', 'line-number', None, _('print matching line numbers')),
2304 ('n', 'line-number', None, _('print matching line numbers')),
2305 ('r', 'rev', [],
2305 ('r', 'rev', [],
2306 _('only search files changed within revision range'), _('REV')),
2306 _('only search files changed within revision range'), _('REV')),
2307 ('u', 'user', None, _('list the author (long with -v)')),
2307 ('u', 'user', None, _('list the author (long with -v)')),
2308 ('d', 'date', None, _('list the date (short with -q)')),
2308 ('d', 'date', None, _('list the date (short with -q)')),
2309 ] + walkopts,
2309 ] + walkopts,
2310 _('[OPTION]... PATTERN [FILE]...'))
2310 _('[OPTION]... PATTERN [FILE]...'))
2311 def grep(ui, repo, pattern, *pats, **opts):
2311 def grep(ui, repo, pattern, *pats, **opts):
2312 """search for a pattern in specified files and revisions
2312 """search for a pattern in specified files and revisions
2313
2313
2314 Search revisions of files for a regular expression.
2314 Search revisions of files for a regular expression.
2315
2315
2316 This command behaves differently than Unix grep. It only accepts
2316 This command behaves differently than Unix grep. It only accepts
2317 Python/Perl regexps. It searches repository history, not the
2317 Python/Perl regexps. It searches repository history, not the
2318 working directory. It always prints the revision number in which a
2318 working directory. It always prints the revision number in which a
2319 match appears.
2319 match appears.
2320
2320
2321 By default, grep only prints output for the first revision of a
2321 By default, grep only prints output for the first revision of a
2322 file in which it finds a match. To get it to print every revision
2322 file in which it finds a match. To get it to print every revision
2323 that contains a change in match status ("-" for a match that
2323 that contains a change in match status ("-" for a match that
2324 becomes a non-match, or "+" for a non-match that becomes a match),
2324 becomes a non-match, or "+" for a non-match that becomes a match),
2325 use the --all flag.
2325 use the --all flag.
2326
2326
2327 Returns 0 if a match is found, 1 otherwise.
2327 Returns 0 if a match is found, 1 otherwise.
2328 """
2328 """
2329 reflags = 0
2329 reflags = 0
2330 if opts.get('ignore_case'):
2330 if opts.get('ignore_case'):
2331 reflags |= re.I
2331 reflags |= re.I
2332 try:
2332 try:
2333 regexp = re.compile(pattern, reflags)
2333 regexp = re.compile(pattern, reflags)
2334 except re.error, inst:
2334 except re.error, inst:
2335 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2335 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2336 return 1
2336 return 1
2337 sep, eol = ':', '\n'
2337 sep, eol = ':', '\n'
2338 if opts.get('print0'):
2338 if opts.get('print0'):
2339 sep = eol = '\0'
2339 sep = eol = '\0'
2340
2340
2341 getfile = util.lrucachefunc(repo.file)
2341 getfile = util.lrucachefunc(repo.file)
2342
2342
2343 def matchlines(body):
2343 def matchlines(body):
2344 begin = 0
2344 begin = 0
2345 linenum = 0
2345 linenum = 0
2346 while True:
2346 while True:
2347 match = regexp.search(body, begin)
2347 match = regexp.search(body, begin)
2348 if not match:
2348 if not match:
2349 break
2349 break
2350 mstart, mend = match.span()
2350 mstart, mend = match.span()
2351 linenum += body.count('\n', begin, mstart) + 1
2351 linenum += body.count('\n', begin, mstart) + 1
2352 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2352 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2353 begin = body.find('\n', mend) + 1 or len(body)
2353 begin = body.find('\n', mend) + 1 or len(body)
2354 lend = begin - 1
2354 lend = begin - 1
2355 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2355 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2356
2356
2357 class linestate(object):
2357 class linestate(object):
2358 def __init__(self, line, linenum, colstart, colend):
2358 def __init__(self, line, linenum, colstart, colend):
2359 self.line = line
2359 self.line = line
2360 self.linenum = linenum
2360 self.linenum = linenum
2361 self.colstart = colstart
2361 self.colstart = colstart
2362 self.colend = colend
2362 self.colend = colend
2363
2363
2364 def __hash__(self):
2364 def __hash__(self):
2365 return hash((self.linenum, self.line))
2365 return hash((self.linenum, self.line))
2366
2366
2367 def __eq__(self, other):
2367 def __eq__(self, other):
2368 return self.line == other.line
2368 return self.line == other.line
2369
2369
2370 matches = {}
2370 matches = {}
2371 copies = {}
2371 copies = {}
2372 def grepbody(fn, rev, body):
2372 def grepbody(fn, rev, body):
2373 matches[rev].setdefault(fn, [])
2373 matches[rev].setdefault(fn, [])
2374 m = matches[rev][fn]
2374 m = matches[rev][fn]
2375 for lnum, cstart, cend, line in matchlines(body):
2375 for lnum, cstart, cend, line in matchlines(body):
2376 s = linestate(line, lnum, cstart, cend)
2376 s = linestate(line, lnum, cstart, cend)
2377 m.append(s)
2377 m.append(s)
2378
2378
2379 def difflinestates(a, b):
2379 def difflinestates(a, b):
2380 sm = difflib.SequenceMatcher(None, a, b)
2380 sm = difflib.SequenceMatcher(None, a, b)
2381 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2381 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2382 if tag == 'insert':
2382 if tag == 'insert':
2383 for i in xrange(blo, bhi):
2383 for i in xrange(blo, bhi):
2384 yield ('+', b[i])
2384 yield ('+', b[i])
2385 elif tag == 'delete':
2385 elif tag == 'delete':
2386 for i in xrange(alo, ahi):
2386 for i in xrange(alo, ahi):
2387 yield ('-', a[i])
2387 yield ('-', a[i])
2388 elif tag == 'replace':
2388 elif tag == 'replace':
2389 for i in xrange(alo, ahi):
2389 for i in xrange(alo, ahi):
2390 yield ('-', a[i])
2390 yield ('-', a[i])
2391 for i in xrange(blo, bhi):
2391 for i in xrange(blo, bhi):
2392 yield ('+', b[i])
2392 yield ('+', b[i])
2393
2393
2394 def display(fn, ctx, pstates, states):
2394 def display(fn, ctx, pstates, states):
2395 rev = ctx.rev()
2395 rev = ctx.rev()
2396 datefunc = ui.quiet and util.shortdate or util.datestr
2396 datefunc = ui.quiet and util.shortdate or util.datestr
2397 found = False
2397 found = False
2398 filerevmatches = {}
2398 filerevmatches = {}
2399 def binary():
2399 def binary():
2400 flog = getfile(fn)
2400 flog = getfile(fn)
2401 return util.binary(flog.read(ctx.filenode(fn)))
2401 return util.binary(flog.read(ctx.filenode(fn)))
2402
2402
2403 if opts.get('all'):
2403 if opts.get('all'):
2404 iter = difflinestates(pstates, states)
2404 iter = difflinestates(pstates, states)
2405 else:
2405 else:
2406 iter = [('', l) for l in states]
2406 iter = [('', l) for l in states]
2407 for change, l in iter:
2407 for change, l in iter:
2408 cols = [fn, str(rev)]
2408 cols = [fn, str(rev)]
2409 before, match, after = None, None, None
2409 before, match, after = None, None, None
2410 if opts.get('line_number'):
2410 if opts.get('line_number'):
2411 cols.append(str(l.linenum))
2411 cols.append(str(l.linenum))
2412 if opts.get('all'):
2412 if opts.get('all'):
2413 cols.append(change)
2413 cols.append(change)
2414 if opts.get('user'):
2414 if opts.get('user'):
2415 cols.append(ui.shortuser(ctx.user()))
2415 cols.append(ui.shortuser(ctx.user()))
2416 if opts.get('date'):
2416 if opts.get('date'):
2417 cols.append(datefunc(ctx.date()))
2417 cols.append(datefunc(ctx.date()))
2418 if opts.get('files_with_matches'):
2418 if opts.get('files_with_matches'):
2419 c = (fn, rev)
2419 c = (fn, rev)
2420 if c in filerevmatches:
2420 if c in filerevmatches:
2421 continue
2421 continue
2422 filerevmatches[c] = 1
2422 filerevmatches[c] = 1
2423 else:
2423 else:
2424 before = l.line[:l.colstart]
2424 before = l.line[:l.colstart]
2425 match = l.line[l.colstart:l.colend]
2425 match = l.line[l.colstart:l.colend]
2426 after = l.line[l.colend:]
2426 after = l.line[l.colend:]
2427 ui.write(sep.join(cols))
2427 ui.write(sep.join(cols))
2428 if before is not None:
2428 if before is not None:
2429 if not opts.get('text') and binary():
2429 if not opts.get('text') and binary():
2430 ui.write(sep + " Binary file matches")
2430 ui.write(sep + " Binary file matches")
2431 else:
2431 else:
2432 ui.write(sep + before)
2432 ui.write(sep + before)
2433 ui.write(match, label='grep.match')
2433 ui.write(match, label='grep.match')
2434 ui.write(after)
2434 ui.write(after)
2435 ui.write(eol)
2435 ui.write(eol)
2436 found = True
2436 found = True
2437 return found
2437 return found
2438
2438
2439 skip = {}
2439 skip = {}
2440 revfiles = {}
2440 revfiles = {}
2441 matchfn = scmutil.match(repo, pats, opts)
2441 matchfn = scmutil.match(repo[None], pats, opts)
2442 found = False
2442 found = False
2443 follow = opts.get('follow')
2443 follow = opts.get('follow')
2444
2444
2445 def prep(ctx, fns):
2445 def prep(ctx, fns):
2446 rev = ctx.rev()
2446 rev = ctx.rev()
2447 pctx = ctx.p1()
2447 pctx = ctx.p1()
2448 parent = pctx.rev()
2448 parent = pctx.rev()
2449 matches.setdefault(rev, {})
2449 matches.setdefault(rev, {})
2450 matches.setdefault(parent, {})
2450 matches.setdefault(parent, {})
2451 files = revfiles.setdefault(rev, [])
2451 files = revfiles.setdefault(rev, [])
2452 for fn in fns:
2452 for fn in fns:
2453 flog = getfile(fn)
2453 flog = getfile(fn)
2454 try:
2454 try:
2455 fnode = ctx.filenode(fn)
2455 fnode = ctx.filenode(fn)
2456 except error.LookupError:
2456 except error.LookupError:
2457 continue
2457 continue
2458
2458
2459 copied = flog.renamed(fnode)
2459 copied = flog.renamed(fnode)
2460 copy = follow and copied and copied[0]
2460 copy = follow and copied and copied[0]
2461 if copy:
2461 if copy:
2462 copies.setdefault(rev, {})[fn] = copy
2462 copies.setdefault(rev, {})[fn] = copy
2463 if fn in skip:
2463 if fn in skip:
2464 if copy:
2464 if copy:
2465 skip[copy] = True
2465 skip[copy] = True
2466 continue
2466 continue
2467 files.append(fn)
2467 files.append(fn)
2468
2468
2469 if fn not in matches[rev]:
2469 if fn not in matches[rev]:
2470 grepbody(fn, rev, flog.read(fnode))
2470 grepbody(fn, rev, flog.read(fnode))
2471
2471
2472 pfn = copy or fn
2472 pfn = copy or fn
2473 if pfn not in matches[parent]:
2473 if pfn not in matches[parent]:
2474 try:
2474 try:
2475 fnode = pctx.filenode(pfn)
2475 fnode = pctx.filenode(pfn)
2476 grepbody(pfn, parent, flog.read(fnode))
2476 grepbody(pfn, parent, flog.read(fnode))
2477 except error.LookupError:
2477 except error.LookupError:
2478 pass
2478 pass
2479
2479
2480 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2480 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2481 rev = ctx.rev()
2481 rev = ctx.rev()
2482 parent = ctx.p1().rev()
2482 parent = ctx.p1().rev()
2483 for fn in sorted(revfiles.get(rev, [])):
2483 for fn in sorted(revfiles.get(rev, [])):
2484 states = matches[rev][fn]
2484 states = matches[rev][fn]
2485 copy = copies.get(rev, {}).get(fn)
2485 copy = copies.get(rev, {}).get(fn)
2486 if fn in skip:
2486 if fn in skip:
2487 if copy:
2487 if copy:
2488 skip[copy] = True
2488 skip[copy] = True
2489 continue
2489 continue
2490 pstates = matches.get(parent, {}).get(copy or fn, [])
2490 pstates = matches.get(parent, {}).get(copy or fn, [])
2491 if pstates or states:
2491 if pstates or states:
2492 r = display(fn, ctx, pstates, states)
2492 r = display(fn, ctx, pstates, states)
2493 found = found or r
2493 found = found or r
2494 if r and not opts.get('all'):
2494 if r and not opts.get('all'):
2495 skip[fn] = True
2495 skip[fn] = True
2496 if copy:
2496 if copy:
2497 skip[copy] = True
2497 skip[copy] = True
2498 del matches[rev]
2498 del matches[rev]
2499 del revfiles[rev]
2499 del revfiles[rev]
2500
2500
2501 return not found
2501 return not found
2502
2502
2503 @command('heads',
2503 @command('heads',
2504 [('r', 'rev', '',
2504 [('r', 'rev', '',
2505 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2505 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2506 ('t', 'topo', False, _('show topological heads only')),
2506 ('t', 'topo', False, _('show topological heads only')),
2507 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2507 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2508 ('c', 'closed', False, _('show normal and closed branch heads')),
2508 ('c', 'closed', False, _('show normal and closed branch heads')),
2509 ] + templateopts,
2509 ] + templateopts,
2510 _('[-ac] [-r STARTREV] [REV]...'))
2510 _('[-ac] [-r STARTREV] [REV]...'))
2511 def heads(ui, repo, *branchrevs, **opts):
2511 def heads(ui, repo, *branchrevs, **opts):
2512 """show current repository heads or show branch heads
2512 """show current repository heads or show branch heads
2513
2513
2514 With no arguments, show all repository branch heads.
2514 With no arguments, show all repository branch heads.
2515
2515
2516 Repository "heads" are changesets with no child changesets. They are
2516 Repository "heads" are changesets with no child changesets. They are
2517 where development generally takes place and are the usual targets
2517 where development generally takes place and are the usual targets
2518 for update and merge operations. Branch heads are changesets that have
2518 for update and merge operations. Branch heads are changesets that have
2519 no child changeset on the same branch.
2519 no child changeset on the same branch.
2520
2520
2521 If one or more REVs are given, only branch heads on the branches
2521 If one or more REVs are given, only branch heads on the branches
2522 associated with the specified changesets are shown.
2522 associated with the specified changesets are shown.
2523
2523
2524 If -c/--closed is specified, also show branch heads marked closed
2524 If -c/--closed is specified, also show branch heads marked closed
2525 (see :hg:`commit --close-branch`).
2525 (see :hg:`commit --close-branch`).
2526
2526
2527 If STARTREV is specified, only those heads that are descendants of
2527 If STARTREV is specified, only those heads that are descendants of
2528 STARTREV will be displayed.
2528 STARTREV will be displayed.
2529
2529
2530 If -t/--topo is specified, named branch mechanics will be ignored and only
2530 If -t/--topo is specified, named branch mechanics will be ignored and only
2531 changesets without children will be shown.
2531 changesets without children will be shown.
2532
2532
2533 Returns 0 if matching heads are found, 1 if not.
2533 Returns 0 if matching heads are found, 1 if not.
2534 """
2534 """
2535
2535
2536 start = None
2536 start = None
2537 if 'rev' in opts:
2537 if 'rev' in opts:
2538 start = scmutil.revsingle(repo, opts['rev'], None).node()
2538 start = scmutil.revsingle(repo, opts['rev'], None).node()
2539
2539
2540 if opts.get('topo'):
2540 if opts.get('topo'):
2541 heads = [repo[h] for h in repo.heads(start)]
2541 heads = [repo[h] for h in repo.heads(start)]
2542 else:
2542 else:
2543 heads = []
2543 heads = []
2544 for branch in repo.branchmap():
2544 for branch in repo.branchmap():
2545 heads += repo.branchheads(branch, start, opts.get('closed'))
2545 heads += repo.branchheads(branch, start, opts.get('closed'))
2546 heads = [repo[h] for h in heads]
2546 heads = [repo[h] for h in heads]
2547
2547
2548 if branchrevs:
2548 if branchrevs:
2549 branches = set(repo[br].branch() for br in branchrevs)
2549 branches = set(repo[br].branch() for br in branchrevs)
2550 heads = [h for h in heads if h.branch() in branches]
2550 heads = [h for h in heads if h.branch() in branches]
2551
2551
2552 if opts.get('active') and branchrevs:
2552 if opts.get('active') and branchrevs:
2553 dagheads = repo.heads(start)
2553 dagheads = repo.heads(start)
2554 heads = [h for h in heads if h.node() in dagheads]
2554 heads = [h for h in heads if h.node() in dagheads]
2555
2555
2556 if branchrevs:
2556 if branchrevs:
2557 haveheads = set(h.branch() for h in heads)
2557 haveheads = set(h.branch() for h in heads)
2558 if branches - haveheads:
2558 if branches - haveheads:
2559 headless = ', '.join(b for b in branches - haveheads)
2559 headless = ', '.join(b for b in branches - haveheads)
2560 msg = _('no open branch heads found on branches %s')
2560 msg = _('no open branch heads found on branches %s')
2561 if opts.get('rev'):
2561 if opts.get('rev'):
2562 msg += _(' (started at %s)' % opts['rev'])
2562 msg += _(' (started at %s)' % opts['rev'])
2563 ui.warn((msg + '\n') % headless)
2563 ui.warn((msg + '\n') % headless)
2564
2564
2565 if not heads:
2565 if not heads:
2566 return 1
2566 return 1
2567
2567
2568 heads = sorted(heads, key=lambda x: -x.rev())
2568 heads = sorted(heads, key=lambda x: -x.rev())
2569 displayer = cmdutil.show_changeset(ui, repo, opts)
2569 displayer = cmdutil.show_changeset(ui, repo, opts)
2570 for ctx in heads:
2570 for ctx in heads:
2571 displayer.show(ctx)
2571 displayer.show(ctx)
2572 displayer.close()
2572 displayer.close()
2573
2573
2574 @command('help',
2574 @command('help',
2575 [('e', 'extension', None, _('show only help for extensions')),
2575 [('e', 'extension', None, _('show only help for extensions')),
2576 ('c', 'command', None, _('show only help for commands'))],
2576 ('c', 'command', None, _('show only help for commands'))],
2577 _('[-ec] [TOPIC]'))
2577 _('[-ec] [TOPIC]'))
2578 def help_(ui, name=None, with_version=False, unknowncmd=False, full=True, **opts):
2578 def help_(ui, name=None, with_version=False, unknowncmd=False, full=True, **opts):
2579 """show help for a given topic or a help overview
2579 """show help for a given topic or a help overview
2580
2580
2581 With no arguments, print a list of commands with short help messages.
2581 With no arguments, print a list of commands with short help messages.
2582
2582
2583 Given a topic, extension, or command name, print help for that
2583 Given a topic, extension, or command name, print help for that
2584 topic.
2584 topic.
2585
2585
2586 Returns 0 if successful.
2586 Returns 0 if successful.
2587 """
2587 """
2588 option_lists = []
2588 option_lists = []
2589 textwidth = min(ui.termwidth(), 80) - 2
2589 textwidth = min(ui.termwidth(), 80) - 2
2590
2590
2591 def addglobalopts(aliases):
2591 def addglobalopts(aliases):
2592 if ui.verbose:
2592 if ui.verbose:
2593 option_lists.append((_("global options:"), globalopts))
2593 option_lists.append((_("global options:"), globalopts))
2594 if name == 'shortlist':
2594 if name == 'shortlist':
2595 option_lists.append((_('use "hg help" for the full list '
2595 option_lists.append((_('use "hg help" for the full list '
2596 'of commands'), ()))
2596 'of commands'), ()))
2597 else:
2597 else:
2598 if name == 'shortlist':
2598 if name == 'shortlist':
2599 msg = _('use "hg help" for the full list of commands '
2599 msg = _('use "hg help" for the full list of commands '
2600 'or "hg -v" for details')
2600 'or "hg -v" for details')
2601 elif name and not full:
2601 elif name and not full:
2602 msg = _('use "hg help %s" to show the full help text' % name)
2602 msg = _('use "hg help %s" to show the full help text' % name)
2603 elif aliases:
2603 elif aliases:
2604 msg = _('use "hg -v help%s" to show builtin aliases and '
2604 msg = _('use "hg -v help%s" to show builtin aliases and '
2605 'global options') % (name and " " + name or "")
2605 'global options') % (name and " " + name or "")
2606 else:
2606 else:
2607 msg = _('use "hg -v help %s" to show global options') % name
2607 msg = _('use "hg -v help %s" to show global options') % name
2608 option_lists.append((msg, ()))
2608 option_lists.append((msg, ()))
2609
2609
2610 def helpcmd(name):
2610 def helpcmd(name):
2611 if with_version:
2611 if with_version:
2612 version_(ui)
2612 version_(ui)
2613 ui.write('\n')
2613 ui.write('\n')
2614
2614
2615 try:
2615 try:
2616 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
2616 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
2617 except error.AmbiguousCommand, inst:
2617 except error.AmbiguousCommand, inst:
2618 # py3k fix: except vars can't be used outside the scope of the
2618 # py3k fix: except vars can't be used outside the scope of the
2619 # except block, nor can be used inside a lambda. python issue4617
2619 # except block, nor can be used inside a lambda. python issue4617
2620 prefix = inst.args[0]
2620 prefix = inst.args[0]
2621 select = lambda c: c.lstrip('^').startswith(prefix)
2621 select = lambda c: c.lstrip('^').startswith(prefix)
2622 helplist(_('list of commands:\n\n'), select)
2622 helplist(_('list of commands:\n\n'), select)
2623 return
2623 return
2624
2624
2625 # check if it's an invalid alias and display its error if it is
2625 # check if it's an invalid alias and display its error if it is
2626 if getattr(entry[0], 'badalias', False):
2626 if getattr(entry[0], 'badalias', False):
2627 if not unknowncmd:
2627 if not unknowncmd:
2628 entry[0](ui)
2628 entry[0](ui)
2629 return
2629 return
2630
2630
2631 # synopsis
2631 # synopsis
2632 if len(entry) > 2:
2632 if len(entry) > 2:
2633 if entry[2].startswith('hg'):
2633 if entry[2].startswith('hg'):
2634 ui.write("%s\n" % entry[2])
2634 ui.write("%s\n" % entry[2])
2635 else:
2635 else:
2636 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2636 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2637 else:
2637 else:
2638 ui.write('hg %s\n' % aliases[0])
2638 ui.write('hg %s\n' % aliases[0])
2639
2639
2640 # aliases
2640 # aliases
2641 if full and not ui.quiet and len(aliases) > 1:
2641 if full and not ui.quiet and len(aliases) > 1:
2642 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2642 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2643
2643
2644 # description
2644 # description
2645 doc = gettext(entry[0].__doc__)
2645 doc = gettext(entry[0].__doc__)
2646 if not doc:
2646 if not doc:
2647 doc = _("(no help text available)")
2647 doc = _("(no help text available)")
2648 if hasattr(entry[0], 'definition'): # aliased command
2648 if hasattr(entry[0], 'definition'): # aliased command
2649 if entry[0].definition.startswith('!'): # shell alias
2649 if entry[0].definition.startswith('!'): # shell alias
2650 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2650 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2651 else:
2651 else:
2652 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2652 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2653 if ui.quiet or not full:
2653 if ui.quiet or not full:
2654 doc = doc.splitlines()[0]
2654 doc = doc.splitlines()[0]
2655 keep = ui.verbose and ['verbose'] or []
2655 keep = ui.verbose and ['verbose'] or []
2656 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2656 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2657 ui.write("\n%s\n" % formatted)
2657 ui.write("\n%s\n" % formatted)
2658 if pruned:
2658 if pruned:
2659 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2659 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2660
2660
2661 if not ui.quiet:
2661 if not ui.quiet:
2662 # options
2662 # options
2663 if entry[1]:
2663 if entry[1]:
2664 option_lists.append((_("options:\n"), entry[1]))
2664 option_lists.append((_("options:\n"), entry[1]))
2665
2665
2666 addglobalopts(False)
2666 addglobalopts(False)
2667
2667
2668 # check if this command shadows a non-trivial (multi-line)
2668 # check if this command shadows a non-trivial (multi-line)
2669 # extension help text
2669 # extension help text
2670 try:
2670 try:
2671 mod = extensions.find(name)
2671 mod = extensions.find(name)
2672 doc = gettext(mod.__doc__) or ''
2672 doc = gettext(mod.__doc__) or ''
2673 if '\n' in doc.strip():
2673 if '\n' in doc.strip():
2674 msg = _('use "hg help -e %s" to show help for '
2674 msg = _('use "hg help -e %s" to show help for '
2675 'the %s extension') % (name, name)
2675 'the %s extension') % (name, name)
2676 ui.write('\n%s\n' % msg)
2676 ui.write('\n%s\n' % msg)
2677 except KeyError:
2677 except KeyError:
2678 pass
2678 pass
2679
2679
2680 def helplist(header, select=None):
2680 def helplist(header, select=None):
2681 h = {}
2681 h = {}
2682 cmds = {}
2682 cmds = {}
2683 for c, e in table.iteritems():
2683 for c, e in table.iteritems():
2684 f = c.split("|", 1)[0]
2684 f = c.split("|", 1)[0]
2685 if select and not select(f):
2685 if select and not select(f):
2686 continue
2686 continue
2687 if (not select and name != 'shortlist' and
2687 if (not select and name != 'shortlist' and
2688 e[0].__module__ != __name__):
2688 e[0].__module__ != __name__):
2689 continue
2689 continue
2690 if name == "shortlist" and not f.startswith("^"):
2690 if name == "shortlist" and not f.startswith("^"):
2691 continue
2691 continue
2692 f = f.lstrip("^")
2692 f = f.lstrip("^")
2693 if not ui.debugflag and f.startswith("debug"):
2693 if not ui.debugflag and f.startswith("debug"):
2694 continue
2694 continue
2695 doc = e[0].__doc__
2695 doc = e[0].__doc__
2696 if doc and 'DEPRECATED' in doc and not ui.verbose:
2696 if doc and 'DEPRECATED' in doc and not ui.verbose:
2697 continue
2697 continue
2698 doc = gettext(doc)
2698 doc = gettext(doc)
2699 if not doc:
2699 if not doc:
2700 doc = _("(no help text available)")
2700 doc = _("(no help text available)")
2701 h[f] = doc.splitlines()[0].rstrip()
2701 h[f] = doc.splitlines()[0].rstrip()
2702 cmds[f] = c.lstrip("^")
2702 cmds[f] = c.lstrip("^")
2703
2703
2704 if not h:
2704 if not h:
2705 ui.status(_('no commands defined\n'))
2705 ui.status(_('no commands defined\n'))
2706 return
2706 return
2707
2707
2708 ui.status(header)
2708 ui.status(header)
2709 fns = sorted(h)
2709 fns = sorted(h)
2710 m = max(map(len, fns))
2710 m = max(map(len, fns))
2711 for f in fns:
2711 for f in fns:
2712 if ui.verbose:
2712 if ui.verbose:
2713 commands = cmds[f].replace("|",", ")
2713 commands = cmds[f].replace("|",", ")
2714 ui.write(" %s:\n %s\n"%(commands, h[f]))
2714 ui.write(" %s:\n %s\n"%(commands, h[f]))
2715 else:
2715 else:
2716 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2716 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2717 initindent=' %-*s ' % (m, f),
2717 initindent=' %-*s ' % (m, f),
2718 hangindent=' ' * (m + 4))))
2718 hangindent=' ' * (m + 4))))
2719
2719
2720 if not ui.quiet:
2720 if not ui.quiet:
2721 addglobalopts(True)
2721 addglobalopts(True)
2722
2722
2723 def helptopic(name):
2723 def helptopic(name):
2724 for names, header, doc in help.helptable:
2724 for names, header, doc in help.helptable:
2725 if name in names:
2725 if name in names:
2726 break
2726 break
2727 else:
2727 else:
2728 raise error.UnknownCommand(name)
2728 raise error.UnknownCommand(name)
2729
2729
2730 # description
2730 # description
2731 if not doc:
2731 if not doc:
2732 doc = _("(no help text available)")
2732 doc = _("(no help text available)")
2733 if hasattr(doc, '__call__'):
2733 if hasattr(doc, '__call__'):
2734 doc = doc()
2734 doc = doc()
2735
2735
2736 ui.write("%s\n\n" % header)
2736 ui.write("%s\n\n" % header)
2737 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2737 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2738 try:
2738 try:
2739 cmdutil.findcmd(name, table)
2739 cmdutil.findcmd(name, table)
2740 ui.write(_('\nuse "hg help -c %s" to see help for '
2740 ui.write(_('\nuse "hg help -c %s" to see help for '
2741 'the %s command\n') % (name, name))
2741 'the %s command\n') % (name, name))
2742 except error.UnknownCommand:
2742 except error.UnknownCommand:
2743 pass
2743 pass
2744
2744
2745 def helpext(name):
2745 def helpext(name):
2746 try:
2746 try:
2747 mod = extensions.find(name)
2747 mod = extensions.find(name)
2748 doc = gettext(mod.__doc__) or _('no help text available')
2748 doc = gettext(mod.__doc__) or _('no help text available')
2749 except KeyError:
2749 except KeyError:
2750 mod = None
2750 mod = None
2751 doc = extensions.disabledext(name)
2751 doc = extensions.disabledext(name)
2752 if not doc:
2752 if not doc:
2753 raise error.UnknownCommand(name)
2753 raise error.UnknownCommand(name)
2754
2754
2755 if '\n' not in doc:
2755 if '\n' not in doc:
2756 head, tail = doc, ""
2756 head, tail = doc, ""
2757 else:
2757 else:
2758 head, tail = doc.split('\n', 1)
2758 head, tail = doc.split('\n', 1)
2759 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2759 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2760 if tail:
2760 if tail:
2761 ui.write(minirst.format(tail, textwidth))
2761 ui.write(minirst.format(tail, textwidth))
2762 ui.status('\n\n')
2762 ui.status('\n\n')
2763
2763
2764 if mod:
2764 if mod:
2765 try:
2765 try:
2766 ct = mod.cmdtable
2766 ct = mod.cmdtable
2767 except AttributeError:
2767 except AttributeError:
2768 ct = {}
2768 ct = {}
2769 modcmds = set([c.split('|', 1)[0] for c in ct])
2769 modcmds = set([c.split('|', 1)[0] for c in ct])
2770 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2770 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2771 else:
2771 else:
2772 ui.write(_('use "hg help extensions" for information on enabling '
2772 ui.write(_('use "hg help extensions" for information on enabling '
2773 'extensions\n'))
2773 'extensions\n'))
2774
2774
2775 def helpextcmd(name):
2775 def helpextcmd(name):
2776 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2776 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2777 doc = gettext(mod.__doc__).splitlines()[0]
2777 doc = gettext(mod.__doc__).splitlines()[0]
2778
2778
2779 msg = help.listexts(_("'%s' is provided by the following "
2779 msg = help.listexts(_("'%s' is provided by the following "
2780 "extension:") % cmd, {ext: doc}, indent=4)
2780 "extension:") % cmd, {ext: doc}, indent=4)
2781 ui.write(minirst.format(msg, textwidth))
2781 ui.write(minirst.format(msg, textwidth))
2782 ui.write('\n\n')
2782 ui.write('\n\n')
2783 ui.write(_('use "hg help extensions" for information on enabling '
2783 ui.write(_('use "hg help extensions" for information on enabling '
2784 'extensions\n'))
2784 'extensions\n'))
2785
2785
2786 if name and name != 'shortlist':
2786 if name and name != 'shortlist':
2787 i = None
2787 i = None
2788 if unknowncmd:
2788 if unknowncmd:
2789 queries = (helpextcmd,)
2789 queries = (helpextcmd,)
2790 elif opts.get('extension'):
2790 elif opts.get('extension'):
2791 queries = (helpext,)
2791 queries = (helpext,)
2792 elif opts.get('command'):
2792 elif opts.get('command'):
2793 queries = (helpcmd,)
2793 queries = (helpcmd,)
2794 else:
2794 else:
2795 queries = (helptopic, helpcmd, helpext, helpextcmd)
2795 queries = (helptopic, helpcmd, helpext, helpextcmd)
2796 for f in queries:
2796 for f in queries:
2797 try:
2797 try:
2798 f(name)
2798 f(name)
2799 i = None
2799 i = None
2800 break
2800 break
2801 except error.UnknownCommand, inst:
2801 except error.UnknownCommand, inst:
2802 i = inst
2802 i = inst
2803 if i:
2803 if i:
2804 raise i
2804 raise i
2805
2805
2806 else:
2806 else:
2807 # program name
2807 # program name
2808 if ui.verbose or with_version:
2808 if ui.verbose or with_version:
2809 version_(ui)
2809 version_(ui)
2810 else:
2810 else:
2811 ui.status(_("Mercurial Distributed SCM\n"))
2811 ui.status(_("Mercurial Distributed SCM\n"))
2812 ui.status('\n')
2812 ui.status('\n')
2813
2813
2814 # list of commands
2814 # list of commands
2815 if name == "shortlist":
2815 if name == "shortlist":
2816 header = _('basic commands:\n\n')
2816 header = _('basic commands:\n\n')
2817 else:
2817 else:
2818 header = _('list of commands:\n\n')
2818 header = _('list of commands:\n\n')
2819
2819
2820 helplist(header)
2820 helplist(header)
2821 if name != 'shortlist':
2821 if name != 'shortlist':
2822 text = help.listexts(_('enabled extensions:'), extensions.enabled())
2822 text = help.listexts(_('enabled extensions:'), extensions.enabled())
2823 if text:
2823 if text:
2824 ui.write("\n%s\n" % minirst.format(text, textwidth))
2824 ui.write("\n%s\n" % minirst.format(text, textwidth))
2825
2825
2826 # list all option lists
2826 # list all option lists
2827 opt_output = []
2827 opt_output = []
2828 multioccur = False
2828 multioccur = False
2829 for title, options in option_lists:
2829 for title, options in option_lists:
2830 opt_output.append(("\n%s" % title, None))
2830 opt_output.append(("\n%s" % title, None))
2831 for option in options:
2831 for option in options:
2832 if len(option) == 5:
2832 if len(option) == 5:
2833 shortopt, longopt, default, desc, optlabel = option
2833 shortopt, longopt, default, desc, optlabel = option
2834 else:
2834 else:
2835 shortopt, longopt, default, desc = option
2835 shortopt, longopt, default, desc = option
2836 optlabel = _("VALUE") # default label
2836 optlabel = _("VALUE") # default label
2837
2837
2838 if _("DEPRECATED") in desc and not ui.verbose:
2838 if _("DEPRECATED") in desc and not ui.verbose:
2839 continue
2839 continue
2840 if isinstance(default, list):
2840 if isinstance(default, list):
2841 numqualifier = " %s [+]" % optlabel
2841 numqualifier = " %s [+]" % optlabel
2842 multioccur = True
2842 multioccur = True
2843 elif (default is not None) and not isinstance(default, bool):
2843 elif (default is not None) and not isinstance(default, bool):
2844 numqualifier = " %s" % optlabel
2844 numqualifier = " %s" % optlabel
2845 else:
2845 else:
2846 numqualifier = ""
2846 numqualifier = ""
2847 opt_output.append(("%2s%s" %
2847 opt_output.append(("%2s%s" %
2848 (shortopt and "-%s" % shortopt,
2848 (shortopt and "-%s" % shortopt,
2849 longopt and " --%s%s" %
2849 longopt and " --%s%s" %
2850 (longopt, numqualifier)),
2850 (longopt, numqualifier)),
2851 "%s%s" % (desc,
2851 "%s%s" % (desc,
2852 default
2852 default
2853 and _(" (default: %s)") % default
2853 and _(" (default: %s)") % default
2854 or "")))
2854 or "")))
2855 if multioccur:
2855 if multioccur:
2856 msg = _("\n[+] marked option can be specified multiple times")
2856 msg = _("\n[+] marked option can be specified multiple times")
2857 if ui.verbose and name != 'shortlist':
2857 if ui.verbose and name != 'shortlist':
2858 opt_output.append((msg, None))
2858 opt_output.append((msg, None))
2859 else:
2859 else:
2860 opt_output.insert(-1, (msg, None))
2860 opt_output.insert(-1, (msg, None))
2861
2861
2862 if not name:
2862 if not name:
2863 ui.write(_("\nadditional help topics:\n\n"))
2863 ui.write(_("\nadditional help topics:\n\n"))
2864 topics = []
2864 topics = []
2865 for names, header, doc in help.helptable:
2865 for names, header, doc in help.helptable:
2866 topics.append((sorted(names, key=len, reverse=True)[0], header))
2866 topics.append((sorted(names, key=len, reverse=True)[0], header))
2867 topics_len = max([len(s[0]) for s in topics])
2867 topics_len = max([len(s[0]) for s in topics])
2868 for t, desc in topics:
2868 for t, desc in topics:
2869 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2869 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2870
2870
2871 if opt_output:
2871 if opt_output:
2872 colwidth = encoding.colwidth
2872 colwidth = encoding.colwidth
2873 # normalize: (opt or message, desc or None, width of opt)
2873 # normalize: (opt or message, desc or None, width of opt)
2874 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2874 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2875 for opt, desc in opt_output]
2875 for opt, desc in opt_output]
2876 hanging = max([e[2] for e in entries])
2876 hanging = max([e[2] for e in entries])
2877 for opt, desc, width in entries:
2877 for opt, desc, width in entries:
2878 if desc:
2878 if desc:
2879 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2879 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2880 hangindent = ' ' * (hanging + 3)
2880 hangindent = ' ' * (hanging + 3)
2881 ui.write('%s\n' % (util.wrap(desc, textwidth,
2881 ui.write('%s\n' % (util.wrap(desc, textwidth,
2882 initindent=initindent,
2882 initindent=initindent,
2883 hangindent=hangindent)))
2883 hangindent=hangindent)))
2884 else:
2884 else:
2885 ui.write("%s\n" % opt)
2885 ui.write("%s\n" % opt)
2886
2886
2887 @command('identify|id',
2887 @command('identify|id',
2888 [('r', 'rev', '',
2888 [('r', 'rev', '',
2889 _('identify the specified revision'), _('REV')),
2889 _('identify the specified revision'), _('REV')),
2890 ('n', 'num', None, _('show local revision number')),
2890 ('n', 'num', None, _('show local revision number')),
2891 ('i', 'id', None, _('show global revision id')),
2891 ('i', 'id', None, _('show global revision id')),
2892 ('b', 'branch', None, _('show branch')),
2892 ('b', 'branch', None, _('show branch')),
2893 ('t', 'tags', None, _('show tags')),
2893 ('t', 'tags', None, _('show tags')),
2894 ('B', 'bookmarks', None, _('show bookmarks'))],
2894 ('B', 'bookmarks', None, _('show bookmarks'))],
2895 _('[-nibtB] [-r REV] [SOURCE]'))
2895 _('[-nibtB] [-r REV] [SOURCE]'))
2896 def identify(ui, repo, source=None, rev=None,
2896 def identify(ui, repo, source=None, rev=None,
2897 num=None, id=None, branch=None, tags=None, bookmarks=None):
2897 num=None, id=None, branch=None, tags=None, bookmarks=None):
2898 """identify the working copy or specified revision
2898 """identify the working copy or specified revision
2899
2899
2900 Print a summary identifying the repository state at REV using one or
2900 Print a summary identifying the repository state at REV using one or
2901 two parent hash identifiers, followed by a "+" if the working
2901 two parent hash identifiers, followed by a "+" if the working
2902 directory has uncommitted changes, the branch name (if not default),
2902 directory has uncommitted changes, the branch name (if not default),
2903 a list of tags, and a list of bookmarks.
2903 a list of tags, and a list of bookmarks.
2904
2904
2905 When REV is not given, print a summary of the current state of the
2905 When REV is not given, print a summary of the current state of the
2906 repository.
2906 repository.
2907
2907
2908 Specifying a path to a repository root or Mercurial bundle will
2908 Specifying a path to a repository root or Mercurial bundle will
2909 cause lookup to operate on that repository/bundle.
2909 cause lookup to operate on that repository/bundle.
2910
2910
2911 Returns 0 if successful.
2911 Returns 0 if successful.
2912 """
2912 """
2913
2913
2914 if not repo and not source:
2914 if not repo and not source:
2915 raise util.Abort(_("there is no Mercurial repository here "
2915 raise util.Abort(_("there is no Mercurial repository here "
2916 "(.hg not found)"))
2916 "(.hg not found)"))
2917
2917
2918 hexfunc = ui.debugflag and hex or short
2918 hexfunc = ui.debugflag and hex or short
2919 default = not (num or id or branch or tags or bookmarks)
2919 default = not (num or id or branch or tags or bookmarks)
2920 output = []
2920 output = []
2921 revs = []
2921 revs = []
2922
2922
2923 if source:
2923 if source:
2924 source, branches = hg.parseurl(ui.expandpath(source))
2924 source, branches = hg.parseurl(ui.expandpath(source))
2925 repo = hg.peer(ui, {}, source)
2925 repo = hg.peer(ui, {}, source)
2926 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2926 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2927
2927
2928 if not repo.local():
2928 if not repo.local():
2929 if num or branch or tags:
2929 if num or branch or tags:
2930 raise util.Abort(
2930 raise util.Abort(
2931 _("can't query remote revision number, branch, or tags"))
2931 _("can't query remote revision number, branch, or tags"))
2932 if not rev and revs:
2932 if not rev and revs:
2933 rev = revs[0]
2933 rev = revs[0]
2934 if not rev:
2934 if not rev:
2935 rev = "tip"
2935 rev = "tip"
2936
2936
2937 remoterev = repo.lookup(rev)
2937 remoterev = repo.lookup(rev)
2938 if default or id:
2938 if default or id:
2939 output = [hexfunc(remoterev)]
2939 output = [hexfunc(remoterev)]
2940
2940
2941 def getbms():
2941 def getbms():
2942 bms = []
2942 bms = []
2943
2943
2944 if 'bookmarks' in repo.listkeys('namespaces'):
2944 if 'bookmarks' in repo.listkeys('namespaces'):
2945 hexremoterev = hex(remoterev)
2945 hexremoterev = hex(remoterev)
2946 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
2946 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
2947 if bmr == hexremoterev]
2947 if bmr == hexremoterev]
2948
2948
2949 return bms
2949 return bms
2950
2950
2951 if bookmarks:
2951 if bookmarks:
2952 output.extend(getbms())
2952 output.extend(getbms())
2953 elif default and not ui.quiet:
2953 elif default and not ui.quiet:
2954 # multiple bookmarks for a single parent separated by '/'
2954 # multiple bookmarks for a single parent separated by '/'
2955 bm = '/'.join(getbms())
2955 bm = '/'.join(getbms())
2956 if bm:
2956 if bm:
2957 output.append(bm)
2957 output.append(bm)
2958 else:
2958 else:
2959 if not rev:
2959 if not rev:
2960 ctx = repo[None]
2960 ctx = repo[None]
2961 parents = ctx.parents()
2961 parents = ctx.parents()
2962 changed = ""
2962 changed = ""
2963 if default or id or num:
2963 if default or id or num:
2964 changed = util.any(repo.status()) and "+" or ""
2964 changed = util.any(repo.status()) and "+" or ""
2965 if default or id:
2965 if default or id:
2966 output = ["%s%s" %
2966 output = ["%s%s" %
2967 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2967 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2968 if num:
2968 if num:
2969 output.append("%s%s" %
2969 output.append("%s%s" %
2970 ('+'.join([str(p.rev()) for p in parents]), changed))
2970 ('+'.join([str(p.rev()) for p in parents]), changed))
2971 else:
2971 else:
2972 ctx = scmutil.revsingle(repo, rev)
2972 ctx = scmutil.revsingle(repo, rev)
2973 if default or id:
2973 if default or id:
2974 output = [hexfunc(ctx.node())]
2974 output = [hexfunc(ctx.node())]
2975 if num:
2975 if num:
2976 output.append(str(ctx.rev()))
2976 output.append(str(ctx.rev()))
2977
2977
2978 if default and not ui.quiet:
2978 if default and not ui.quiet:
2979 b = ctx.branch()
2979 b = ctx.branch()
2980 if b != 'default':
2980 if b != 'default':
2981 output.append("(%s)" % b)
2981 output.append("(%s)" % b)
2982
2982
2983 # multiple tags for a single parent separated by '/'
2983 # multiple tags for a single parent separated by '/'
2984 t = '/'.join(ctx.tags())
2984 t = '/'.join(ctx.tags())
2985 if t:
2985 if t:
2986 output.append(t)
2986 output.append(t)
2987
2987
2988 # multiple bookmarks for a single parent separated by '/'
2988 # multiple bookmarks for a single parent separated by '/'
2989 bm = '/'.join(ctx.bookmarks())
2989 bm = '/'.join(ctx.bookmarks())
2990 if bm:
2990 if bm:
2991 output.append(bm)
2991 output.append(bm)
2992 else:
2992 else:
2993 if branch:
2993 if branch:
2994 output.append(ctx.branch())
2994 output.append(ctx.branch())
2995
2995
2996 if tags:
2996 if tags:
2997 output.extend(ctx.tags())
2997 output.extend(ctx.tags())
2998
2998
2999 if bookmarks:
2999 if bookmarks:
3000 output.extend(ctx.bookmarks())
3000 output.extend(ctx.bookmarks())
3001
3001
3002 ui.write("%s\n" % ' '.join(output))
3002 ui.write("%s\n" % ' '.join(output))
3003
3003
3004 @command('import|patch',
3004 @command('import|patch',
3005 [('p', 'strip', 1,
3005 [('p', 'strip', 1,
3006 _('directory strip option for patch. This has the same '
3006 _('directory strip option for patch. This has the same '
3007 'meaning as the corresponding patch option'), _('NUM')),
3007 'meaning as the corresponding patch option'), _('NUM')),
3008 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3008 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3009 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3009 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3010 ('', 'no-commit', None,
3010 ('', 'no-commit', None,
3011 _("don't commit, just update the working directory")),
3011 _("don't commit, just update the working directory")),
3012 ('', 'bypass', None,
3012 ('', 'bypass', None,
3013 _("apply patch without touching the working directory")),
3013 _("apply patch without touching the working directory")),
3014 ('', 'exact', None,
3014 ('', 'exact', None,
3015 _('apply patch to the nodes from which it was generated')),
3015 _('apply patch to the nodes from which it was generated')),
3016 ('', 'import-branch', None,
3016 ('', 'import-branch', None,
3017 _('use any branch information in patch (implied by --exact)'))] +
3017 _('use any branch information in patch (implied by --exact)'))] +
3018 commitopts + commitopts2 + similarityopts,
3018 commitopts + commitopts2 + similarityopts,
3019 _('[OPTION]... PATCH...'))
3019 _('[OPTION]... PATCH...'))
3020 def import_(ui, repo, patch1, *patches, **opts):
3020 def import_(ui, repo, patch1, *patches, **opts):
3021 """import an ordered set of patches
3021 """import an ordered set of patches
3022
3022
3023 Import a list of patches and commit them individually (unless
3023 Import a list of patches and commit them individually (unless
3024 --no-commit is specified).
3024 --no-commit is specified).
3025
3025
3026 If there are outstanding changes in the working directory, import
3026 If there are outstanding changes in the working directory, import
3027 will abort unless given the -f/--force flag.
3027 will abort unless given the -f/--force flag.
3028
3028
3029 You can import a patch straight from a mail message. Even patches
3029 You can import a patch straight from a mail message. Even patches
3030 as attachments work (to use the body part, it must have type
3030 as attachments work (to use the body part, it must have type
3031 text/plain or text/x-patch). From and Subject headers of email
3031 text/plain or text/x-patch). From and Subject headers of email
3032 message are used as default committer and commit message. All
3032 message are used as default committer and commit message. All
3033 text/plain body parts before first diff are added to commit
3033 text/plain body parts before first diff are added to commit
3034 message.
3034 message.
3035
3035
3036 If the imported patch was generated by :hg:`export`, user and
3036 If the imported patch was generated by :hg:`export`, user and
3037 description from patch override values from message headers and
3037 description from patch override values from message headers and
3038 body. Values given on command line with -m/--message and -u/--user
3038 body. Values given on command line with -m/--message and -u/--user
3039 override these.
3039 override these.
3040
3040
3041 If --exact is specified, import will set the working directory to
3041 If --exact is specified, import will set the working directory to
3042 the parent of each patch before applying it, and will abort if the
3042 the parent of each patch before applying it, and will abort if the
3043 resulting changeset has a different ID than the one recorded in
3043 resulting changeset has a different ID than the one recorded in
3044 the patch. This may happen due to character set problems or other
3044 the patch. This may happen due to character set problems or other
3045 deficiencies in the text patch format.
3045 deficiencies in the text patch format.
3046
3046
3047 Use --bypass to apply and commit patches directly to the
3047 Use --bypass to apply and commit patches directly to the
3048 repository, not touching the working directory. Without --exact,
3048 repository, not touching the working directory. Without --exact,
3049 patches will be applied on top of the working directory parent
3049 patches will be applied on top of the working directory parent
3050 revision.
3050 revision.
3051
3051
3052 With -s/--similarity, hg will attempt to discover renames and
3052 With -s/--similarity, hg will attempt to discover renames and
3053 copies in the patch in the same way as 'addremove'.
3053 copies in the patch in the same way as 'addremove'.
3054
3054
3055 To read a patch from standard input, use "-" as the patch name. If
3055 To read a patch from standard input, use "-" as the patch name. If
3056 a URL is specified, the patch will be downloaded from it.
3056 a URL is specified, the patch will be downloaded from it.
3057 See :hg:`help dates` for a list of formats valid for -d/--date.
3057 See :hg:`help dates` for a list of formats valid for -d/--date.
3058
3058
3059 Returns 0 on success.
3059 Returns 0 on success.
3060 """
3060 """
3061 patches = (patch1,) + patches
3061 patches = (patch1,) + patches
3062
3062
3063 date = opts.get('date')
3063 date = opts.get('date')
3064 if date:
3064 if date:
3065 opts['date'] = util.parsedate(date)
3065 opts['date'] = util.parsedate(date)
3066
3066
3067 update = not opts.get('bypass')
3067 update = not opts.get('bypass')
3068 if not update and opts.get('no_commit'):
3068 if not update and opts.get('no_commit'):
3069 raise util.Abort(_('cannot use --no-commit with --bypass'))
3069 raise util.Abort(_('cannot use --no-commit with --bypass'))
3070 try:
3070 try:
3071 sim = float(opts.get('similarity') or 0)
3071 sim = float(opts.get('similarity') or 0)
3072 except ValueError:
3072 except ValueError:
3073 raise util.Abort(_('similarity must be a number'))
3073 raise util.Abort(_('similarity must be a number'))
3074 if sim < 0 or sim > 100:
3074 if sim < 0 or sim > 100:
3075 raise util.Abort(_('similarity must be between 0 and 100'))
3075 raise util.Abort(_('similarity must be between 0 and 100'))
3076 if sim and not update:
3076 if sim and not update:
3077 raise util.Abort(_('cannot use --similarity with --bypass'))
3077 raise util.Abort(_('cannot use --similarity with --bypass'))
3078
3078
3079 if (opts.get('exact') or not opts.get('force')) and update:
3079 if (opts.get('exact') or not opts.get('force')) and update:
3080 cmdutil.bailifchanged(repo)
3080 cmdutil.bailifchanged(repo)
3081
3081
3082 d = opts["base"]
3082 d = opts["base"]
3083 strip = opts["strip"]
3083 strip = opts["strip"]
3084 wlock = lock = None
3084 wlock = lock = None
3085 msgs = []
3085 msgs = []
3086
3086
3087 def checkexact(repo, n, nodeid):
3087 def checkexact(repo, n, nodeid):
3088 if opts.get('exact') and hex(n) != nodeid:
3088 if opts.get('exact') and hex(n) != nodeid:
3089 repo.rollback()
3089 repo.rollback()
3090 raise util.Abort(_('patch is damaged or loses information'))
3090 raise util.Abort(_('patch is damaged or loses information'))
3091
3091
3092 def tryone(ui, hunk, parents):
3092 def tryone(ui, hunk, parents):
3093 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3093 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3094 patch.extract(ui, hunk)
3094 patch.extract(ui, hunk)
3095
3095
3096 if not tmpname:
3096 if not tmpname:
3097 return None
3097 return None
3098 commitid = _('to working directory')
3098 commitid = _('to working directory')
3099
3099
3100 try:
3100 try:
3101 cmdline_message = cmdutil.logmessage(ui, opts)
3101 cmdline_message = cmdutil.logmessage(ui, opts)
3102 if cmdline_message:
3102 if cmdline_message:
3103 # pickup the cmdline msg
3103 # pickup the cmdline msg
3104 message = cmdline_message
3104 message = cmdline_message
3105 elif message:
3105 elif message:
3106 # pickup the patch msg
3106 # pickup the patch msg
3107 message = message.strip()
3107 message = message.strip()
3108 else:
3108 else:
3109 # launch the editor
3109 # launch the editor
3110 message = None
3110 message = None
3111 ui.debug('message:\n%s\n' % message)
3111 ui.debug('message:\n%s\n' % message)
3112
3112
3113 if len(parents) == 1:
3113 if len(parents) == 1:
3114 parents.append(repo[nullid])
3114 parents.append(repo[nullid])
3115 if opts.get('exact'):
3115 if opts.get('exact'):
3116 if not nodeid or not p1:
3116 if not nodeid or not p1:
3117 raise util.Abort(_('not a Mercurial patch'))
3117 raise util.Abort(_('not a Mercurial patch'))
3118 p1 = repo[p1]
3118 p1 = repo[p1]
3119 p2 = repo[p2 or nullid]
3119 p2 = repo[p2 or nullid]
3120 elif p2:
3120 elif p2:
3121 try:
3121 try:
3122 p1 = repo[p1]
3122 p1 = repo[p1]
3123 p2 = repo[p2]
3123 p2 = repo[p2]
3124 except error.RepoError:
3124 except error.RepoError:
3125 p1, p2 = parents
3125 p1, p2 = parents
3126 else:
3126 else:
3127 p1, p2 = parents
3127 p1, p2 = parents
3128
3128
3129 n = None
3129 n = None
3130 if update:
3130 if update:
3131 if opts.get('exact') and p1 != parents[0]:
3131 if opts.get('exact') and p1 != parents[0]:
3132 hg.clean(repo, p1.node())
3132 hg.clean(repo, p1.node())
3133 if p1 != parents[0] and p2 != parents[1]:
3133 if p1 != parents[0] and p2 != parents[1]:
3134 repo.dirstate.setparents(p1.node(), p2.node())
3134 repo.dirstate.setparents(p1.node(), p2.node())
3135
3135
3136 if opts.get('exact') or opts.get('import_branch'):
3136 if opts.get('exact') or opts.get('import_branch'):
3137 repo.dirstate.setbranch(branch or 'default')
3137 repo.dirstate.setbranch(branch or 'default')
3138
3138
3139 files = set()
3139 files = set()
3140 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3140 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3141 eolmode=None, similarity=sim / 100.0)
3141 eolmode=None, similarity=sim / 100.0)
3142 files = list(files)
3142 files = list(files)
3143 if opts.get('no_commit'):
3143 if opts.get('no_commit'):
3144 if message:
3144 if message:
3145 msgs.append(message)
3145 msgs.append(message)
3146 else:
3146 else:
3147 if opts.get('exact'):
3147 if opts.get('exact'):
3148 m = None
3148 m = None
3149 else:
3149 else:
3150 m = scmutil.matchfiles(repo, files or [])
3150 m = scmutil.matchfiles(repo, files or [])
3151 n = repo.commit(message, opts.get('user') or user,
3151 n = repo.commit(message, opts.get('user') or user,
3152 opts.get('date') or date, match=m,
3152 opts.get('date') or date, match=m,
3153 editor=cmdutil.commiteditor)
3153 editor=cmdutil.commiteditor)
3154 checkexact(repo, n, nodeid)
3154 checkexact(repo, n, nodeid)
3155 # Force a dirstate write so that the next transaction
3155 # Force a dirstate write so that the next transaction
3156 # backups an up-to-date file.
3156 # backups an up-to-date file.
3157 repo.dirstate.write()
3157 repo.dirstate.write()
3158 else:
3158 else:
3159 if opts.get('exact') or opts.get('import_branch'):
3159 if opts.get('exact') or opts.get('import_branch'):
3160 branch = branch or 'default'
3160 branch = branch or 'default'
3161 else:
3161 else:
3162 branch = p1.branch()
3162 branch = p1.branch()
3163 store = patch.filestore()
3163 store = patch.filestore()
3164 try:
3164 try:
3165 files = set()
3165 files = set()
3166 try:
3166 try:
3167 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3167 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3168 files, eolmode=None)
3168 files, eolmode=None)
3169 except patch.PatchError, e:
3169 except patch.PatchError, e:
3170 raise util.Abort(str(e))
3170 raise util.Abort(str(e))
3171 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3171 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3172 message,
3172 message,
3173 opts.get('user') or user,
3173 opts.get('user') or user,
3174 opts.get('date') or date,
3174 opts.get('date') or date,
3175 branch, files, store,
3175 branch, files, store,
3176 editor=cmdutil.commiteditor)
3176 editor=cmdutil.commiteditor)
3177 repo.savecommitmessage(memctx.description())
3177 repo.savecommitmessage(memctx.description())
3178 n = memctx.commit()
3178 n = memctx.commit()
3179 checkexact(repo, n, nodeid)
3179 checkexact(repo, n, nodeid)
3180 finally:
3180 finally:
3181 store.close()
3181 store.close()
3182 if n:
3182 if n:
3183 commitid = short(n)
3183 commitid = short(n)
3184 return commitid
3184 return commitid
3185 finally:
3185 finally:
3186 os.unlink(tmpname)
3186 os.unlink(tmpname)
3187
3187
3188 try:
3188 try:
3189 wlock = repo.wlock()
3189 wlock = repo.wlock()
3190 lock = repo.lock()
3190 lock = repo.lock()
3191 parents = repo.parents()
3191 parents = repo.parents()
3192 lastcommit = None
3192 lastcommit = None
3193 for p in patches:
3193 for p in patches:
3194 pf = os.path.join(d, p)
3194 pf = os.path.join(d, p)
3195
3195
3196 if pf == '-':
3196 if pf == '-':
3197 ui.status(_("applying patch from stdin\n"))
3197 ui.status(_("applying patch from stdin\n"))
3198 pf = ui.fin
3198 pf = ui.fin
3199 else:
3199 else:
3200 ui.status(_("applying %s\n") % p)
3200 ui.status(_("applying %s\n") % p)
3201 pf = url.open(ui, pf)
3201 pf = url.open(ui, pf)
3202
3202
3203 haspatch = False
3203 haspatch = False
3204 for hunk in patch.split(pf):
3204 for hunk in patch.split(pf):
3205 commitid = tryone(ui, hunk, parents)
3205 commitid = tryone(ui, hunk, parents)
3206 if commitid:
3206 if commitid:
3207 haspatch = True
3207 haspatch = True
3208 if lastcommit:
3208 if lastcommit:
3209 ui.status(_('applied %s\n') % lastcommit)
3209 ui.status(_('applied %s\n') % lastcommit)
3210 lastcommit = commitid
3210 lastcommit = commitid
3211 if update or opts.get('exact'):
3211 if update or opts.get('exact'):
3212 parents = repo.parents()
3212 parents = repo.parents()
3213 else:
3213 else:
3214 parents = [repo[commitid]]
3214 parents = [repo[commitid]]
3215
3215
3216 if not haspatch:
3216 if not haspatch:
3217 raise util.Abort(_('no diffs found'))
3217 raise util.Abort(_('no diffs found'))
3218
3218
3219 if msgs:
3219 if msgs:
3220 repo.savecommitmessage('\n* * *\n'.join(msgs))
3220 repo.savecommitmessage('\n* * *\n'.join(msgs))
3221 finally:
3221 finally:
3222 release(lock, wlock)
3222 release(lock, wlock)
3223
3223
3224 @command('incoming|in',
3224 @command('incoming|in',
3225 [('f', 'force', None,
3225 [('f', 'force', None,
3226 _('run even if remote repository is unrelated')),
3226 _('run even if remote repository is unrelated')),
3227 ('n', 'newest-first', None, _('show newest record first')),
3227 ('n', 'newest-first', None, _('show newest record first')),
3228 ('', 'bundle', '',
3228 ('', 'bundle', '',
3229 _('file to store the bundles into'), _('FILE')),
3229 _('file to store the bundles into'), _('FILE')),
3230 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3230 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3231 ('B', 'bookmarks', False, _("compare bookmarks")),
3231 ('B', 'bookmarks', False, _("compare bookmarks")),
3232 ('b', 'branch', [],
3232 ('b', 'branch', [],
3233 _('a specific branch you would like to pull'), _('BRANCH')),
3233 _('a specific branch you would like to pull'), _('BRANCH')),
3234 ] + logopts + remoteopts + subrepoopts,
3234 ] + logopts + remoteopts + subrepoopts,
3235 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3235 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3236 def incoming(ui, repo, source="default", **opts):
3236 def incoming(ui, repo, source="default", **opts):
3237 """show new changesets found in source
3237 """show new changesets found in source
3238
3238
3239 Show new changesets found in the specified path/URL or the default
3239 Show new changesets found in the specified path/URL or the default
3240 pull location. These are the changesets that would have been pulled
3240 pull location. These are the changesets that would have been pulled
3241 if a pull at the time you issued this command.
3241 if a pull at the time you issued this command.
3242
3242
3243 For remote repository, using --bundle avoids downloading the
3243 For remote repository, using --bundle avoids downloading the
3244 changesets twice if the incoming is followed by a pull.
3244 changesets twice if the incoming is followed by a pull.
3245
3245
3246 See pull for valid source format details.
3246 See pull for valid source format details.
3247
3247
3248 Returns 0 if there are incoming changes, 1 otherwise.
3248 Returns 0 if there are incoming changes, 1 otherwise.
3249 """
3249 """
3250 if opts.get('bundle') and opts.get('subrepos'):
3250 if opts.get('bundle') and opts.get('subrepos'):
3251 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3251 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3252
3252
3253 if opts.get('bookmarks'):
3253 if opts.get('bookmarks'):
3254 source, branches = hg.parseurl(ui.expandpath(source),
3254 source, branches = hg.parseurl(ui.expandpath(source),
3255 opts.get('branch'))
3255 opts.get('branch'))
3256 other = hg.peer(repo, opts, source)
3256 other = hg.peer(repo, opts, source)
3257 if 'bookmarks' not in other.listkeys('namespaces'):
3257 if 'bookmarks' not in other.listkeys('namespaces'):
3258 ui.warn(_("remote doesn't support bookmarks\n"))
3258 ui.warn(_("remote doesn't support bookmarks\n"))
3259 return 0
3259 return 0
3260 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3260 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3261 return bookmarks.diff(ui, repo, other)
3261 return bookmarks.diff(ui, repo, other)
3262
3262
3263 repo._subtoppath = ui.expandpath(source)
3263 repo._subtoppath = ui.expandpath(source)
3264 try:
3264 try:
3265 return hg.incoming(ui, repo, source, opts)
3265 return hg.incoming(ui, repo, source, opts)
3266 finally:
3266 finally:
3267 del repo._subtoppath
3267 del repo._subtoppath
3268
3268
3269
3269
3270 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3270 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3271 def init(ui, dest=".", **opts):
3271 def init(ui, dest=".", **opts):
3272 """create a new repository in the given directory
3272 """create a new repository in the given directory
3273
3273
3274 Initialize a new repository in the given directory. If the given
3274 Initialize a new repository in the given directory. If the given
3275 directory does not exist, it will be created.
3275 directory does not exist, it will be created.
3276
3276
3277 If no directory is given, the current directory is used.
3277 If no directory is given, the current directory is used.
3278
3278
3279 It is possible to specify an ``ssh://`` URL as the destination.
3279 It is possible to specify an ``ssh://`` URL as the destination.
3280 See :hg:`help urls` for more information.
3280 See :hg:`help urls` for more information.
3281
3281
3282 Returns 0 on success.
3282 Returns 0 on success.
3283 """
3283 """
3284 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3284 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3285
3285
3286 @command('locate',
3286 @command('locate',
3287 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3287 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3288 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3288 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3289 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3289 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3290 ] + walkopts,
3290 ] + walkopts,
3291 _('[OPTION]... [PATTERN]...'))
3291 _('[OPTION]... [PATTERN]...'))
3292 def locate(ui, repo, *pats, **opts):
3292 def locate(ui, repo, *pats, **opts):
3293 """locate files matching specific patterns
3293 """locate files matching specific patterns
3294
3294
3295 Print files under Mercurial control in the working directory whose
3295 Print files under Mercurial control in the working directory whose
3296 names match the given patterns.
3296 names match the given patterns.
3297
3297
3298 By default, this command searches all directories in the working
3298 By default, this command searches all directories in the working
3299 directory. To search just the current directory and its
3299 directory. To search just the current directory and its
3300 subdirectories, use "--include .".
3300 subdirectories, use "--include .".
3301
3301
3302 If no patterns are given to match, this command prints the names
3302 If no patterns are given to match, this command prints the names
3303 of all files under Mercurial control in the working directory.
3303 of all files under Mercurial control in the working directory.
3304
3304
3305 If you want to feed the output of this command into the "xargs"
3305 If you want to feed the output of this command into the "xargs"
3306 command, use the -0 option to both this command and "xargs". This
3306 command, use the -0 option to both this command and "xargs". This
3307 will avoid the problem of "xargs" treating single filenames that
3307 will avoid the problem of "xargs" treating single filenames that
3308 contain whitespace as multiple filenames.
3308 contain whitespace as multiple filenames.
3309
3309
3310 Returns 0 if a match is found, 1 otherwise.
3310 Returns 0 if a match is found, 1 otherwise.
3311 """
3311 """
3312 end = opts.get('print0') and '\0' or '\n'
3312 end = opts.get('print0') and '\0' or '\n'
3313 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3313 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3314
3314
3315 ret = 1
3315 ret = 1
3316 m = scmutil.match(repo, pats, opts, default='relglob')
3316 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3317 m.bad = lambda x, y: False
3317 m.bad = lambda x, y: False
3318 for abs in repo[rev].walk(m):
3318 for abs in repo[rev].walk(m):
3319 if not rev and abs not in repo.dirstate:
3319 if not rev and abs not in repo.dirstate:
3320 continue
3320 continue
3321 if opts.get('fullpath'):
3321 if opts.get('fullpath'):
3322 ui.write(repo.wjoin(abs), end)
3322 ui.write(repo.wjoin(abs), end)
3323 else:
3323 else:
3324 ui.write(((pats and m.rel(abs)) or abs), end)
3324 ui.write(((pats and m.rel(abs)) or abs), end)
3325 ret = 0
3325 ret = 0
3326
3326
3327 return ret
3327 return ret
3328
3328
3329 @command('^log|history',
3329 @command('^log|history',
3330 [('f', 'follow', None,
3330 [('f', 'follow', None,
3331 _('follow changeset history, or file history across copies and renames')),
3331 _('follow changeset history, or file history across copies and renames')),
3332 ('', 'follow-first', None,
3332 ('', 'follow-first', None,
3333 _('only follow the first parent of merge changesets')),
3333 _('only follow the first parent of merge changesets')),
3334 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3334 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3335 ('C', 'copies', None, _('show copied files')),
3335 ('C', 'copies', None, _('show copied files')),
3336 ('k', 'keyword', [],
3336 ('k', 'keyword', [],
3337 _('do case-insensitive search for a given text'), _('TEXT')),
3337 _('do case-insensitive search for a given text'), _('TEXT')),
3338 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3338 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3339 ('', 'removed', None, _('include revisions where files were removed')),
3339 ('', 'removed', None, _('include revisions where files were removed')),
3340 ('m', 'only-merges', None, _('show only merges')),
3340 ('m', 'only-merges', None, _('show only merges')),
3341 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3341 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3342 ('', 'only-branch', [],
3342 ('', 'only-branch', [],
3343 _('show only changesets within the given named branch (DEPRECATED)'),
3343 _('show only changesets within the given named branch (DEPRECATED)'),
3344 _('BRANCH')),
3344 _('BRANCH')),
3345 ('b', 'branch', [],
3345 ('b', 'branch', [],
3346 _('show changesets within the given named branch'), _('BRANCH')),
3346 _('show changesets within the given named branch'), _('BRANCH')),
3347 ('P', 'prune', [],
3347 ('P', 'prune', [],
3348 _('do not display revision or any of its ancestors'), _('REV')),
3348 _('do not display revision or any of its ancestors'), _('REV')),
3349 ('h', 'hidden', False, _('show hidden changesets')),
3349 ('h', 'hidden', False, _('show hidden changesets')),
3350 ] + logopts + walkopts,
3350 ] + logopts + walkopts,
3351 _('[OPTION]... [FILE]'))
3351 _('[OPTION]... [FILE]'))
3352 def log(ui, repo, *pats, **opts):
3352 def log(ui, repo, *pats, **opts):
3353 """show revision history of entire repository or files
3353 """show revision history of entire repository or files
3354
3354
3355 Print the revision history of the specified files or the entire
3355 Print the revision history of the specified files or the entire
3356 project.
3356 project.
3357
3357
3358 File history is shown without following rename or copy history of
3358 File history is shown without following rename or copy history of
3359 files. Use -f/--follow with a filename to follow history across
3359 files. Use -f/--follow with a filename to follow history across
3360 renames and copies. --follow without a filename will only show
3360 renames and copies. --follow without a filename will only show
3361 ancestors or descendants of the starting revision. --follow-first
3361 ancestors or descendants of the starting revision. --follow-first
3362 only follows the first parent of merge revisions.
3362 only follows the first parent of merge revisions.
3363
3363
3364 If no revision range is specified, the default is ``tip:0`` unless
3364 If no revision range is specified, the default is ``tip:0`` unless
3365 --follow is set, in which case the working directory parent is
3365 --follow is set, in which case the working directory parent is
3366 used as the starting revision. You can specify a revision set for
3366 used as the starting revision. You can specify a revision set for
3367 log, see :hg:`help revsets` for more information.
3367 log, see :hg:`help revsets` for more information.
3368
3368
3369 See :hg:`help dates` for a list of formats valid for -d/--date.
3369 See :hg:`help dates` for a list of formats valid for -d/--date.
3370
3370
3371 By default this command prints revision number and changeset id,
3371 By default this command prints revision number and changeset id,
3372 tags, non-trivial parents, user, date and time, and a summary for
3372 tags, non-trivial parents, user, date and time, and a summary for
3373 each commit. When the -v/--verbose switch is used, the list of
3373 each commit. When the -v/--verbose switch is used, the list of
3374 changed files and full commit message are shown.
3374 changed files and full commit message are shown.
3375
3375
3376 .. note::
3376 .. note::
3377 log -p/--patch may generate unexpected diff output for merge
3377 log -p/--patch may generate unexpected diff output for merge
3378 changesets, as it will only compare the merge changeset against
3378 changesets, as it will only compare the merge changeset against
3379 its first parent. Also, only files different from BOTH parents
3379 its first parent. Also, only files different from BOTH parents
3380 will appear in files:.
3380 will appear in files:.
3381
3381
3382 Returns 0 on success.
3382 Returns 0 on success.
3383 """
3383 """
3384
3384
3385 matchfn = scmutil.match(repo, pats, opts)
3385 matchfn = scmutil.match(repo[None], pats, opts)
3386 limit = cmdutil.loglimit(opts)
3386 limit = cmdutil.loglimit(opts)
3387 count = 0
3387 count = 0
3388
3388
3389 endrev = None
3389 endrev = None
3390 if opts.get('copies') and opts.get('rev'):
3390 if opts.get('copies') and opts.get('rev'):
3391 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
3391 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
3392
3392
3393 df = False
3393 df = False
3394 if opts["date"]:
3394 if opts["date"]:
3395 df = util.matchdate(opts["date"])
3395 df = util.matchdate(opts["date"])
3396
3396
3397 branches = opts.get('branch', []) + opts.get('only_branch', [])
3397 branches = opts.get('branch', []) + opts.get('only_branch', [])
3398 opts['branch'] = [repo.lookupbranch(b) for b in branches]
3398 opts['branch'] = [repo.lookupbranch(b) for b in branches]
3399
3399
3400 displayer = cmdutil.show_changeset(ui, repo, opts, True)
3400 displayer = cmdutil.show_changeset(ui, repo, opts, True)
3401 def prep(ctx, fns):
3401 def prep(ctx, fns):
3402 rev = ctx.rev()
3402 rev = ctx.rev()
3403 parents = [p for p in repo.changelog.parentrevs(rev)
3403 parents = [p for p in repo.changelog.parentrevs(rev)
3404 if p != nullrev]
3404 if p != nullrev]
3405 if opts.get('no_merges') and len(parents) == 2:
3405 if opts.get('no_merges') and len(parents) == 2:
3406 return
3406 return
3407 if opts.get('only_merges') and len(parents) != 2:
3407 if opts.get('only_merges') and len(parents) != 2:
3408 return
3408 return
3409 if opts.get('branch') and ctx.branch() not in opts['branch']:
3409 if opts.get('branch') and ctx.branch() not in opts['branch']:
3410 return
3410 return
3411 if not opts.get('hidden') and ctx.hidden():
3411 if not opts.get('hidden') and ctx.hidden():
3412 return
3412 return
3413 if df and not df(ctx.date()[0]):
3413 if df and not df(ctx.date()[0]):
3414 return
3414 return
3415 if opts['user'] and not [k for k in opts['user']
3415 if opts['user'] and not [k for k in opts['user']
3416 if k.lower() in ctx.user().lower()]:
3416 if k.lower() in ctx.user().lower()]:
3417 return
3417 return
3418 if opts.get('keyword'):
3418 if opts.get('keyword'):
3419 for k in [kw.lower() for kw in opts['keyword']]:
3419 for k in [kw.lower() for kw in opts['keyword']]:
3420 if (k in ctx.user().lower() or
3420 if (k in ctx.user().lower() or
3421 k in ctx.description().lower() or
3421 k in ctx.description().lower() or
3422 k in " ".join(ctx.files()).lower()):
3422 k in " ".join(ctx.files()).lower()):
3423 break
3423 break
3424 else:
3424 else:
3425 return
3425 return
3426
3426
3427 copies = None
3427 copies = None
3428 if opts.get('copies') and rev:
3428 if opts.get('copies') and rev:
3429 copies = []
3429 copies = []
3430 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3430 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3431 for fn in ctx.files():
3431 for fn in ctx.files():
3432 rename = getrenamed(fn, rev)
3432 rename = getrenamed(fn, rev)
3433 if rename:
3433 if rename:
3434 copies.append((fn, rename[0]))
3434 copies.append((fn, rename[0]))
3435
3435
3436 revmatchfn = None
3436 revmatchfn = None
3437 if opts.get('patch') or opts.get('stat'):
3437 if opts.get('patch') or opts.get('stat'):
3438 if opts.get('follow') or opts.get('follow_first'):
3438 if opts.get('follow') or opts.get('follow_first'):
3439 # note: this might be wrong when following through merges
3439 # note: this might be wrong when following through merges
3440 revmatchfn = scmutil.match(repo, fns, default='path')
3440 revmatchfn = scmutil.match(repo[None], fns, default='path')
3441 else:
3441 else:
3442 revmatchfn = matchfn
3442 revmatchfn = matchfn
3443
3443
3444 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3444 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3445
3445
3446 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3446 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3447 if count == limit:
3447 if count == limit:
3448 break
3448 break
3449 if displayer.flush(ctx.rev()):
3449 if displayer.flush(ctx.rev()):
3450 count += 1
3450 count += 1
3451 displayer.close()
3451 displayer.close()
3452
3452
3453 @command('manifest',
3453 @command('manifest',
3454 [('r', 'rev', '', _('revision to display'), _('REV')),
3454 [('r', 'rev', '', _('revision to display'), _('REV')),
3455 ('', 'all', False, _("list files from all revisions"))],
3455 ('', 'all', False, _("list files from all revisions"))],
3456 _('[-r REV]'))
3456 _('[-r REV]'))
3457 def manifest(ui, repo, node=None, rev=None, **opts):
3457 def manifest(ui, repo, node=None, rev=None, **opts):
3458 """output the current or given revision of the project manifest
3458 """output the current or given revision of the project manifest
3459
3459
3460 Print a list of version controlled files for the given revision.
3460 Print a list of version controlled files for the given revision.
3461 If no revision is given, the first parent of the working directory
3461 If no revision is given, the first parent of the working directory
3462 is used, or the null revision if no revision is checked out.
3462 is used, or the null revision if no revision is checked out.
3463
3463
3464 With -v, print file permissions, symlink and executable bits.
3464 With -v, print file permissions, symlink and executable bits.
3465 With --debug, print file revision hashes.
3465 With --debug, print file revision hashes.
3466
3466
3467 If option --all is specified, the list of all files from all revisions
3467 If option --all is specified, the list of all files from all revisions
3468 is printed. This includes deleted and renamed files.
3468 is printed. This includes deleted and renamed files.
3469
3469
3470 Returns 0 on success.
3470 Returns 0 on success.
3471 """
3471 """
3472 if opts.get('all'):
3472 if opts.get('all'):
3473 if rev or node:
3473 if rev or node:
3474 raise util.Abort(_("can't specify a revision with --all"))
3474 raise util.Abort(_("can't specify a revision with --all"))
3475
3475
3476 res = []
3476 res = []
3477 prefix = "data/"
3477 prefix = "data/"
3478 suffix = ".i"
3478 suffix = ".i"
3479 plen = len(prefix)
3479 plen = len(prefix)
3480 slen = len(suffix)
3480 slen = len(suffix)
3481 lock = repo.lock()
3481 lock = repo.lock()
3482 try:
3482 try:
3483 for fn, b, size in repo.store.datafiles():
3483 for fn, b, size in repo.store.datafiles():
3484 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3484 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3485 res.append(fn[plen:-slen])
3485 res.append(fn[plen:-slen])
3486 finally:
3486 finally:
3487 lock.release()
3487 lock.release()
3488 for f in sorted(res):
3488 for f in sorted(res):
3489 ui.write("%s\n" % f)
3489 ui.write("%s\n" % f)
3490 return
3490 return
3491
3491
3492 if rev and node:
3492 if rev and node:
3493 raise util.Abort(_("please specify just one revision"))
3493 raise util.Abort(_("please specify just one revision"))
3494
3494
3495 if not node:
3495 if not node:
3496 node = rev
3496 node = rev
3497
3497
3498 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
3498 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
3499 ctx = scmutil.revsingle(repo, node)
3499 ctx = scmutil.revsingle(repo, node)
3500 for f in ctx:
3500 for f in ctx:
3501 if ui.debugflag:
3501 if ui.debugflag:
3502 ui.write("%40s " % hex(ctx.manifest()[f]))
3502 ui.write("%40s " % hex(ctx.manifest()[f]))
3503 if ui.verbose:
3503 if ui.verbose:
3504 ui.write(decor[ctx.flags(f)])
3504 ui.write(decor[ctx.flags(f)])
3505 ui.write("%s\n" % f)
3505 ui.write("%s\n" % f)
3506
3506
3507 @command('^merge',
3507 @command('^merge',
3508 [('f', 'force', None, _('force a merge with outstanding changes')),
3508 [('f', 'force', None, _('force a merge with outstanding changes')),
3509 ('t', 'tool', '', _('specify merge tool')),
3509 ('t', 'tool', '', _('specify merge tool')),
3510 ('r', 'rev', '', _('revision to merge'), _('REV')),
3510 ('r', 'rev', '', _('revision to merge'), _('REV')),
3511 ('P', 'preview', None,
3511 ('P', 'preview', None,
3512 _('review revisions to merge (no merge is performed)'))],
3512 _('review revisions to merge (no merge is performed)'))],
3513 _('[-P] [-f] [[-r] REV]'))
3513 _('[-P] [-f] [[-r] REV]'))
3514 def merge(ui, repo, node=None, **opts):
3514 def merge(ui, repo, node=None, **opts):
3515 """merge working directory with another revision
3515 """merge working directory with another revision
3516
3516
3517 The current working directory is updated with all changes made in
3517 The current working directory is updated with all changes made in
3518 the requested revision since the last common predecessor revision.
3518 the requested revision since the last common predecessor revision.
3519
3519
3520 Files that changed between either parent are marked as changed for
3520 Files that changed between either parent are marked as changed for
3521 the next commit and a commit must be performed before any further
3521 the next commit and a commit must be performed before any further
3522 updates to the repository are allowed. The next commit will have
3522 updates to the repository are allowed. The next commit will have
3523 two parents.
3523 two parents.
3524
3524
3525 ``--tool`` can be used to specify the merge tool used for file
3525 ``--tool`` can be used to specify the merge tool used for file
3526 merges. It overrides the HGMERGE environment variable and your
3526 merges. It overrides the HGMERGE environment variable and your
3527 configuration files. See :hg:`help merge-tools` for options.
3527 configuration files. See :hg:`help merge-tools` for options.
3528
3528
3529 If no revision is specified, the working directory's parent is a
3529 If no revision is specified, the working directory's parent is a
3530 head revision, and the current branch contains exactly one other
3530 head revision, and the current branch contains exactly one other
3531 head, the other head is merged with by default. Otherwise, an
3531 head, the other head is merged with by default. Otherwise, an
3532 explicit revision with which to merge with must be provided.
3532 explicit revision with which to merge with must be provided.
3533
3533
3534 :hg:`resolve` must be used to resolve unresolved files.
3534 :hg:`resolve` must be used to resolve unresolved files.
3535
3535
3536 To undo an uncommitted merge, use :hg:`update --clean .` which
3536 To undo an uncommitted merge, use :hg:`update --clean .` which
3537 will check out a clean copy of the original merge parent, losing
3537 will check out a clean copy of the original merge parent, losing
3538 all changes.
3538 all changes.
3539
3539
3540 Returns 0 on success, 1 if there are unresolved files.
3540 Returns 0 on success, 1 if there are unresolved files.
3541 """
3541 """
3542
3542
3543 if opts.get('rev') and node:
3543 if opts.get('rev') and node:
3544 raise util.Abort(_("please specify just one revision"))
3544 raise util.Abort(_("please specify just one revision"))
3545 if not node:
3545 if not node:
3546 node = opts.get('rev')
3546 node = opts.get('rev')
3547
3547
3548 if not node:
3548 if not node:
3549 branch = repo[None].branch()
3549 branch = repo[None].branch()
3550 bheads = repo.branchheads(branch)
3550 bheads = repo.branchheads(branch)
3551 if len(bheads) > 2:
3551 if len(bheads) > 2:
3552 raise util.Abort(_("branch '%s' has %d heads - "
3552 raise util.Abort(_("branch '%s' has %d heads - "
3553 "please merge with an explicit rev")
3553 "please merge with an explicit rev")
3554 % (branch, len(bheads)),
3554 % (branch, len(bheads)),
3555 hint=_("run 'hg heads .' to see heads"))
3555 hint=_("run 'hg heads .' to see heads"))
3556
3556
3557 parent = repo.dirstate.p1()
3557 parent = repo.dirstate.p1()
3558 if len(bheads) == 1:
3558 if len(bheads) == 1:
3559 if len(repo.heads()) > 1:
3559 if len(repo.heads()) > 1:
3560 raise util.Abort(_("branch '%s' has one head - "
3560 raise util.Abort(_("branch '%s' has one head - "
3561 "please merge with an explicit rev")
3561 "please merge with an explicit rev")
3562 % branch,
3562 % branch,
3563 hint=_("run 'hg heads' to see all heads"))
3563 hint=_("run 'hg heads' to see all heads"))
3564 msg = _('there is nothing to merge')
3564 msg = _('there is nothing to merge')
3565 if parent != repo.lookup(repo[None].branch()):
3565 if parent != repo.lookup(repo[None].branch()):
3566 msg = _('%s - use "hg update" instead') % msg
3566 msg = _('%s - use "hg update" instead') % msg
3567 raise util.Abort(msg)
3567 raise util.Abort(msg)
3568
3568
3569 if parent not in bheads:
3569 if parent not in bheads:
3570 raise util.Abort(_('working directory not at a head revision'),
3570 raise util.Abort(_('working directory not at a head revision'),
3571 hint=_("use 'hg update' or merge with an "
3571 hint=_("use 'hg update' or merge with an "
3572 "explicit revision"))
3572 "explicit revision"))
3573 node = parent == bheads[0] and bheads[-1] or bheads[0]
3573 node = parent == bheads[0] and bheads[-1] or bheads[0]
3574 else:
3574 else:
3575 node = scmutil.revsingle(repo, node).node()
3575 node = scmutil.revsingle(repo, node).node()
3576
3576
3577 if opts.get('preview'):
3577 if opts.get('preview'):
3578 # find nodes that are ancestors of p2 but not of p1
3578 # find nodes that are ancestors of p2 but not of p1
3579 p1 = repo.lookup('.')
3579 p1 = repo.lookup('.')
3580 p2 = repo.lookup(node)
3580 p2 = repo.lookup(node)
3581 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3581 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3582
3582
3583 displayer = cmdutil.show_changeset(ui, repo, opts)
3583 displayer = cmdutil.show_changeset(ui, repo, opts)
3584 for node in nodes:
3584 for node in nodes:
3585 displayer.show(repo[node])
3585 displayer.show(repo[node])
3586 displayer.close()
3586 displayer.close()
3587 return 0
3587 return 0
3588
3588
3589 try:
3589 try:
3590 # ui.forcemerge is an internal variable, do not document
3590 # ui.forcemerge is an internal variable, do not document
3591 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3591 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3592 return hg.merge(repo, node, force=opts.get('force'))
3592 return hg.merge(repo, node, force=opts.get('force'))
3593 finally:
3593 finally:
3594 ui.setconfig('ui', 'forcemerge', '')
3594 ui.setconfig('ui', 'forcemerge', '')
3595
3595
3596 @command('outgoing|out',
3596 @command('outgoing|out',
3597 [('f', 'force', None, _('run even when the destination is unrelated')),
3597 [('f', 'force', None, _('run even when the destination is unrelated')),
3598 ('r', 'rev', [],
3598 ('r', 'rev', [],
3599 _('a changeset intended to be included in the destination'), _('REV')),
3599 _('a changeset intended to be included in the destination'), _('REV')),
3600 ('n', 'newest-first', None, _('show newest record first')),
3600 ('n', 'newest-first', None, _('show newest record first')),
3601 ('B', 'bookmarks', False, _('compare bookmarks')),
3601 ('B', 'bookmarks', False, _('compare bookmarks')),
3602 ('b', 'branch', [], _('a specific branch you would like to push'),
3602 ('b', 'branch', [], _('a specific branch you would like to push'),
3603 _('BRANCH')),
3603 _('BRANCH')),
3604 ] + logopts + remoteopts + subrepoopts,
3604 ] + logopts + remoteopts + subrepoopts,
3605 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3605 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3606 def outgoing(ui, repo, dest=None, **opts):
3606 def outgoing(ui, repo, dest=None, **opts):
3607 """show changesets not found in the destination
3607 """show changesets not found in the destination
3608
3608
3609 Show changesets not found in the specified destination repository
3609 Show changesets not found in the specified destination repository
3610 or the default push location. These are the changesets that would
3610 or the default push location. These are the changesets that would
3611 be pushed if a push was requested.
3611 be pushed if a push was requested.
3612
3612
3613 See pull for details of valid destination formats.
3613 See pull for details of valid destination formats.
3614
3614
3615 Returns 0 if there are outgoing changes, 1 otherwise.
3615 Returns 0 if there are outgoing changes, 1 otherwise.
3616 """
3616 """
3617
3617
3618 if opts.get('bookmarks'):
3618 if opts.get('bookmarks'):
3619 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3619 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3620 dest, branches = hg.parseurl(dest, opts.get('branch'))
3620 dest, branches = hg.parseurl(dest, opts.get('branch'))
3621 other = hg.peer(repo, opts, dest)
3621 other = hg.peer(repo, opts, dest)
3622 if 'bookmarks' not in other.listkeys('namespaces'):
3622 if 'bookmarks' not in other.listkeys('namespaces'):
3623 ui.warn(_("remote doesn't support bookmarks\n"))
3623 ui.warn(_("remote doesn't support bookmarks\n"))
3624 return 0
3624 return 0
3625 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3625 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3626 return bookmarks.diff(ui, other, repo)
3626 return bookmarks.diff(ui, other, repo)
3627
3627
3628 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3628 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3629 try:
3629 try:
3630 return hg.outgoing(ui, repo, dest, opts)
3630 return hg.outgoing(ui, repo, dest, opts)
3631 finally:
3631 finally:
3632 del repo._subtoppath
3632 del repo._subtoppath
3633
3633
3634 @command('parents',
3634 @command('parents',
3635 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3635 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3636 ] + templateopts,
3636 ] + templateopts,
3637 _('[-r REV] [FILE]'))
3637 _('[-r REV] [FILE]'))
3638 def parents(ui, repo, file_=None, **opts):
3638 def parents(ui, repo, file_=None, **opts):
3639 """show the parents of the working directory or revision
3639 """show the parents of the working directory or revision
3640
3640
3641 Print the working directory's parent revisions. If a revision is
3641 Print the working directory's parent revisions. If a revision is
3642 given via -r/--rev, the parent of that revision will be printed.
3642 given via -r/--rev, the parent of that revision will be printed.
3643 If a file argument is given, the revision in which the file was
3643 If a file argument is given, the revision in which the file was
3644 last changed (before the working directory revision or the
3644 last changed (before the working directory revision or the
3645 argument to --rev if given) is printed.
3645 argument to --rev if given) is printed.
3646
3646
3647 Returns 0 on success.
3647 Returns 0 on success.
3648 """
3648 """
3649
3649
3650 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3650 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3651
3651
3652 if file_:
3652 if file_:
3653 m = scmutil.match(repo, (file_,), opts)
3653 m = scmutil.match(ctx, (file_,), opts)
3654 if m.anypats() or len(m.files()) != 1:
3654 if m.anypats() or len(m.files()) != 1:
3655 raise util.Abort(_('can only specify an explicit filename'))
3655 raise util.Abort(_('can only specify an explicit filename'))
3656 file_ = m.files()[0]
3656 file_ = m.files()[0]
3657 filenodes = []
3657 filenodes = []
3658 for cp in ctx.parents():
3658 for cp in ctx.parents():
3659 if not cp:
3659 if not cp:
3660 continue
3660 continue
3661 try:
3661 try:
3662 filenodes.append(cp.filenode(file_))
3662 filenodes.append(cp.filenode(file_))
3663 except error.LookupError:
3663 except error.LookupError:
3664 pass
3664 pass
3665 if not filenodes:
3665 if not filenodes:
3666 raise util.Abort(_("'%s' not found in manifest!") % file_)
3666 raise util.Abort(_("'%s' not found in manifest!") % file_)
3667 fl = repo.file(file_)
3667 fl = repo.file(file_)
3668 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
3668 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
3669 else:
3669 else:
3670 p = [cp.node() for cp in ctx.parents()]
3670 p = [cp.node() for cp in ctx.parents()]
3671
3671
3672 displayer = cmdutil.show_changeset(ui, repo, opts)
3672 displayer = cmdutil.show_changeset(ui, repo, opts)
3673 for n in p:
3673 for n in p:
3674 if n != nullid:
3674 if n != nullid:
3675 displayer.show(repo[n])
3675 displayer.show(repo[n])
3676 displayer.close()
3676 displayer.close()
3677
3677
3678 @command('paths', [], _('[NAME]'))
3678 @command('paths', [], _('[NAME]'))
3679 def paths(ui, repo, search=None):
3679 def paths(ui, repo, search=None):
3680 """show aliases for remote repositories
3680 """show aliases for remote repositories
3681
3681
3682 Show definition of symbolic path name NAME. If no name is given,
3682 Show definition of symbolic path name NAME. If no name is given,
3683 show definition of all available names.
3683 show definition of all available names.
3684
3684
3685 Option -q/--quiet suppresses all output when searching for NAME
3685 Option -q/--quiet suppresses all output when searching for NAME
3686 and shows only the path names when listing all definitions.
3686 and shows only the path names when listing all definitions.
3687
3687
3688 Path names are defined in the [paths] section of your
3688 Path names are defined in the [paths] section of your
3689 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3689 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3690 repository, ``.hg/hgrc`` is used, too.
3690 repository, ``.hg/hgrc`` is used, too.
3691
3691
3692 The path names ``default`` and ``default-push`` have a special
3692 The path names ``default`` and ``default-push`` have a special
3693 meaning. When performing a push or pull operation, they are used
3693 meaning. When performing a push or pull operation, they are used
3694 as fallbacks if no location is specified on the command-line.
3694 as fallbacks if no location is specified on the command-line.
3695 When ``default-push`` is set, it will be used for push and
3695 When ``default-push`` is set, it will be used for push and
3696 ``default`` will be used for pull; otherwise ``default`` is used
3696 ``default`` will be used for pull; otherwise ``default`` is used
3697 as the fallback for both. When cloning a repository, the clone
3697 as the fallback for both. When cloning a repository, the clone
3698 source is written as ``default`` in ``.hg/hgrc``. Note that
3698 source is written as ``default`` in ``.hg/hgrc``. Note that
3699 ``default`` and ``default-push`` apply to all inbound (e.g.
3699 ``default`` and ``default-push`` apply to all inbound (e.g.
3700 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
3700 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
3701 :hg:`bundle`) operations.
3701 :hg:`bundle`) operations.
3702
3702
3703 See :hg:`help urls` for more information.
3703 See :hg:`help urls` for more information.
3704
3704
3705 Returns 0 on success.
3705 Returns 0 on success.
3706 """
3706 """
3707 if search:
3707 if search:
3708 for name, path in ui.configitems("paths"):
3708 for name, path in ui.configitems("paths"):
3709 if name == search:
3709 if name == search:
3710 ui.status("%s\n" % util.hidepassword(path))
3710 ui.status("%s\n" % util.hidepassword(path))
3711 return
3711 return
3712 if not ui.quiet:
3712 if not ui.quiet:
3713 ui.warn(_("not found!\n"))
3713 ui.warn(_("not found!\n"))
3714 return 1
3714 return 1
3715 else:
3715 else:
3716 for name, path in ui.configitems("paths"):
3716 for name, path in ui.configitems("paths"):
3717 if ui.quiet:
3717 if ui.quiet:
3718 ui.write("%s\n" % name)
3718 ui.write("%s\n" % name)
3719 else:
3719 else:
3720 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
3720 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
3721
3721
3722 def postincoming(ui, repo, modheads, optupdate, checkout):
3722 def postincoming(ui, repo, modheads, optupdate, checkout):
3723 if modheads == 0:
3723 if modheads == 0:
3724 return
3724 return
3725 if optupdate:
3725 if optupdate:
3726 try:
3726 try:
3727 return hg.update(repo, checkout)
3727 return hg.update(repo, checkout)
3728 except util.Abort, inst:
3728 except util.Abort, inst:
3729 ui.warn(_("not updating: %s\n" % str(inst)))
3729 ui.warn(_("not updating: %s\n" % str(inst)))
3730 return 0
3730 return 0
3731 if modheads > 1:
3731 if modheads > 1:
3732 currentbranchheads = len(repo.branchheads())
3732 currentbranchheads = len(repo.branchheads())
3733 if currentbranchheads == modheads:
3733 if currentbranchheads == modheads:
3734 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3734 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3735 elif currentbranchheads > 1:
3735 elif currentbranchheads > 1:
3736 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
3736 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
3737 else:
3737 else:
3738 ui.status(_("(run 'hg heads' to see heads)\n"))
3738 ui.status(_("(run 'hg heads' to see heads)\n"))
3739 else:
3739 else:
3740 ui.status(_("(run 'hg update' to get a working copy)\n"))
3740 ui.status(_("(run 'hg update' to get a working copy)\n"))
3741
3741
3742 @command('^pull',
3742 @command('^pull',
3743 [('u', 'update', None,
3743 [('u', 'update', None,
3744 _('update to new branch head if changesets were pulled')),
3744 _('update to new branch head if changesets were pulled')),
3745 ('f', 'force', None, _('run even when remote repository is unrelated')),
3745 ('f', 'force', None, _('run even when remote repository is unrelated')),
3746 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3746 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3747 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3747 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3748 ('b', 'branch', [], _('a specific branch you would like to pull'),
3748 ('b', 'branch', [], _('a specific branch you would like to pull'),
3749 _('BRANCH')),
3749 _('BRANCH')),
3750 ] + remoteopts,
3750 ] + remoteopts,
3751 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3751 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3752 def pull(ui, repo, source="default", **opts):
3752 def pull(ui, repo, source="default", **opts):
3753 """pull changes from the specified source
3753 """pull changes from the specified source
3754
3754
3755 Pull changes from a remote repository to a local one.
3755 Pull changes from a remote repository to a local one.
3756
3756
3757 This finds all changes from the repository at the specified path
3757 This finds all changes from the repository at the specified path
3758 or URL and adds them to a local repository (the current one unless
3758 or URL and adds them to a local repository (the current one unless
3759 -R is specified). By default, this does not update the copy of the
3759 -R is specified). By default, this does not update the copy of the
3760 project in the working directory.
3760 project in the working directory.
3761
3761
3762 Use :hg:`incoming` if you want to see what would have been added
3762 Use :hg:`incoming` if you want to see what would have been added
3763 by a pull at the time you issued this command. If you then decide
3763 by a pull at the time you issued this command. If you then decide
3764 to add those changes to the repository, you should use :hg:`pull
3764 to add those changes to the repository, you should use :hg:`pull
3765 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3765 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3766
3766
3767 If SOURCE is omitted, the 'default' path will be used.
3767 If SOURCE is omitted, the 'default' path will be used.
3768 See :hg:`help urls` for more information.
3768 See :hg:`help urls` for more information.
3769
3769
3770 Returns 0 on success, 1 if an update had unresolved files.
3770 Returns 0 on success, 1 if an update had unresolved files.
3771 """
3771 """
3772 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3772 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3773 other = hg.peer(repo, opts, source)
3773 other = hg.peer(repo, opts, source)
3774 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3774 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3775 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3775 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3776
3776
3777 if opts.get('bookmark'):
3777 if opts.get('bookmark'):
3778 if not revs:
3778 if not revs:
3779 revs = []
3779 revs = []
3780 rb = other.listkeys('bookmarks')
3780 rb = other.listkeys('bookmarks')
3781 for b in opts['bookmark']:
3781 for b in opts['bookmark']:
3782 if b not in rb:
3782 if b not in rb:
3783 raise util.Abort(_('remote bookmark %s not found!') % b)
3783 raise util.Abort(_('remote bookmark %s not found!') % b)
3784 revs.append(rb[b])
3784 revs.append(rb[b])
3785
3785
3786 if revs:
3786 if revs:
3787 try:
3787 try:
3788 revs = [other.lookup(rev) for rev in revs]
3788 revs = [other.lookup(rev) for rev in revs]
3789 except error.CapabilityError:
3789 except error.CapabilityError:
3790 err = _("other repository doesn't support revision lookup, "
3790 err = _("other repository doesn't support revision lookup, "
3791 "so a rev cannot be specified.")
3791 "so a rev cannot be specified.")
3792 raise util.Abort(err)
3792 raise util.Abort(err)
3793
3793
3794 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
3794 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
3795 bookmarks.updatefromremote(ui, repo, other)
3795 bookmarks.updatefromremote(ui, repo, other)
3796 if checkout:
3796 if checkout:
3797 checkout = str(repo.changelog.rev(other.lookup(checkout)))
3797 checkout = str(repo.changelog.rev(other.lookup(checkout)))
3798 repo._subtoppath = source
3798 repo._subtoppath = source
3799 try:
3799 try:
3800 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
3800 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
3801
3801
3802 finally:
3802 finally:
3803 del repo._subtoppath
3803 del repo._subtoppath
3804
3804
3805 # update specified bookmarks
3805 # update specified bookmarks
3806 if opts.get('bookmark'):
3806 if opts.get('bookmark'):
3807 for b in opts['bookmark']:
3807 for b in opts['bookmark']:
3808 # explicit pull overrides local bookmark if any
3808 # explicit pull overrides local bookmark if any
3809 ui.status(_("importing bookmark %s\n") % b)
3809 ui.status(_("importing bookmark %s\n") % b)
3810 repo._bookmarks[b] = repo[rb[b]].node()
3810 repo._bookmarks[b] = repo[rb[b]].node()
3811 bookmarks.write(repo)
3811 bookmarks.write(repo)
3812
3812
3813 return ret
3813 return ret
3814
3814
3815 @command('^push',
3815 @command('^push',
3816 [('f', 'force', None, _('force push')),
3816 [('f', 'force', None, _('force push')),
3817 ('r', 'rev', [],
3817 ('r', 'rev', [],
3818 _('a changeset intended to be included in the destination'),
3818 _('a changeset intended to be included in the destination'),
3819 _('REV')),
3819 _('REV')),
3820 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
3820 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
3821 ('b', 'branch', [],
3821 ('b', 'branch', [],
3822 _('a specific branch you would like to push'), _('BRANCH')),
3822 _('a specific branch you would like to push'), _('BRANCH')),
3823 ('', 'new-branch', False, _('allow pushing a new branch')),
3823 ('', 'new-branch', False, _('allow pushing a new branch')),
3824 ] + remoteopts,
3824 ] + remoteopts,
3825 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
3825 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
3826 def push(ui, repo, dest=None, **opts):
3826 def push(ui, repo, dest=None, **opts):
3827 """push changes to the specified destination
3827 """push changes to the specified destination
3828
3828
3829 Push changesets from the local repository to the specified
3829 Push changesets from the local repository to the specified
3830 destination.
3830 destination.
3831
3831
3832 This operation is symmetrical to pull: it is identical to a pull
3832 This operation is symmetrical to pull: it is identical to a pull
3833 in the destination repository from the current one.
3833 in the destination repository from the current one.
3834
3834
3835 By default, push will not allow creation of new heads at the
3835 By default, push will not allow creation of new heads at the
3836 destination, since multiple heads would make it unclear which head
3836 destination, since multiple heads would make it unclear which head
3837 to use. In this situation, it is recommended to pull and merge
3837 to use. In this situation, it is recommended to pull and merge
3838 before pushing.
3838 before pushing.
3839
3839
3840 Use --new-branch if you want to allow push to create a new named
3840 Use --new-branch if you want to allow push to create a new named
3841 branch that is not present at the destination. This allows you to
3841 branch that is not present at the destination. This allows you to
3842 only create a new branch without forcing other changes.
3842 only create a new branch without forcing other changes.
3843
3843
3844 Use -f/--force to override the default behavior and push all
3844 Use -f/--force to override the default behavior and push all
3845 changesets on all branches.
3845 changesets on all branches.
3846
3846
3847 If -r/--rev is used, the specified revision and all its ancestors
3847 If -r/--rev is used, the specified revision and all its ancestors
3848 will be pushed to the remote repository.
3848 will be pushed to the remote repository.
3849
3849
3850 Please see :hg:`help urls` for important details about ``ssh://``
3850 Please see :hg:`help urls` for important details about ``ssh://``
3851 URLs. If DESTINATION is omitted, a default path will be used.
3851 URLs. If DESTINATION is omitted, a default path will be used.
3852
3852
3853 Returns 0 if push was successful, 1 if nothing to push.
3853 Returns 0 if push was successful, 1 if nothing to push.
3854 """
3854 """
3855
3855
3856 if opts.get('bookmark'):
3856 if opts.get('bookmark'):
3857 for b in opts['bookmark']:
3857 for b in opts['bookmark']:
3858 # translate -B options to -r so changesets get pushed
3858 # translate -B options to -r so changesets get pushed
3859 if b in repo._bookmarks:
3859 if b in repo._bookmarks:
3860 opts.setdefault('rev', []).append(b)
3860 opts.setdefault('rev', []).append(b)
3861 else:
3861 else:
3862 # if we try to push a deleted bookmark, translate it to null
3862 # if we try to push a deleted bookmark, translate it to null
3863 # this lets simultaneous -r, -b options continue working
3863 # this lets simultaneous -r, -b options continue working
3864 opts.setdefault('rev', []).append("null")
3864 opts.setdefault('rev', []).append("null")
3865
3865
3866 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3866 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3867 dest, branches = hg.parseurl(dest, opts.get('branch'))
3867 dest, branches = hg.parseurl(dest, opts.get('branch'))
3868 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
3868 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
3869 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
3869 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
3870 other = hg.peer(repo, opts, dest)
3870 other = hg.peer(repo, opts, dest)
3871 if revs:
3871 if revs:
3872 revs = [repo.lookup(rev) for rev in revs]
3872 revs = [repo.lookup(rev) for rev in revs]
3873
3873
3874 repo._subtoppath = dest
3874 repo._subtoppath = dest
3875 try:
3875 try:
3876 # push subrepos depth-first for coherent ordering
3876 # push subrepos depth-first for coherent ordering
3877 c = repo['']
3877 c = repo['']
3878 subs = c.substate # only repos that are committed
3878 subs = c.substate # only repos that are committed
3879 for s in sorted(subs):
3879 for s in sorted(subs):
3880 if not c.sub(s).push(opts.get('force')):
3880 if not c.sub(s).push(opts.get('force')):
3881 return False
3881 return False
3882 finally:
3882 finally:
3883 del repo._subtoppath
3883 del repo._subtoppath
3884 result = repo.push(other, opts.get('force'), revs=revs,
3884 result = repo.push(other, opts.get('force'), revs=revs,
3885 newbranch=opts.get('new_branch'))
3885 newbranch=opts.get('new_branch'))
3886
3886
3887 result = (result == 0)
3887 result = (result == 0)
3888
3888
3889 if opts.get('bookmark'):
3889 if opts.get('bookmark'):
3890 rb = other.listkeys('bookmarks')
3890 rb = other.listkeys('bookmarks')
3891 for b in opts['bookmark']:
3891 for b in opts['bookmark']:
3892 # explicit push overrides remote bookmark if any
3892 # explicit push overrides remote bookmark if any
3893 if b in repo._bookmarks:
3893 if b in repo._bookmarks:
3894 ui.status(_("exporting bookmark %s\n") % b)
3894 ui.status(_("exporting bookmark %s\n") % b)
3895 new = repo[b].hex()
3895 new = repo[b].hex()
3896 elif b in rb:
3896 elif b in rb:
3897 ui.status(_("deleting remote bookmark %s\n") % b)
3897 ui.status(_("deleting remote bookmark %s\n") % b)
3898 new = '' # delete
3898 new = '' # delete
3899 else:
3899 else:
3900 ui.warn(_('bookmark %s does not exist on the local '
3900 ui.warn(_('bookmark %s does not exist on the local '
3901 'or remote repository!\n') % b)
3901 'or remote repository!\n') % b)
3902 return 2
3902 return 2
3903 old = rb.get(b, '')
3903 old = rb.get(b, '')
3904 r = other.pushkey('bookmarks', b, old, new)
3904 r = other.pushkey('bookmarks', b, old, new)
3905 if not r:
3905 if not r:
3906 ui.warn(_('updating bookmark %s failed!\n') % b)
3906 ui.warn(_('updating bookmark %s failed!\n') % b)
3907 if not result:
3907 if not result:
3908 result = 2
3908 result = 2
3909
3909
3910 return result
3910 return result
3911
3911
3912 @command('recover', [])
3912 @command('recover', [])
3913 def recover(ui, repo):
3913 def recover(ui, repo):
3914 """roll back an interrupted transaction
3914 """roll back an interrupted transaction
3915
3915
3916 Recover from an interrupted commit or pull.
3916 Recover from an interrupted commit or pull.
3917
3917
3918 This command tries to fix the repository status after an
3918 This command tries to fix the repository status after an
3919 interrupted operation. It should only be necessary when Mercurial
3919 interrupted operation. It should only be necessary when Mercurial
3920 suggests it.
3920 suggests it.
3921
3921
3922 Returns 0 if successful, 1 if nothing to recover or verify fails.
3922 Returns 0 if successful, 1 if nothing to recover or verify fails.
3923 """
3923 """
3924 if repo.recover():
3924 if repo.recover():
3925 return hg.verify(repo)
3925 return hg.verify(repo)
3926 return 1
3926 return 1
3927
3927
3928 @command('^remove|rm',
3928 @command('^remove|rm',
3929 [('A', 'after', None, _('record delete for missing files')),
3929 [('A', 'after', None, _('record delete for missing files')),
3930 ('f', 'force', None,
3930 ('f', 'force', None,
3931 _('remove (and delete) file even if added or modified')),
3931 _('remove (and delete) file even if added or modified')),
3932 ] + walkopts,
3932 ] + walkopts,
3933 _('[OPTION]... FILE...'))
3933 _('[OPTION]... FILE...'))
3934 def remove(ui, repo, *pats, **opts):
3934 def remove(ui, repo, *pats, **opts):
3935 """remove the specified files on the next commit
3935 """remove the specified files on the next commit
3936
3936
3937 Schedule the indicated files for removal from the repository.
3937 Schedule the indicated files for removal from the repository.
3938
3938
3939 This only removes files from the current branch, not from the
3939 This only removes files from the current branch, not from the
3940 entire project history. -A/--after can be used to remove only
3940 entire project history. -A/--after can be used to remove only
3941 files that have already been deleted, -f/--force can be used to
3941 files that have already been deleted, -f/--force can be used to
3942 force deletion, and -Af can be used to remove files from the next
3942 force deletion, and -Af can be used to remove files from the next
3943 revision without deleting them from the working directory.
3943 revision without deleting them from the working directory.
3944
3944
3945 The following table details the behavior of remove for different
3945 The following table details the behavior of remove for different
3946 file states (columns) and option combinations (rows). The file
3946 file states (columns) and option combinations (rows). The file
3947 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3947 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3948 reported by :hg:`status`). The actions are Warn, Remove (from
3948 reported by :hg:`status`). The actions are Warn, Remove (from
3949 branch) and Delete (from disk)::
3949 branch) and Delete (from disk)::
3950
3950
3951 A C M !
3951 A C M !
3952 none W RD W R
3952 none W RD W R
3953 -f R RD RD R
3953 -f R RD RD R
3954 -A W W W R
3954 -A W W W R
3955 -Af R R R R
3955 -Af R R R R
3956
3956
3957 Note that remove never deletes files in Added [A] state from the
3957 Note that remove never deletes files in Added [A] state from the
3958 working directory, not even if option --force is specified.
3958 working directory, not even if option --force is specified.
3959
3959
3960 This command schedules the files to be removed at the next commit.
3960 This command schedules the files to be removed at the next commit.
3961 To undo a remove before that, see :hg:`revert`.
3961 To undo a remove before that, see :hg:`revert`.
3962
3962
3963 Returns 0 on success, 1 if any warnings encountered.
3963 Returns 0 on success, 1 if any warnings encountered.
3964 """
3964 """
3965
3965
3966 ret = 0
3966 ret = 0
3967 after, force = opts.get('after'), opts.get('force')
3967 after, force = opts.get('after'), opts.get('force')
3968 if not pats and not after:
3968 if not pats and not after:
3969 raise util.Abort(_('no files specified'))
3969 raise util.Abort(_('no files specified'))
3970
3970
3971 m = scmutil.match(repo, pats, opts)
3971 m = scmutil.match(repo[None], pats, opts)
3972 s = repo.status(match=m, clean=True)
3972 s = repo.status(match=m, clean=True)
3973 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3973 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3974
3974
3975 for f in m.files():
3975 for f in m.files():
3976 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3976 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3977 if os.path.exists(m.rel(f)):
3977 if os.path.exists(m.rel(f)):
3978 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3978 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3979 ret = 1
3979 ret = 1
3980
3980
3981 if force:
3981 if force:
3982 list = modified + deleted + clean + added
3982 list = modified + deleted + clean + added
3983 elif after:
3983 elif after:
3984 list = deleted
3984 list = deleted
3985 for f in modified + added + clean:
3985 for f in modified + added + clean:
3986 ui.warn(_('not removing %s: file still exists (use -f'
3986 ui.warn(_('not removing %s: file still exists (use -f'
3987 ' to force removal)\n') % m.rel(f))
3987 ' to force removal)\n') % m.rel(f))
3988 ret = 1
3988 ret = 1
3989 else:
3989 else:
3990 list = deleted + clean
3990 list = deleted + clean
3991 for f in modified:
3991 for f in modified:
3992 ui.warn(_('not removing %s: file is modified (use -f'
3992 ui.warn(_('not removing %s: file is modified (use -f'
3993 ' to force removal)\n') % m.rel(f))
3993 ' to force removal)\n') % m.rel(f))
3994 ret = 1
3994 ret = 1
3995 for f in added:
3995 for f in added:
3996 ui.warn(_('not removing %s: file has been marked for add (use -f'
3996 ui.warn(_('not removing %s: file has been marked for add (use -f'
3997 ' to force removal)\n') % m.rel(f))
3997 ' to force removal)\n') % m.rel(f))
3998 ret = 1
3998 ret = 1
3999
3999
4000 for f in sorted(list):
4000 for f in sorted(list):
4001 if ui.verbose or not m.exact(f):
4001 if ui.verbose or not m.exact(f):
4002 ui.status(_('removing %s\n') % m.rel(f))
4002 ui.status(_('removing %s\n') % m.rel(f))
4003
4003
4004 wlock = repo.wlock()
4004 wlock = repo.wlock()
4005 try:
4005 try:
4006 if not after:
4006 if not after:
4007 for f in list:
4007 for f in list:
4008 if f in added:
4008 if f in added:
4009 continue # we never unlink added files on remove
4009 continue # we never unlink added files on remove
4010 try:
4010 try:
4011 util.unlinkpath(repo.wjoin(f))
4011 util.unlinkpath(repo.wjoin(f))
4012 except OSError, inst:
4012 except OSError, inst:
4013 if inst.errno != errno.ENOENT:
4013 if inst.errno != errno.ENOENT:
4014 raise
4014 raise
4015 repo[None].forget(list)
4015 repo[None].forget(list)
4016 finally:
4016 finally:
4017 wlock.release()
4017 wlock.release()
4018
4018
4019 return ret
4019 return ret
4020
4020
4021 @command('rename|move|mv',
4021 @command('rename|move|mv',
4022 [('A', 'after', None, _('record a rename that has already occurred')),
4022 [('A', 'after', None, _('record a rename that has already occurred')),
4023 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4023 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4024 ] + walkopts + dryrunopts,
4024 ] + walkopts + dryrunopts,
4025 _('[OPTION]... SOURCE... DEST'))
4025 _('[OPTION]... SOURCE... DEST'))
4026 def rename(ui, repo, *pats, **opts):
4026 def rename(ui, repo, *pats, **opts):
4027 """rename files; equivalent of copy + remove
4027 """rename files; equivalent of copy + remove
4028
4028
4029 Mark dest as copies of sources; mark sources for deletion. If dest
4029 Mark dest as copies of sources; mark sources for deletion. If dest
4030 is a directory, copies are put in that directory. If dest is a
4030 is a directory, copies are put in that directory. If dest is a
4031 file, there can only be one source.
4031 file, there can only be one source.
4032
4032
4033 By default, this command copies the contents of files as they
4033 By default, this command copies the contents of files as they
4034 exist in the working directory. If invoked with -A/--after, the
4034 exist in the working directory. If invoked with -A/--after, the
4035 operation is recorded, but no copying is performed.
4035 operation is recorded, but no copying is performed.
4036
4036
4037 This command takes effect at the next commit. To undo a rename
4037 This command takes effect at the next commit. To undo a rename
4038 before that, see :hg:`revert`.
4038 before that, see :hg:`revert`.
4039
4039
4040 Returns 0 on success, 1 if errors are encountered.
4040 Returns 0 on success, 1 if errors are encountered.
4041 """
4041 """
4042 wlock = repo.wlock(False)
4042 wlock = repo.wlock(False)
4043 try:
4043 try:
4044 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4044 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4045 finally:
4045 finally:
4046 wlock.release()
4046 wlock.release()
4047
4047
4048 @command('resolve',
4048 @command('resolve',
4049 [('a', 'all', None, _('select all unresolved files')),
4049 [('a', 'all', None, _('select all unresolved files')),
4050 ('l', 'list', None, _('list state of files needing merge')),
4050 ('l', 'list', None, _('list state of files needing merge')),
4051 ('m', 'mark', None, _('mark files as resolved')),
4051 ('m', 'mark', None, _('mark files as resolved')),
4052 ('u', 'unmark', None, _('mark files as unresolved')),
4052 ('u', 'unmark', None, _('mark files as unresolved')),
4053 ('t', 'tool', '', _('specify merge tool')),
4053 ('t', 'tool', '', _('specify merge tool')),
4054 ('n', 'no-status', None, _('hide status prefix'))]
4054 ('n', 'no-status', None, _('hide status prefix'))]
4055 + walkopts,
4055 + walkopts,
4056 _('[OPTION]... [FILE]...'))
4056 _('[OPTION]... [FILE]...'))
4057 def resolve(ui, repo, *pats, **opts):
4057 def resolve(ui, repo, *pats, **opts):
4058 """redo merges or set/view the merge status of files
4058 """redo merges or set/view the merge status of files
4059
4059
4060 Merges with unresolved conflicts are often the result of
4060 Merges with unresolved conflicts are often the result of
4061 non-interactive merging using the ``internal:merge`` configuration
4061 non-interactive merging using the ``internal:merge`` configuration
4062 setting, or a command-line merge tool like ``diff3``. The resolve
4062 setting, or a command-line merge tool like ``diff3``. The resolve
4063 command is used to manage the files involved in a merge, after
4063 command is used to manage the files involved in a merge, after
4064 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4064 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4065 working directory must have two parents).
4065 working directory must have two parents).
4066
4066
4067 The resolve command can be used in the following ways:
4067 The resolve command can be used in the following ways:
4068
4068
4069 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4069 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4070 files, discarding any previous merge attempts. Re-merging is not
4070 files, discarding any previous merge attempts. Re-merging is not
4071 performed for files already marked as resolved. Use ``--all/-a``
4071 performed for files already marked as resolved. Use ``--all/-a``
4072 to selects all unresolved files. ``--tool`` can be used to specify
4072 to selects all unresolved files. ``--tool`` can be used to specify
4073 the merge tool used for the given files. It overrides the HGMERGE
4073 the merge tool used for the given files. It overrides the HGMERGE
4074 environment variable and your configuration files.
4074 environment variable and your configuration files.
4075
4075
4076 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4076 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4077 (e.g. after having manually fixed-up the files). The default is
4077 (e.g. after having manually fixed-up the files). The default is
4078 to mark all unresolved files.
4078 to mark all unresolved files.
4079
4079
4080 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4080 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4081 default is to mark all resolved files.
4081 default is to mark all resolved files.
4082
4082
4083 - :hg:`resolve -l`: list files which had or still have conflicts.
4083 - :hg:`resolve -l`: list files which had or still have conflicts.
4084 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4084 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4085
4085
4086 Note that Mercurial will not let you commit files with unresolved
4086 Note that Mercurial will not let you commit files with unresolved
4087 merge conflicts. You must use :hg:`resolve -m ...` before you can
4087 merge conflicts. You must use :hg:`resolve -m ...` before you can
4088 commit after a conflicting merge.
4088 commit after a conflicting merge.
4089
4089
4090 Returns 0 on success, 1 if any files fail a resolve attempt.
4090 Returns 0 on success, 1 if any files fail a resolve attempt.
4091 """
4091 """
4092
4092
4093 all, mark, unmark, show, nostatus = \
4093 all, mark, unmark, show, nostatus = \
4094 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4094 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4095
4095
4096 if (show and (mark or unmark)) or (mark and unmark):
4096 if (show and (mark or unmark)) or (mark and unmark):
4097 raise util.Abort(_("too many options specified"))
4097 raise util.Abort(_("too many options specified"))
4098 if pats and all:
4098 if pats and all:
4099 raise util.Abort(_("can't specify --all and patterns"))
4099 raise util.Abort(_("can't specify --all and patterns"))
4100 if not (all or pats or show or mark or unmark):
4100 if not (all or pats or show or mark or unmark):
4101 raise util.Abort(_('no files or directories specified; '
4101 raise util.Abort(_('no files or directories specified; '
4102 'use --all to remerge all files'))
4102 'use --all to remerge all files'))
4103
4103
4104 ms = mergemod.mergestate(repo)
4104 ms = mergemod.mergestate(repo)
4105 m = scmutil.match(repo, pats, opts)
4105 m = scmutil.match(repo[None], pats, opts)
4106 ret = 0
4106 ret = 0
4107
4107
4108 for f in ms:
4108 for f in ms:
4109 if m(f):
4109 if m(f):
4110 if show:
4110 if show:
4111 if nostatus:
4111 if nostatus:
4112 ui.write("%s\n" % f)
4112 ui.write("%s\n" % f)
4113 else:
4113 else:
4114 ui.write("%s %s\n" % (ms[f].upper(), f),
4114 ui.write("%s %s\n" % (ms[f].upper(), f),
4115 label='resolve.' +
4115 label='resolve.' +
4116 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4116 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4117 elif mark:
4117 elif mark:
4118 ms.mark(f, "r")
4118 ms.mark(f, "r")
4119 elif unmark:
4119 elif unmark:
4120 ms.mark(f, "u")
4120 ms.mark(f, "u")
4121 else:
4121 else:
4122 wctx = repo[None]
4122 wctx = repo[None]
4123 mctx = wctx.parents()[-1]
4123 mctx = wctx.parents()[-1]
4124
4124
4125 # backup pre-resolve (merge uses .orig for its own purposes)
4125 # backup pre-resolve (merge uses .orig for its own purposes)
4126 a = repo.wjoin(f)
4126 a = repo.wjoin(f)
4127 util.copyfile(a, a + ".resolve")
4127 util.copyfile(a, a + ".resolve")
4128
4128
4129 try:
4129 try:
4130 # resolve file
4130 # resolve file
4131 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4131 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4132 if ms.resolve(f, wctx, mctx):
4132 if ms.resolve(f, wctx, mctx):
4133 ret = 1
4133 ret = 1
4134 finally:
4134 finally:
4135 ui.setconfig('ui', 'forcemerge', '')
4135 ui.setconfig('ui', 'forcemerge', '')
4136
4136
4137 # replace filemerge's .orig file with our resolve file
4137 # replace filemerge's .orig file with our resolve file
4138 util.rename(a + ".resolve", a + ".orig")
4138 util.rename(a + ".resolve", a + ".orig")
4139
4139
4140 ms.commit()
4140 ms.commit()
4141 return ret
4141 return ret
4142
4142
4143 @command('revert',
4143 @command('revert',
4144 [('a', 'all', None, _('revert all changes when no arguments given')),
4144 [('a', 'all', None, _('revert all changes when no arguments given')),
4145 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4145 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4146 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4146 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4147 ('', 'no-backup', None, _('do not save backup copies of files')),
4147 ('', 'no-backup', None, _('do not save backup copies of files')),
4148 ] + walkopts + dryrunopts,
4148 ] + walkopts + dryrunopts,
4149 _('[OPTION]... [-r REV] [NAME]...'))
4149 _('[OPTION]... [-r REV] [NAME]...'))
4150 def revert(ui, repo, *pats, **opts):
4150 def revert(ui, repo, *pats, **opts):
4151 """restore files to their checkout state
4151 """restore files to their checkout state
4152
4152
4153 .. note::
4153 .. note::
4154 To check out earlier revisions, you should use :hg:`update REV`.
4154 To check out earlier revisions, you should use :hg:`update REV`.
4155 To cancel a merge (and lose your changes), use :hg:`update --clean .`.
4155 To cancel a merge (and lose your changes), use :hg:`update --clean .`.
4156
4156
4157 With no revision specified, revert the specified files or directories
4157 With no revision specified, revert the specified files or directories
4158 to the state they had in the first parent of the working directory.
4158 to the state they had in the first parent of the working directory.
4159 This restores the contents of files to an unmodified
4159 This restores the contents of files to an unmodified
4160 state and unschedules adds, removes, copies, and renames.
4160 state and unschedules adds, removes, copies, and renames.
4161
4161
4162 Using the -r/--rev or -d/--date options, revert the given files or
4162 Using the -r/--rev or -d/--date options, revert the given files or
4163 directories to their states as of a specific revision. Because
4163 directories to their states as of a specific revision. Because
4164 revert does not change the working directory parents, this will
4164 revert does not change the working directory parents, this will
4165 cause these files to appear modified. This can be helpful to "back
4165 cause these files to appear modified. This can be helpful to "back
4166 out" some or all of an earlier change. See :hg:`backout` for a
4166 out" some or all of an earlier change. See :hg:`backout` for a
4167 related method.
4167 related method.
4168
4168
4169 Modified files are saved with a .orig suffix before reverting.
4169 Modified files are saved with a .orig suffix before reverting.
4170 To disable these backups, use --no-backup.
4170 To disable these backups, use --no-backup.
4171
4171
4172 See :hg:`help dates` for a list of formats valid for -d/--date.
4172 See :hg:`help dates` for a list of formats valid for -d/--date.
4173
4173
4174 Returns 0 on success.
4174 Returns 0 on success.
4175 """
4175 """
4176
4176
4177 if opts.get("date"):
4177 if opts.get("date"):
4178 if opts.get("rev"):
4178 if opts.get("rev"):
4179 raise util.Abort(_("you can't specify a revision and a date"))
4179 raise util.Abort(_("you can't specify a revision and a date"))
4180 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4180 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4181
4181
4182 parent, p2 = repo.dirstate.parents()
4182 parent, p2 = repo.dirstate.parents()
4183
4183
4184 if not pats and not opts.get('all'):
4184 if not pats and not opts.get('all'):
4185 raise util.Abort(_('no files or directories specified'),
4185 raise util.Abort(_('no files or directories specified'),
4186 hint=_('use --all to revert all files'))
4186 hint=_('use --all to revert all files'))
4187
4187
4188 ctx = scmutil.revsingle(repo, opts.get('rev'))
4188 ctx = scmutil.revsingle(repo, opts.get('rev'))
4189 node = ctx.node()
4189 node = ctx.node()
4190 mf = ctx.manifest()
4190 mf = ctx.manifest()
4191 if node == parent:
4191 if node == parent:
4192 pmf = mf
4192 pmf = mf
4193 else:
4193 else:
4194 pmf = None
4194 pmf = None
4195
4195
4196 # need all matching names in dirstate and manifest of target rev,
4196 # need all matching names in dirstate and manifest of target rev,
4197 # so have to walk both. do not print errors if files exist in one
4197 # so have to walk both. do not print errors if files exist in one
4198 # but not other.
4198 # but not other.
4199
4199
4200 names = {}
4200 names = {}
4201
4201
4202 wlock = repo.wlock()
4202 wlock = repo.wlock()
4203 try:
4203 try:
4204 # walk dirstate.
4204 # walk dirstate.
4205
4205
4206 m = scmutil.match(repo, pats, opts)
4206 m = scmutil.match(repo[None], pats, opts)
4207 m.bad = lambda x, y: False
4207 m.bad = lambda x, y: False
4208 for abs in repo.walk(m):
4208 for abs in repo.walk(m):
4209 names[abs] = m.rel(abs), m.exact(abs)
4209 names[abs] = m.rel(abs), m.exact(abs)
4210
4210
4211 # walk target manifest.
4211 # walk target manifest.
4212
4212
4213 def badfn(path, msg):
4213 def badfn(path, msg):
4214 if path in names:
4214 if path in names:
4215 return
4215 return
4216 path_ = path + '/'
4216 path_ = path + '/'
4217 for f in names:
4217 for f in names:
4218 if f.startswith(path_):
4218 if f.startswith(path_):
4219 return
4219 return
4220 ui.warn("%s: %s\n" % (m.rel(path), msg))
4220 ui.warn("%s: %s\n" % (m.rel(path), msg))
4221
4221
4222 m = scmutil.match(repo, pats, opts)
4222 m = scmutil.match(repo[node], pats, opts)
4223 m.bad = badfn
4223 m.bad = badfn
4224 for abs in repo[node].walk(m):
4224 for abs in repo[node].walk(m):
4225 if abs not in names:
4225 if abs not in names:
4226 names[abs] = m.rel(abs), m.exact(abs)
4226 names[abs] = m.rel(abs), m.exact(abs)
4227
4227
4228 m = scmutil.matchfiles(repo, names)
4228 m = scmutil.matchfiles(repo, names)
4229 changes = repo.status(match=m)[:4]
4229 changes = repo.status(match=m)[:4]
4230 modified, added, removed, deleted = map(set, changes)
4230 modified, added, removed, deleted = map(set, changes)
4231
4231
4232 # if f is a rename, also revert the source
4232 # if f is a rename, also revert the source
4233 cwd = repo.getcwd()
4233 cwd = repo.getcwd()
4234 for f in added:
4234 for f in added:
4235 src = repo.dirstate.copied(f)
4235 src = repo.dirstate.copied(f)
4236 if src and src not in names and repo.dirstate[src] == 'r':
4236 if src and src not in names and repo.dirstate[src] == 'r':
4237 removed.add(src)
4237 removed.add(src)
4238 names[src] = (repo.pathto(src, cwd), True)
4238 names[src] = (repo.pathto(src, cwd), True)
4239
4239
4240 def removeforget(abs):
4240 def removeforget(abs):
4241 if repo.dirstate[abs] == 'a':
4241 if repo.dirstate[abs] == 'a':
4242 return _('forgetting %s\n')
4242 return _('forgetting %s\n')
4243 return _('removing %s\n')
4243 return _('removing %s\n')
4244
4244
4245 revert = ([], _('reverting %s\n'))
4245 revert = ([], _('reverting %s\n'))
4246 add = ([], _('adding %s\n'))
4246 add = ([], _('adding %s\n'))
4247 remove = ([], removeforget)
4247 remove = ([], removeforget)
4248 undelete = ([], _('undeleting %s\n'))
4248 undelete = ([], _('undeleting %s\n'))
4249
4249
4250 disptable = (
4250 disptable = (
4251 # dispatch table:
4251 # dispatch table:
4252 # file state
4252 # file state
4253 # action if in target manifest
4253 # action if in target manifest
4254 # action if not in target manifest
4254 # action if not in target manifest
4255 # make backup if in target manifest
4255 # make backup if in target manifest
4256 # make backup if not in target manifest
4256 # make backup if not in target manifest
4257 (modified, revert, remove, True, True),
4257 (modified, revert, remove, True, True),
4258 (added, revert, remove, True, False),
4258 (added, revert, remove, True, False),
4259 (removed, undelete, None, False, False),
4259 (removed, undelete, None, False, False),
4260 (deleted, revert, remove, False, False),
4260 (deleted, revert, remove, False, False),
4261 )
4261 )
4262
4262
4263 for abs, (rel, exact) in sorted(names.items()):
4263 for abs, (rel, exact) in sorted(names.items()):
4264 mfentry = mf.get(abs)
4264 mfentry = mf.get(abs)
4265 target = repo.wjoin(abs)
4265 target = repo.wjoin(abs)
4266 def handle(xlist, dobackup):
4266 def handle(xlist, dobackup):
4267 xlist[0].append(abs)
4267 xlist[0].append(abs)
4268 if (dobackup and not opts.get('no_backup') and
4268 if (dobackup and not opts.get('no_backup') and
4269 os.path.lexists(target)):
4269 os.path.lexists(target)):
4270 bakname = "%s.orig" % rel
4270 bakname = "%s.orig" % rel
4271 ui.note(_('saving current version of %s as %s\n') %
4271 ui.note(_('saving current version of %s as %s\n') %
4272 (rel, bakname))
4272 (rel, bakname))
4273 if not opts.get('dry_run'):
4273 if not opts.get('dry_run'):
4274 util.rename(target, bakname)
4274 util.rename(target, bakname)
4275 if ui.verbose or not exact:
4275 if ui.verbose or not exact:
4276 msg = xlist[1]
4276 msg = xlist[1]
4277 if not isinstance(msg, basestring):
4277 if not isinstance(msg, basestring):
4278 msg = msg(abs)
4278 msg = msg(abs)
4279 ui.status(msg % rel)
4279 ui.status(msg % rel)
4280 for table, hitlist, misslist, backuphit, backupmiss in disptable:
4280 for table, hitlist, misslist, backuphit, backupmiss in disptable:
4281 if abs not in table:
4281 if abs not in table:
4282 continue
4282 continue
4283 # file has changed in dirstate
4283 # file has changed in dirstate
4284 if mfentry:
4284 if mfentry:
4285 handle(hitlist, backuphit)
4285 handle(hitlist, backuphit)
4286 elif misslist is not None:
4286 elif misslist is not None:
4287 handle(misslist, backupmiss)
4287 handle(misslist, backupmiss)
4288 break
4288 break
4289 else:
4289 else:
4290 if abs not in repo.dirstate:
4290 if abs not in repo.dirstate:
4291 if mfentry:
4291 if mfentry:
4292 handle(add, True)
4292 handle(add, True)
4293 elif exact:
4293 elif exact:
4294 ui.warn(_('file not managed: %s\n') % rel)
4294 ui.warn(_('file not managed: %s\n') % rel)
4295 continue
4295 continue
4296 # file has not changed in dirstate
4296 # file has not changed in dirstate
4297 if node == parent:
4297 if node == parent:
4298 if exact:
4298 if exact:
4299 ui.warn(_('no changes needed to %s\n') % rel)
4299 ui.warn(_('no changes needed to %s\n') % rel)
4300 continue
4300 continue
4301 if pmf is None:
4301 if pmf is None:
4302 # only need parent manifest in this unlikely case,
4302 # only need parent manifest in this unlikely case,
4303 # so do not read by default
4303 # so do not read by default
4304 pmf = repo[parent].manifest()
4304 pmf = repo[parent].manifest()
4305 if abs in pmf:
4305 if abs in pmf:
4306 if mfentry:
4306 if mfentry:
4307 # if version of file is same in parent and target
4307 # if version of file is same in parent and target
4308 # manifests, do nothing
4308 # manifests, do nothing
4309 if (pmf[abs] != mfentry or
4309 if (pmf[abs] != mfentry or
4310 pmf.flags(abs) != mf.flags(abs)):
4310 pmf.flags(abs) != mf.flags(abs)):
4311 handle(revert, False)
4311 handle(revert, False)
4312 else:
4312 else:
4313 handle(remove, False)
4313 handle(remove, False)
4314
4314
4315 if not opts.get('dry_run'):
4315 if not opts.get('dry_run'):
4316 def checkout(f):
4316 def checkout(f):
4317 fc = ctx[f]
4317 fc = ctx[f]
4318 repo.wwrite(f, fc.data(), fc.flags())
4318 repo.wwrite(f, fc.data(), fc.flags())
4319
4319
4320 audit_path = scmutil.pathauditor(repo.root)
4320 audit_path = scmutil.pathauditor(repo.root)
4321 for f in remove[0]:
4321 for f in remove[0]:
4322 if repo.dirstate[f] == 'a':
4322 if repo.dirstate[f] == 'a':
4323 repo.dirstate.drop(f)
4323 repo.dirstate.drop(f)
4324 continue
4324 continue
4325 audit_path(f)
4325 audit_path(f)
4326 try:
4326 try:
4327 util.unlinkpath(repo.wjoin(f))
4327 util.unlinkpath(repo.wjoin(f))
4328 except OSError:
4328 except OSError:
4329 pass
4329 pass
4330 repo.dirstate.remove(f)
4330 repo.dirstate.remove(f)
4331
4331
4332 normal = None
4332 normal = None
4333 if node == parent:
4333 if node == parent:
4334 # We're reverting to our parent. If possible, we'd like status
4334 # We're reverting to our parent. If possible, we'd like status
4335 # to report the file as clean. We have to use normallookup for
4335 # to report the file as clean. We have to use normallookup for
4336 # merges to avoid losing information about merged/dirty files.
4336 # merges to avoid losing information about merged/dirty files.
4337 if p2 != nullid:
4337 if p2 != nullid:
4338 normal = repo.dirstate.normallookup
4338 normal = repo.dirstate.normallookup
4339 else:
4339 else:
4340 normal = repo.dirstate.normal
4340 normal = repo.dirstate.normal
4341 for f in revert[0]:
4341 for f in revert[0]:
4342 checkout(f)
4342 checkout(f)
4343 if normal:
4343 if normal:
4344 normal(f)
4344 normal(f)
4345
4345
4346 for f in add[0]:
4346 for f in add[0]:
4347 checkout(f)
4347 checkout(f)
4348 repo.dirstate.add(f)
4348 repo.dirstate.add(f)
4349
4349
4350 normal = repo.dirstate.normallookup
4350 normal = repo.dirstate.normallookup
4351 if node == parent and p2 == nullid:
4351 if node == parent and p2 == nullid:
4352 normal = repo.dirstate.normal
4352 normal = repo.dirstate.normal
4353 for f in undelete[0]:
4353 for f in undelete[0]:
4354 checkout(f)
4354 checkout(f)
4355 normal(f)
4355 normal(f)
4356
4356
4357 finally:
4357 finally:
4358 wlock.release()
4358 wlock.release()
4359
4359
4360 @command('rollback', dryrunopts)
4360 @command('rollback', dryrunopts)
4361 def rollback(ui, repo, **opts):
4361 def rollback(ui, repo, **opts):
4362 """roll back the last transaction (dangerous)
4362 """roll back the last transaction (dangerous)
4363
4363
4364 This command should be used with care. There is only one level of
4364 This command should be used with care. There is only one level of
4365 rollback, and there is no way to undo a rollback. It will also
4365 rollback, and there is no way to undo a rollback. It will also
4366 restore the dirstate at the time of the last transaction, losing
4366 restore the dirstate at the time of the last transaction, losing
4367 any dirstate changes since that time. This command does not alter
4367 any dirstate changes since that time. This command does not alter
4368 the working directory.
4368 the working directory.
4369
4369
4370 Transactions are used to encapsulate the effects of all commands
4370 Transactions are used to encapsulate the effects of all commands
4371 that create new changesets or propagate existing changesets into a
4371 that create new changesets or propagate existing changesets into a
4372 repository. For example, the following commands are transactional,
4372 repository. For example, the following commands are transactional,
4373 and their effects can be rolled back:
4373 and their effects can be rolled back:
4374
4374
4375 - commit
4375 - commit
4376 - import
4376 - import
4377 - pull
4377 - pull
4378 - push (with this repository as the destination)
4378 - push (with this repository as the destination)
4379 - unbundle
4379 - unbundle
4380
4380
4381 This command is not intended for use on public repositories. Once
4381 This command is not intended for use on public repositories. Once
4382 changes are visible for pull by other users, rolling a transaction
4382 changes are visible for pull by other users, rolling a transaction
4383 back locally is ineffective (someone else may already have pulled
4383 back locally is ineffective (someone else may already have pulled
4384 the changes). Furthermore, a race is possible with readers of the
4384 the changes). Furthermore, a race is possible with readers of the
4385 repository; for example an in-progress pull from the repository
4385 repository; for example an in-progress pull from the repository
4386 may fail if a rollback is performed.
4386 may fail if a rollback is performed.
4387
4387
4388 Returns 0 on success, 1 if no rollback data is available.
4388 Returns 0 on success, 1 if no rollback data is available.
4389 """
4389 """
4390 return repo.rollback(opts.get('dry_run'))
4390 return repo.rollback(opts.get('dry_run'))
4391
4391
4392 @command('root', [])
4392 @command('root', [])
4393 def root(ui, repo):
4393 def root(ui, repo):
4394 """print the root (top) of the current working directory
4394 """print the root (top) of the current working directory
4395
4395
4396 Print the root directory of the current repository.
4396 Print the root directory of the current repository.
4397
4397
4398 Returns 0 on success.
4398 Returns 0 on success.
4399 """
4399 """
4400 ui.write(repo.root + "\n")
4400 ui.write(repo.root + "\n")
4401
4401
4402 @command('^serve',
4402 @command('^serve',
4403 [('A', 'accesslog', '', _('name of access log file to write to'),
4403 [('A', 'accesslog', '', _('name of access log file to write to'),
4404 _('FILE')),
4404 _('FILE')),
4405 ('d', 'daemon', None, _('run server in background')),
4405 ('d', 'daemon', None, _('run server in background')),
4406 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
4406 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
4407 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4407 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4408 # use string type, then we can check if something was passed
4408 # use string type, then we can check if something was passed
4409 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4409 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4410 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4410 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4411 _('ADDR')),
4411 _('ADDR')),
4412 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4412 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4413 _('PREFIX')),
4413 _('PREFIX')),
4414 ('n', 'name', '',
4414 ('n', 'name', '',
4415 _('name to show in web pages (default: working directory)'), _('NAME')),
4415 _('name to show in web pages (default: working directory)'), _('NAME')),
4416 ('', 'web-conf', '',
4416 ('', 'web-conf', '',
4417 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
4417 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
4418 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4418 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4419 _('FILE')),
4419 _('FILE')),
4420 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4420 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4421 ('', 'stdio', None, _('for remote clients')),
4421 ('', 'stdio', None, _('for remote clients')),
4422 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
4422 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
4423 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4423 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4424 ('', 'style', '', _('template style to use'), _('STYLE')),
4424 ('', 'style', '', _('template style to use'), _('STYLE')),
4425 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4425 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4426 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
4426 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
4427 _('[OPTION]...'))
4427 _('[OPTION]...'))
4428 def serve(ui, repo, **opts):
4428 def serve(ui, repo, **opts):
4429 """start stand-alone webserver
4429 """start stand-alone webserver
4430
4430
4431 Start a local HTTP repository browser and pull server. You can use
4431 Start a local HTTP repository browser and pull server. You can use
4432 this for ad-hoc sharing and browsing of repositories. It is
4432 this for ad-hoc sharing and browsing of repositories. It is
4433 recommended to use a real web server to serve a repository for
4433 recommended to use a real web server to serve a repository for
4434 longer periods of time.
4434 longer periods of time.
4435
4435
4436 Please note that the server does not implement access control.
4436 Please note that the server does not implement access control.
4437 This means that, by default, anybody can read from the server and
4437 This means that, by default, anybody can read from the server and
4438 nobody can write to it by default. Set the ``web.allow_push``
4438 nobody can write to it by default. Set the ``web.allow_push``
4439 option to ``*`` to allow everybody to push to the server. You
4439 option to ``*`` to allow everybody to push to the server. You
4440 should use a real web server if you need to authenticate users.
4440 should use a real web server if you need to authenticate users.
4441
4441
4442 By default, the server logs accesses to stdout and errors to
4442 By default, the server logs accesses to stdout and errors to
4443 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4443 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4444 files.
4444 files.
4445
4445
4446 To have the server choose a free port number to listen on, specify
4446 To have the server choose a free port number to listen on, specify
4447 a port number of 0; in this case, the server will print the port
4447 a port number of 0; in this case, the server will print the port
4448 number it uses.
4448 number it uses.
4449
4449
4450 Returns 0 on success.
4450 Returns 0 on success.
4451 """
4451 """
4452
4452
4453 if opts["stdio"] and opts["cmdserver"]:
4453 if opts["stdio"] and opts["cmdserver"]:
4454 raise util.Abort(_("cannot use --stdio with --cmdserver"))
4454 raise util.Abort(_("cannot use --stdio with --cmdserver"))
4455
4455
4456 def checkrepo():
4456 def checkrepo():
4457 if repo is None:
4457 if repo is None:
4458 raise error.RepoError(_("There is no Mercurial repository here"
4458 raise error.RepoError(_("There is no Mercurial repository here"
4459 " (.hg not found)"))
4459 " (.hg not found)"))
4460
4460
4461 if opts["stdio"]:
4461 if opts["stdio"]:
4462 checkrepo()
4462 checkrepo()
4463 s = sshserver.sshserver(ui, repo)
4463 s = sshserver.sshserver(ui, repo)
4464 s.serve_forever()
4464 s.serve_forever()
4465
4465
4466 if opts["cmdserver"]:
4466 if opts["cmdserver"]:
4467 checkrepo()
4467 checkrepo()
4468 s = commandserver.server(ui, repo, opts["cmdserver"])
4468 s = commandserver.server(ui, repo, opts["cmdserver"])
4469 return s.serve()
4469 return s.serve()
4470
4470
4471 # this way we can check if something was given in the command-line
4471 # this way we can check if something was given in the command-line
4472 if opts.get('port'):
4472 if opts.get('port'):
4473 opts['port'] = util.getport(opts.get('port'))
4473 opts['port'] = util.getport(opts.get('port'))
4474
4474
4475 baseui = repo and repo.baseui or ui
4475 baseui = repo and repo.baseui or ui
4476 optlist = ("name templates style address port prefix ipv6"
4476 optlist = ("name templates style address port prefix ipv6"
4477 " accesslog errorlog certificate encoding")
4477 " accesslog errorlog certificate encoding")
4478 for o in optlist.split():
4478 for o in optlist.split():
4479 val = opts.get(o, '')
4479 val = opts.get(o, '')
4480 if val in (None, ''): # should check against default options instead
4480 if val in (None, ''): # should check against default options instead
4481 continue
4481 continue
4482 baseui.setconfig("web", o, val)
4482 baseui.setconfig("web", o, val)
4483 if repo and repo.ui != baseui:
4483 if repo and repo.ui != baseui:
4484 repo.ui.setconfig("web", o, val)
4484 repo.ui.setconfig("web", o, val)
4485
4485
4486 o = opts.get('web_conf') or opts.get('webdir_conf')
4486 o = opts.get('web_conf') or opts.get('webdir_conf')
4487 if not o:
4487 if not o:
4488 if not repo:
4488 if not repo:
4489 raise error.RepoError(_("There is no Mercurial repository"
4489 raise error.RepoError(_("There is no Mercurial repository"
4490 " here (.hg not found)"))
4490 " here (.hg not found)"))
4491 o = repo.root
4491 o = repo.root
4492
4492
4493 app = hgweb.hgweb(o, baseui=ui)
4493 app = hgweb.hgweb(o, baseui=ui)
4494
4494
4495 class service(object):
4495 class service(object):
4496 def init(self):
4496 def init(self):
4497 util.setsignalhandler()
4497 util.setsignalhandler()
4498 self.httpd = hgweb.server.create_server(ui, app)
4498 self.httpd = hgweb.server.create_server(ui, app)
4499
4499
4500 if opts['port'] and not ui.verbose:
4500 if opts['port'] and not ui.verbose:
4501 return
4501 return
4502
4502
4503 if self.httpd.prefix:
4503 if self.httpd.prefix:
4504 prefix = self.httpd.prefix.strip('/') + '/'
4504 prefix = self.httpd.prefix.strip('/') + '/'
4505 else:
4505 else:
4506 prefix = ''
4506 prefix = ''
4507
4507
4508 port = ':%d' % self.httpd.port
4508 port = ':%d' % self.httpd.port
4509 if port == ':80':
4509 if port == ':80':
4510 port = ''
4510 port = ''
4511
4511
4512 bindaddr = self.httpd.addr
4512 bindaddr = self.httpd.addr
4513 if bindaddr == '0.0.0.0':
4513 if bindaddr == '0.0.0.0':
4514 bindaddr = '*'
4514 bindaddr = '*'
4515 elif ':' in bindaddr: # IPv6
4515 elif ':' in bindaddr: # IPv6
4516 bindaddr = '[%s]' % bindaddr
4516 bindaddr = '[%s]' % bindaddr
4517
4517
4518 fqaddr = self.httpd.fqaddr
4518 fqaddr = self.httpd.fqaddr
4519 if ':' in fqaddr:
4519 if ':' in fqaddr:
4520 fqaddr = '[%s]' % fqaddr
4520 fqaddr = '[%s]' % fqaddr
4521 if opts['port']:
4521 if opts['port']:
4522 write = ui.status
4522 write = ui.status
4523 else:
4523 else:
4524 write = ui.write
4524 write = ui.write
4525 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
4525 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
4526 (fqaddr, port, prefix, bindaddr, self.httpd.port))
4526 (fqaddr, port, prefix, bindaddr, self.httpd.port))
4527
4527
4528 def run(self):
4528 def run(self):
4529 self.httpd.serve_forever()
4529 self.httpd.serve_forever()
4530
4530
4531 service = service()
4531 service = service()
4532
4532
4533 cmdutil.service(opts, initfn=service.init, runfn=service.run)
4533 cmdutil.service(opts, initfn=service.init, runfn=service.run)
4534
4534
4535 @command('showconfig|debugconfig',
4535 @command('showconfig|debugconfig',
4536 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4536 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4537 _('[-u] [NAME]...'))
4537 _('[-u] [NAME]...'))
4538 def showconfig(ui, repo, *values, **opts):
4538 def showconfig(ui, repo, *values, **opts):
4539 """show combined config settings from all hgrc files
4539 """show combined config settings from all hgrc files
4540
4540
4541 With no arguments, print names and values of all config items.
4541 With no arguments, print names and values of all config items.
4542
4542
4543 With one argument of the form section.name, print just the value
4543 With one argument of the form section.name, print just the value
4544 of that config item.
4544 of that config item.
4545
4545
4546 With multiple arguments, print names and values of all config
4546 With multiple arguments, print names and values of all config
4547 items with matching section names.
4547 items with matching section names.
4548
4548
4549 With --debug, the source (filename and line number) is printed
4549 With --debug, the source (filename and line number) is printed
4550 for each config item.
4550 for each config item.
4551
4551
4552 Returns 0 on success.
4552 Returns 0 on success.
4553 """
4553 """
4554
4554
4555 for f in scmutil.rcpath():
4555 for f in scmutil.rcpath():
4556 ui.debug(_('read config from: %s\n') % f)
4556 ui.debug(_('read config from: %s\n') % f)
4557 untrusted = bool(opts.get('untrusted'))
4557 untrusted = bool(opts.get('untrusted'))
4558 if values:
4558 if values:
4559 sections = [v for v in values if '.' not in v]
4559 sections = [v for v in values if '.' not in v]
4560 items = [v for v in values if '.' in v]
4560 items = [v for v in values if '.' in v]
4561 if len(items) > 1 or items and sections:
4561 if len(items) > 1 or items and sections:
4562 raise util.Abort(_('only one config item permitted'))
4562 raise util.Abort(_('only one config item permitted'))
4563 for section, name, value in ui.walkconfig(untrusted=untrusted):
4563 for section, name, value in ui.walkconfig(untrusted=untrusted):
4564 value = str(value).replace('\n', '\\n')
4564 value = str(value).replace('\n', '\\n')
4565 sectname = section + '.' + name
4565 sectname = section + '.' + name
4566 if values:
4566 if values:
4567 for v in values:
4567 for v in values:
4568 if v == section:
4568 if v == section:
4569 ui.debug('%s: ' %
4569 ui.debug('%s: ' %
4570 ui.configsource(section, name, untrusted))
4570 ui.configsource(section, name, untrusted))
4571 ui.write('%s=%s\n' % (sectname, value))
4571 ui.write('%s=%s\n' % (sectname, value))
4572 elif v == sectname:
4572 elif v == sectname:
4573 ui.debug('%s: ' %
4573 ui.debug('%s: ' %
4574 ui.configsource(section, name, untrusted))
4574 ui.configsource(section, name, untrusted))
4575 ui.write(value, '\n')
4575 ui.write(value, '\n')
4576 else:
4576 else:
4577 ui.debug('%s: ' %
4577 ui.debug('%s: ' %
4578 ui.configsource(section, name, untrusted))
4578 ui.configsource(section, name, untrusted))
4579 ui.write('%s=%s\n' % (sectname, value))
4579 ui.write('%s=%s\n' % (sectname, value))
4580
4580
4581 @command('^status|st',
4581 @command('^status|st',
4582 [('A', 'all', None, _('show status of all files')),
4582 [('A', 'all', None, _('show status of all files')),
4583 ('m', 'modified', None, _('show only modified files')),
4583 ('m', 'modified', None, _('show only modified files')),
4584 ('a', 'added', None, _('show only added files')),
4584 ('a', 'added', None, _('show only added files')),
4585 ('r', 'removed', None, _('show only removed files')),
4585 ('r', 'removed', None, _('show only removed files')),
4586 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4586 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4587 ('c', 'clean', None, _('show only files without changes')),
4587 ('c', 'clean', None, _('show only files without changes')),
4588 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4588 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4589 ('i', 'ignored', None, _('show only ignored files')),
4589 ('i', 'ignored', None, _('show only ignored files')),
4590 ('n', 'no-status', None, _('hide status prefix')),
4590 ('n', 'no-status', None, _('hide status prefix')),
4591 ('C', 'copies', None, _('show source of copied files')),
4591 ('C', 'copies', None, _('show source of copied files')),
4592 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4592 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4593 ('', 'rev', [], _('show difference from revision'), _('REV')),
4593 ('', 'rev', [], _('show difference from revision'), _('REV')),
4594 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4594 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4595 ] + walkopts + subrepoopts,
4595 ] + walkopts + subrepoopts,
4596 _('[OPTION]... [FILE]...'))
4596 _('[OPTION]... [FILE]...'))
4597 def status(ui, repo, *pats, **opts):
4597 def status(ui, repo, *pats, **opts):
4598 """show changed files in the working directory
4598 """show changed files in the working directory
4599
4599
4600 Show status of files in the repository. If names are given, only
4600 Show status of files in the repository. If names are given, only
4601 files that match are shown. Files that are clean or ignored or
4601 files that match are shown. Files that are clean or ignored or
4602 the source of a copy/move operation, are not listed unless
4602 the source of a copy/move operation, are not listed unless
4603 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4603 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4604 Unless options described with "show only ..." are given, the
4604 Unless options described with "show only ..." are given, the
4605 options -mardu are used.
4605 options -mardu are used.
4606
4606
4607 Option -q/--quiet hides untracked (unknown and ignored) files
4607 Option -q/--quiet hides untracked (unknown and ignored) files
4608 unless explicitly requested with -u/--unknown or -i/--ignored.
4608 unless explicitly requested with -u/--unknown or -i/--ignored.
4609
4609
4610 .. note::
4610 .. note::
4611 status may appear to disagree with diff if permissions have
4611 status may appear to disagree with diff if permissions have
4612 changed or a merge has occurred. The standard diff format does
4612 changed or a merge has occurred. The standard diff format does
4613 not report permission changes and diff only reports changes
4613 not report permission changes and diff only reports changes
4614 relative to one merge parent.
4614 relative to one merge parent.
4615
4615
4616 If one revision is given, it is used as the base revision.
4616 If one revision is given, it is used as the base revision.
4617 If two revisions are given, the differences between them are
4617 If two revisions are given, the differences between them are
4618 shown. The --change option can also be used as a shortcut to list
4618 shown. The --change option can also be used as a shortcut to list
4619 the changed files of a revision from its first parent.
4619 the changed files of a revision from its first parent.
4620
4620
4621 The codes used to show the status of files are::
4621 The codes used to show the status of files are::
4622
4622
4623 M = modified
4623 M = modified
4624 A = added
4624 A = added
4625 R = removed
4625 R = removed
4626 C = clean
4626 C = clean
4627 ! = missing (deleted by non-hg command, but still tracked)
4627 ! = missing (deleted by non-hg command, but still tracked)
4628 ? = not tracked
4628 ? = not tracked
4629 I = ignored
4629 I = ignored
4630 = origin of the previous file listed as A (added)
4630 = origin of the previous file listed as A (added)
4631
4631
4632 Returns 0 on success.
4632 Returns 0 on success.
4633 """
4633 """
4634
4634
4635 revs = opts.get('rev')
4635 revs = opts.get('rev')
4636 change = opts.get('change')
4636 change = opts.get('change')
4637
4637
4638 if revs and change:
4638 if revs and change:
4639 msg = _('cannot specify --rev and --change at the same time')
4639 msg = _('cannot specify --rev and --change at the same time')
4640 raise util.Abort(msg)
4640 raise util.Abort(msg)
4641 elif change:
4641 elif change:
4642 node2 = repo.lookup(change)
4642 node2 = repo.lookup(change)
4643 node1 = repo[node2].p1().node()
4643 node1 = repo[node2].p1().node()
4644 else:
4644 else:
4645 node1, node2 = scmutil.revpair(repo, revs)
4645 node1, node2 = scmutil.revpair(repo, revs)
4646
4646
4647 cwd = (pats and repo.getcwd()) or ''
4647 cwd = (pats and repo.getcwd()) or ''
4648 end = opts.get('print0') and '\0' or '\n'
4648 end = opts.get('print0') and '\0' or '\n'
4649 copy = {}
4649 copy = {}
4650 states = 'modified added removed deleted unknown ignored clean'.split()
4650 states = 'modified added removed deleted unknown ignored clean'.split()
4651 show = [k for k in states if opts.get(k)]
4651 show = [k for k in states if opts.get(k)]
4652 if opts.get('all'):
4652 if opts.get('all'):
4653 show += ui.quiet and (states[:4] + ['clean']) or states
4653 show += ui.quiet and (states[:4] + ['clean']) or states
4654 if not show:
4654 if not show:
4655 show = ui.quiet and states[:4] or states[:5]
4655 show = ui.quiet and states[:4] or states[:5]
4656
4656
4657 stat = repo.status(node1, node2, scmutil.match(repo, pats, opts),
4657 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
4658 'ignored' in show, 'clean' in show, 'unknown' in show,
4658 'ignored' in show, 'clean' in show, 'unknown' in show,
4659 opts.get('subrepos'))
4659 opts.get('subrepos'))
4660 changestates = zip(states, 'MAR!?IC', stat)
4660 changestates = zip(states, 'MAR!?IC', stat)
4661
4661
4662 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
4662 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
4663 ctxn = repo[nullid]
4663 ctxn = repo[nullid]
4664 ctx1 = repo[node1]
4664 ctx1 = repo[node1]
4665 ctx2 = repo[node2]
4665 ctx2 = repo[node2]
4666 added = stat[1]
4666 added = stat[1]
4667 if node2 is None:
4667 if node2 is None:
4668 added = stat[0] + stat[1] # merged?
4668 added = stat[0] + stat[1] # merged?
4669
4669
4670 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
4670 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
4671 if k in added:
4671 if k in added:
4672 copy[k] = v
4672 copy[k] = v
4673 elif v in added:
4673 elif v in added:
4674 copy[v] = k
4674 copy[v] = k
4675
4675
4676 for state, char, files in changestates:
4676 for state, char, files in changestates:
4677 if state in show:
4677 if state in show:
4678 format = "%s %%s%s" % (char, end)
4678 format = "%s %%s%s" % (char, end)
4679 if opts.get('no_status'):
4679 if opts.get('no_status'):
4680 format = "%%s%s" % end
4680 format = "%%s%s" % end
4681
4681
4682 for f in files:
4682 for f in files:
4683 ui.write(format % repo.pathto(f, cwd),
4683 ui.write(format % repo.pathto(f, cwd),
4684 label='status.' + state)
4684 label='status.' + state)
4685 if f in copy:
4685 if f in copy:
4686 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
4686 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
4687 label='status.copied')
4687 label='status.copied')
4688
4688
4689 @command('^summary|sum',
4689 @command('^summary|sum',
4690 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4690 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4691 def summary(ui, repo, **opts):
4691 def summary(ui, repo, **opts):
4692 """summarize working directory state
4692 """summarize working directory state
4693
4693
4694 This generates a brief summary of the working directory state,
4694 This generates a brief summary of the working directory state,
4695 including parents, branch, commit status, and available updates.
4695 including parents, branch, commit status, and available updates.
4696
4696
4697 With the --remote option, this will check the default paths for
4697 With the --remote option, this will check the default paths for
4698 incoming and outgoing changes. This can be time-consuming.
4698 incoming and outgoing changes. This can be time-consuming.
4699
4699
4700 Returns 0 on success.
4700 Returns 0 on success.
4701 """
4701 """
4702
4702
4703 ctx = repo[None]
4703 ctx = repo[None]
4704 parents = ctx.parents()
4704 parents = ctx.parents()
4705 pnode = parents[0].node()
4705 pnode = parents[0].node()
4706
4706
4707 for p in parents:
4707 for p in parents:
4708 # label with log.changeset (instead of log.parent) since this
4708 # label with log.changeset (instead of log.parent) since this
4709 # shows a working directory parent *changeset*:
4709 # shows a working directory parent *changeset*:
4710 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
4710 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
4711 label='log.changeset')
4711 label='log.changeset')
4712 ui.write(' '.join(p.tags()), label='log.tag')
4712 ui.write(' '.join(p.tags()), label='log.tag')
4713 if p.bookmarks():
4713 if p.bookmarks():
4714 ui.write(' ' + ' '.join(p.bookmarks()), label='log.bookmark')
4714 ui.write(' ' + ' '.join(p.bookmarks()), label='log.bookmark')
4715 if p.rev() == -1:
4715 if p.rev() == -1:
4716 if not len(repo):
4716 if not len(repo):
4717 ui.write(_(' (empty repository)'))
4717 ui.write(_(' (empty repository)'))
4718 else:
4718 else:
4719 ui.write(_(' (no revision checked out)'))
4719 ui.write(_(' (no revision checked out)'))
4720 ui.write('\n')
4720 ui.write('\n')
4721 if p.description():
4721 if p.description():
4722 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4722 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4723 label='log.summary')
4723 label='log.summary')
4724
4724
4725 branch = ctx.branch()
4725 branch = ctx.branch()
4726 bheads = repo.branchheads(branch)
4726 bheads = repo.branchheads(branch)
4727 m = _('branch: %s\n') % branch
4727 m = _('branch: %s\n') % branch
4728 if branch != 'default':
4728 if branch != 'default':
4729 ui.write(m, label='log.branch')
4729 ui.write(m, label='log.branch')
4730 else:
4730 else:
4731 ui.status(m, label='log.branch')
4731 ui.status(m, label='log.branch')
4732
4732
4733 st = list(repo.status(unknown=True))[:6]
4733 st = list(repo.status(unknown=True))[:6]
4734
4734
4735 c = repo.dirstate.copies()
4735 c = repo.dirstate.copies()
4736 copied, renamed = [], []
4736 copied, renamed = [], []
4737 for d, s in c.iteritems():
4737 for d, s in c.iteritems():
4738 if s in st[2]:
4738 if s in st[2]:
4739 st[2].remove(s)
4739 st[2].remove(s)
4740 renamed.append(d)
4740 renamed.append(d)
4741 else:
4741 else:
4742 copied.append(d)
4742 copied.append(d)
4743 if d in st[1]:
4743 if d in st[1]:
4744 st[1].remove(d)
4744 st[1].remove(d)
4745 st.insert(3, renamed)
4745 st.insert(3, renamed)
4746 st.insert(4, copied)
4746 st.insert(4, copied)
4747
4747
4748 ms = mergemod.mergestate(repo)
4748 ms = mergemod.mergestate(repo)
4749 st.append([f for f in ms if ms[f] == 'u'])
4749 st.append([f for f in ms if ms[f] == 'u'])
4750
4750
4751 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4751 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4752 st.append(subs)
4752 st.append(subs)
4753
4753
4754 labels = [ui.label(_('%d modified'), 'status.modified'),
4754 labels = [ui.label(_('%d modified'), 'status.modified'),
4755 ui.label(_('%d added'), 'status.added'),
4755 ui.label(_('%d added'), 'status.added'),
4756 ui.label(_('%d removed'), 'status.removed'),
4756 ui.label(_('%d removed'), 'status.removed'),
4757 ui.label(_('%d renamed'), 'status.copied'),
4757 ui.label(_('%d renamed'), 'status.copied'),
4758 ui.label(_('%d copied'), 'status.copied'),
4758 ui.label(_('%d copied'), 'status.copied'),
4759 ui.label(_('%d deleted'), 'status.deleted'),
4759 ui.label(_('%d deleted'), 'status.deleted'),
4760 ui.label(_('%d unknown'), 'status.unknown'),
4760 ui.label(_('%d unknown'), 'status.unknown'),
4761 ui.label(_('%d ignored'), 'status.ignored'),
4761 ui.label(_('%d ignored'), 'status.ignored'),
4762 ui.label(_('%d unresolved'), 'resolve.unresolved'),
4762 ui.label(_('%d unresolved'), 'resolve.unresolved'),
4763 ui.label(_('%d subrepos'), 'status.modified')]
4763 ui.label(_('%d subrepos'), 'status.modified')]
4764 t = []
4764 t = []
4765 for s, l in zip(st, labels):
4765 for s, l in zip(st, labels):
4766 if s:
4766 if s:
4767 t.append(l % len(s))
4767 t.append(l % len(s))
4768
4768
4769 t = ', '.join(t)
4769 t = ', '.join(t)
4770 cleanworkdir = False
4770 cleanworkdir = False
4771
4771
4772 if len(parents) > 1:
4772 if len(parents) > 1:
4773 t += _(' (merge)')
4773 t += _(' (merge)')
4774 elif branch != parents[0].branch():
4774 elif branch != parents[0].branch():
4775 t += _(' (new branch)')
4775 t += _(' (new branch)')
4776 elif (parents[0].extra().get('close') and
4776 elif (parents[0].extra().get('close') and
4777 pnode in repo.branchheads(branch, closed=True)):
4777 pnode in repo.branchheads(branch, closed=True)):
4778 t += _(' (head closed)')
4778 t += _(' (head closed)')
4779 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
4779 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
4780 t += _(' (clean)')
4780 t += _(' (clean)')
4781 cleanworkdir = True
4781 cleanworkdir = True
4782 elif pnode not in bheads:
4782 elif pnode not in bheads:
4783 t += _(' (new branch head)')
4783 t += _(' (new branch head)')
4784
4784
4785 if cleanworkdir:
4785 if cleanworkdir:
4786 ui.status(_('commit: %s\n') % t.strip())
4786 ui.status(_('commit: %s\n') % t.strip())
4787 else:
4787 else:
4788 ui.write(_('commit: %s\n') % t.strip())
4788 ui.write(_('commit: %s\n') % t.strip())
4789
4789
4790 # all ancestors of branch heads - all ancestors of parent = new csets
4790 # all ancestors of branch heads - all ancestors of parent = new csets
4791 new = [0] * len(repo)
4791 new = [0] * len(repo)
4792 cl = repo.changelog
4792 cl = repo.changelog
4793 for a in [cl.rev(n) for n in bheads]:
4793 for a in [cl.rev(n) for n in bheads]:
4794 new[a] = 1
4794 new[a] = 1
4795 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
4795 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
4796 new[a] = 1
4796 new[a] = 1
4797 for a in [p.rev() for p in parents]:
4797 for a in [p.rev() for p in parents]:
4798 if a >= 0:
4798 if a >= 0:
4799 new[a] = 0
4799 new[a] = 0
4800 for a in cl.ancestors(*[p.rev() for p in parents]):
4800 for a in cl.ancestors(*[p.rev() for p in parents]):
4801 new[a] = 0
4801 new[a] = 0
4802 new = sum(new)
4802 new = sum(new)
4803
4803
4804 if new == 0:
4804 if new == 0:
4805 ui.status(_('update: (current)\n'))
4805 ui.status(_('update: (current)\n'))
4806 elif pnode not in bheads:
4806 elif pnode not in bheads:
4807 ui.write(_('update: %d new changesets (update)\n') % new)
4807 ui.write(_('update: %d new changesets (update)\n') % new)
4808 else:
4808 else:
4809 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4809 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4810 (new, len(bheads)))
4810 (new, len(bheads)))
4811
4811
4812 if opts.get('remote'):
4812 if opts.get('remote'):
4813 t = []
4813 t = []
4814 source, branches = hg.parseurl(ui.expandpath('default'))
4814 source, branches = hg.parseurl(ui.expandpath('default'))
4815 other = hg.peer(repo, {}, source)
4815 other = hg.peer(repo, {}, source)
4816 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4816 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4817 ui.debug('comparing with %s\n' % util.hidepassword(source))
4817 ui.debug('comparing with %s\n' % util.hidepassword(source))
4818 repo.ui.pushbuffer()
4818 repo.ui.pushbuffer()
4819 commoninc = discovery.findcommonincoming(repo, other)
4819 commoninc = discovery.findcommonincoming(repo, other)
4820 _common, incoming, _rheads = commoninc
4820 _common, incoming, _rheads = commoninc
4821 repo.ui.popbuffer()
4821 repo.ui.popbuffer()
4822 if incoming:
4822 if incoming:
4823 t.append(_('1 or more incoming'))
4823 t.append(_('1 or more incoming'))
4824
4824
4825 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
4825 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
4826 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
4826 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
4827 if source != dest:
4827 if source != dest:
4828 other = hg.peer(repo, {}, dest)
4828 other = hg.peer(repo, {}, dest)
4829 commoninc = None
4829 commoninc = None
4830 ui.debug('comparing with %s\n' % util.hidepassword(dest))
4830 ui.debug('comparing with %s\n' % util.hidepassword(dest))
4831 repo.ui.pushbuffer()
4831 repo.ui.pushbuffer()
4832 common, outheads = discovery.findcommonoutgoing(repo, other,
4832 common, outheads = discovery.findcommonoutgoing(repo, other,
4833 commoninc=commoninc)
4833 commoninc=commoninc)
4834 repo.ui.popbuffer()
4834 repo.ui.popbuffer()
4835 o = repo.changelog.findmissing(common=common, heads=outheads)
4835 o = repo.changelog.findmissing(common=common, heads=outheads)
4836 if o:
4836 if o:
4837 t.append(_('%d outgoing') % len(o))
4837 t.append(_('%d outgoing') % len(o))
4838 if 'bookmarks' in other.listkeys('namespaces'):
4838 if 'bookmarks' in other.listkeys('namespaces'):
4839 lmarks = repo.listkeys('bookmarks')
4839 lmarks = repo.listkeys('bookmarks')
4840 rmarks = other.listkeys('bookmarks')
4840 rmarks = other.listkeys('bookmarks')
4841 diff = set(rmarks) - set(lmarks)
4841 diff = set(rmarks) - set(lmarks)
4842 if len(diff) > 0:
4842 if len(diff) > 0:
4843 t.append(_('%d incoming bookmarks') % len(diff))
4843 t.append(_('%d incoming bookmarks') % len(diff))
4844 diff = set(lmarks) - set(rmarks)
4844 diff = set(lmarks) - set(rmarks)
4845 if len(diff) > 0:
4845 if len(diff) > 0:
4846 t.append(_('%d outgoing bookmarks') % len(diff))
4846 t.append(_('%d outgoing bookmarks') % len(diff))
4847
4847
4848 if t:
4848 if t:
4849 ui.write(_('remote: %s\n') % (', '.join(t)))
4849 ui.write(_('remote: %s\n') % (', '.join(t)))
4850 else:
4850 else:
4851 ui.status(_('remote: (synced)\n'))
4851 ui.status(_('remote: (synced)\n'))
4852
4852
4853 @command('tag',
4853 @command('tag',
4854 [('f', 'force', None, _('force tag')),
4854 [('f', 'force', None, _('force tag')),
4855 ('l', 'local', None, _('make the tag local')),
4855 ('l', 'local', None, _('make the tag local')),
4856 ('r', 'rev', '', _('revision to tag'), _('REV')),
4856 ('r', 'rev', '', _('revision to tag'), _('REV')),
4857 ('', 'remove', None, _('remove a tag')),
4857 ('', 'remove', None, _('remove a tag')),
4858 # -l/--local is already there, commitopts cannot be used
4858 # -l/--local is already there, commitopts cannot be used
4859 ('e', 'edit', None, _('edit commit message')),
4859 ('e', 'edit', None, _('edit commit message')),
4860 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
4860 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
4861 ] + commitopts2,
4861 ] + commitopts2,
4862 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
4862 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
4863 def tag(ui, repo, name1, *names, **opts):
4863 def tag(ui, repo, name1, *names, **opts):
4864 """add one or more tags for the current or given revision
4864 """add one or more tags for the current or given revision
4865
4865
4866 Name a particular revision using <name>.
4866 Name a particular revision using <name>.
4867
4867
4868 Tags are used to name particular revisions of the repository and are
4868 Tags are used to name particular revisions of the repository and are
4869 very useful to compare different revisions, to go back to significant
4869 very useful to compare different revisions, to go back to significant
4870 earlier versions or to mark branch points as releases, etc. Changing
4870 earlier versions or to mark branch points as releases, etc. Changing
4871 an existing tag is normally disallowed; use -f/--force to override.
4871 an existing tag is normally disallowed; use -f/--force to override.
4872
4872
4873 If no revision is given, the parent of the working directory is
4873 If no revision is given, the parent of the working directory is
4874 used, or tip if no revision is checked out.
4874 used, or tip if no revision is checked out.
4875
4875
4876 To facilitate version control, distribution, and merging of tags,
4876 To facilitate version control, distribution, and merging of tags,
4877 they are stored as a file named ".hgtags" which is managed similarly
4877 they are stored as a file named ".hgtags" which is managed similarly
4878 to other project files and can be hand-edited if necessary. This
4878 to other project files and can be hand-edited if necessary. This
4879 also means that tagging creates a new commit. The file
4879 also means that tagging creates a new commit. The file
4880 ".hg/localtags" is used for local tags (not shared among
4880 ".hg/localtags" is used for local tags (not shared among
4881 repositories).
4881 repositories).
4882
4882
4883 Tag commits are usually made at the head of a branch. If the parent
4883 Tag commits are usually made at the head of a branch. If the parent
4884 of the working directory is not a branch head, :hg:`tag` aborts; use
4884 of the working directory is not a branch head, :hg:`tag` aborts; use
4885 -f/--force to force the tag commit to be based on a non-head
4885 -f/--force to force the tag commit to be based on a non-head
4886 changeset.
4886 changeset.
4887
4887
4888 See :hg:`help dates` for a list of formats valid for -d/--date.
4888 See :hg:`help dates` for a list of formats valid for -d/--date.
4889
4889
4890 Since tag names have priority over branch names during revision
4890 Since tag names have priority over branch names during revision
4891 lookup, using an existing branch name as a tag name is discouraged.
4891 lookup, using an existing branch name as a tag name is discouraged.
4892
4892
4893 Returns 0 on success.
4893 Returns 0 on success.
4894 """
4894 """
4895
4895
4896 rev_ = "."
4896 rev_ = "."
4897 names = [t.strip() for t in (name1,) + names]
4897 names = [t.strip() for t in (name1,) + names]
4898 if len(names) != len(set(names)):
4898 if len(names) != len(set(names)):
4899 raise util.Abort(_('tag names must be unique'))
4899 raise util.Abort(_('tag names must be unique'))
4900 for n in names:
4900 for n in names:
4901 if n in ['tip', '.', 'null']:
4901 if n in ['tip', '.', 'null']:
4902 raise util.Abort(_("the name '%s' is reserved") % n)
4902 raise util.Abort(_("the name '%s' is reserved") % n)
4903 if not n:
4903 if not n:
4904 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
4904 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
4905 if opts.get('rev') and opts.get('remove'):
4905 if opts.get('rev') and opts.get('remove'):
4906 raise util.Abort(_("--rev and --remove are incompatible"))
4906 raise util.Abort(_("--rev and --remove are incompatible"))
4907 if opts.get('rev'):
4907 if opts.get('rev'):
4908 rev_ = opts['rev']
4908 rev_ = opts['rev']
4909 message = opts.get('message')
4909 message = opts.get('message')
4910 if opts.get('remove'):
4910 if opts.get('remove'):
4911 expectedtype = opts.get('local') and 'local' or 'global'
4911 expectedtype = opts.get('local') and 'local' or 'global'
4912 for n in names:
4912 for n in names:
4913 if not repo.tagtype(n):
4913 if not repo.tagtype(n):
4914 raise util.Abort(_("tag '%s' does not exist") % n)
4914 raise util.Abort(_("tag '%s' does not exist") % n)
4915 if repo.tagtype(n) != expectedtype:
4915 if repo.tagtype(n) != expectedtype:
4916 if expectedtype == 'global':
4916 if expectedtype == 'global':
4917 raise util.Abort(_("tag '%s' is not a global tag") % n)
4917 raise util.Abort(_("tag '%s' is not a global tag") % n)
4918 else:
4918 else:
4919 raise util.Abort(_("tag '%s' is not a local tag") % n)
4919 raise util.Abort(_("tag '%s' is not a local tag") % n)
4920 rev_ = nullid
4920 rev_ = nullid
4921 if not message:
4921 if not message:
4922 # we don't translate commit messages
4922 # we don't translate commit messages
4923 message = 'Removed tag %s' % ', '.join(names)
4923 message = 'Removed tag %s' % ', '.join(names)
4924 elif not opts.get('force'):
4924 elif not opts.get('force'):
4925 for n in names:
4925 for n in names:
4926 if n in repo.tags():
4926 if n in repo.tags():
4927 raise util.Abort(_("tag '%s' already exists "
4927 raise util.Abort(_("tag '%s' already exists "
4928 "(use -f to force)") % n)
4928 "(use -f to force)") % n)
4929 if not opts.get('local'):
4929 if not opts.get('local'):
4930 p1, p2 = repo.dirstate.parents()
4930 p1, p2 = repo.dirstate.parents()
4931 if p2 != nullid:
4931 if p2 != nullid:
4932 raise util.Abort(_('uncommitted merge'))
4932 raise util.Abort(_('uncommitted merge'))
4933 bheads = repo.branchheads()
4933 bheads = repo.branchheads()
4934 if not opts.get('force') and bheads and p1 not in bheads:
4934 if not opts.get('force') and bheads and p1 not in bheads:
4935 raise util.Abort(_('not at a branch head (use -f to force)'))
4935 raise util.Abort(_('not at a branch head (use -f to force)'))
4936 r = scmutil.revsingle(repo, rev_).node()
4936 r = scmutil.revsingle(repo, rev_).node()
4937
4937
4938 if not message:
4938 if not message:
4939 # we don't translate commit messages
4939 # we don't translate commit messages
4940 message = ('Added tag %s for changeset %s' %
4940 message = ('Added tag %s for changeset %s' %
4941 (', '.join(names), short(r)))
4941 (', '.join(names), short(r)))
4942
4942
4943 date = opts.get('date')
4943 date = opts.get('date')
4944 if date:
4944 if date:
4945 date = util.parsedate(date)
4945 date = util.parsedate(date)
4946
4946
4947 if opts.get('edit'):
4947 if opts.get('edit'):
4948 message = ui.edit(message, ui.username())
4948 message = ui.edit(message, ui.username())
4949
4949
4950 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
4950 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
4951
4951
4952 @command('tags', [], '')
4952 @command('tags', [], '')
4953 def tags(ui, repo):
4953 def tags(ui, repo):
4954 """list repository tags
4954 """list repository tags
4955
4955
4956 This lists both regular and local tags. When the -v/--verbose
4956 This lists both regular and local tags. When the -v/--verbose
4957 switch is used, a third column "local" is printed for local tags.
4957 switch is used, a third column "local" is printed for local tags.
4958
4958
4959 Returns 0 on success.
4959 Returns 0 on success.
4960 """
4960 """
4961
4961
4962 hexfunc = ui.debugflag and hex or short
4962 hexfunc = ui.debugflag and hex or short
4963 tagtype = ""
4963 tagtype = ""
4964
4964
4965 for t, n in reversed(repo.tagslist()):
4965 for t, n in reversed(repo.tagslist()):
4966 if ui.quiet:
4966 if ui.quiet:
4967 ui.write("%s\n" % t)
4967 ui.write("%s\n" % t)
4968 continue
4968 continue
4969
4969
4970 hn = hexfunc(n)
4970 hn = hexfunc(n)
4971 r = "%5d:%s" % (repo.changelog.rev(n), hn)
4971 r = "%5d:%s" % (repo.changelog.rev(n), hn)
4972 spaces = " " * (30 - encoding.colwidth(t))
4972 spaces = " " * (30 - encoding.colwidth(t))
4973
4973
4974 if ui.verbose:
4974 if ui.verbose:
4975 if repo.tagtype(t) == 'local':
4975 if repo.tagtype(t) == 'local':
4976 tagtype = " local"
4976 tagtype = " local"
4977 else:
4977 else:
4978 tagtype = ""
4978 tagtype = ""
4979 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
4979 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
4980
4980
4981 @command('tip',
4981 @command('tip',
4982 [('p', 'patch', None, _('show patch')),
4982 [('p', 'patch', None, _('show patch')),
4983 ('g', 'git', None, _('use git extended diff format')),
4983 ('g', 'git', None, _('use git extended diff format')),
4984 ] + templateopts,
4984 ] + templateopts,
4985 _('[-p] [-g]'))
4985 _('[-p] [-g]'))
4986 def tip(ui, repo, **opts):
4986 def tip(ui, repo, **opts):
4987 """show the tip revision
4987 """show the tip revision
4988
4988
4989 The tip revision (usually just called the tip) is the changeset
4989 The tip revision (usually just called the tip) is the changeset
4990 most recently added to the repository (and therefore the most
4990 most recently added to the repository (and therefore the most
4991 recently changed head).
4991 recently changed head).
4992
4992
4993 If you have just made a commit, that commit will be the tip. If
4993 If you have just made a commit, that commit will be the tip. If
4994 you have just pulled changes from another repository, the tip of
4994 you have just pulled changes from another repository, the tip of
4995 that repository becomes the current tip. The "tip" tag is special
4995 that repository becomes the current tip. The "tip" tag is special
4996 and cannot be renamed or assigned to a different changeset.
4996 and cannot be renamed or assigned to a different changeset.
4997
4997
4998 Returns 0 on success.
4998 Returns 0 on success.
4999 """
4999 """
5000 displayer = cmdutil.show_changeset(ui, repo, opts)
5000 displayer = cmdutil.show_changeset(ui, repo, opts)
5001 displayer.show(repo[len(repo) - 1])
5001 displayer.show(repo[len(repo) - 1])
5002 displayer.close()
5002 displayer.close()
5003
5003
5004 @command('unbundle',
5004 @command('unbundle',
5005 [('u', 'update', None,
5005 [('u', 'update', None,
5006 _('update to new branch head if changesets were unbundled'))],
5006 _('update to new branch head if changesets were unbundled'))],
5007 _('[-u] FILE...'))
5007 _('[-u] FILE...'))
5008 def unbundle(ui, repo, fname1, *fnames, **opts):
5008 def unbundle(ui, repo, fname1, *fnames, **opts):
5009 """apply one or more changegroup files
5009 """apply one or more changegroup files
5010
5010
5011 Apply one or more compressed changegroup files generated by the
5011 Apply one or more compressed changegroup files generated by the
5012 bundle command.
5012 bundle command.
5013
5013
5014 Returns 0 on success, 1 if an update has unresolved files.
5014 Returns 0 on success, 1 if an update has unresolved files.
5015 """
5015 """
5016 fnames = (fname1,) + fnames
5016 fnames = (fname1,) + fnames
5017
5017
5018 lock = repo.lock()
5018 lock = repo.lock()
5019 wc = repo['.']
5019 wc = repo['.']
5020 try:
5020 try:
5021 for fname in fnames:
5021 for fname in fnames:
5022 f = url.open(ui, fname)
5022 f = url.open(ui, fname)
5023 gen = changegroup.readbundle(f, fname)
5023 gen = changegroup.readbundle(f, fname)
5024 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
5024 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
5025 lock=lock)
5025 lock=lock)
5026 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5026 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5027 finally:
5027 finally:
5028 lock.release()
5028 lock.release()
5029 return postincoming(ui, repo, modheads, opts.get('update'), None)
5029 return postincoming(ui, repo, modheads, opts.get('update'), None)
5030
5030
5031 @command('^update|up|checkout|co',
5031 @command('^update|up|checkout|co',
5032 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5032 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5033 ('c', 'check', None,
5033 ('c', 'check', None,
5034 _('update across branches if no uncommitted changes')),
5034 _('update across branches if no uncommitted changes')),
5035 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5035 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5036 ('r', 'rev', '', _('revision'), _('REV'))],
5036 ('r', 'rev', '', _('revision'), _('REV'))],
5037 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5037 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5038 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5038 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5039 """update working directory (or switch revisions)
5039 """update working directory (or switch revisions)
5040
5040
5041 Update the repository's working directory to the specified
5041 Update the repository's working directory to the specified
5042 changeset. If no changeset is specified, update to the tip of the
5042 changeset. If no changeset is specified, update to the tip of the
5043 current named branch.
5043 current named branch.
5044
5044
5045 If the changeset is not a descendant of the working directory's
5045 If the changeset is not a descendant of the working directory's
5046 parent, the update is aborted. With the -c/--check option, the
5046 parent, the update is aborted. With the -c/--check option, the
5047 working directory is checked for uncommitted changes; if none are
5047 working directory is checked for uncommitted changes; if none are
5048 found, the working directory is updated to the specified
5048 found, the working directory is updated to the specified
5049 changeset.
5049 changeset.
5050
5050
5051 The following rules apply when the working directory contains
5051 The following rules apply when the working directory contains
5052 uncommitted changes:
5052 uncommitted changes:
5053
5053
5054 1. If neither -c/--check nor -C/--clean is specified, and if
5054 1. If neither -c/--check nor -C/--clean is specified, and if
5055 the requested changeset is an ancestor or descendant of
5055 the requested changeset is an ancestor or descendant of
5056 the working directory's parent, the uncommitted changes
5056 the working directory's parent, the uncommitted changes
5057 are merged into the requested changeset and the merged
5057 are merged into the requested changeset and the merged
5058 result is left uncommitted. If the requested changeset is
5058 result is left uncommitted. If the requested changeset is
5059 not an ancestor or descendant (that is, it is on another
5059 not an ancestor or descendant (that is, it is on another
5060 branch), the update is aborted and the uncommitted changes
5060 branch), the update is aborted and the uncommitted changes
5061 are preserved.
5061 are preserved.
5062
5062
5063 2. With the -c/--check option, the update is aborted and the
5063 2. With the -c/--check option, the update is aborted and the
5064 uncommitted changes are preserved.
5064 uncommitted changes are preserved.
5065
5065
5066 3. With the -C/--clean option, uncommitted changes are discarded and
5066 3. With the -C/--clean option, uncommitted changes are discarded and
5067 the working directory is updated to the requested changeset.
5067 the working directory is updated to the requested changeset.
5068
5068
5069 Use null as the changeset to remove the working directory (like
5069 Use null as the changeset to remove the working directory (like
5070 :hg:`clone -U`).
5070 :hg:`clone -U`).
5071
5071
5072 If you want to update just one file to an older changeset, use
5072 If you want to update just one file to an older changeset, use
5073 :hg:`revert`.
5073 :hg:`revert`.
5074
5074
5075 See :hg:`help dates` for a list of formats valid for -d/--date.
5075 See :hg:`help dates` for a list of formats valid for -d/--date.
5076
5076
5077 Returns 0 on success, 1 if there are unresolved files.
5077 Returns 0 on success, 1 if there are unresolved files.
5078 """
5078 """
5079 if rev and node:
5079 if rev and node:
5080 raise util.Abort(_("please specify just one revision"))
5080 raise util.Abort(_("please specify just one revision"))
5081
5081
5082 if rev is None or rev == '':
5082 if rev is None or rev == '':
5083 rev = node
5083 rev = node
5084
5084
5085 # if we defined a bookmark, we have to remember the original bookmark name
5085 # if we defined a bookmark, we have to remember the original bookmark name
5086 brev = rev
5086 brev = rev
5087 rev = scmutil.revsingle(repo, rev, rev).rev()
5087 rev = scmutil.revsingle(repo, rev, rev).rev()
5088
5088
5089 if check and clean:
5089 if check and clean:
5090 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5090 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5091
5091
5092 if check:
5092 if check:
5093 # we could use dirty() but we can ignore merge and branch trivia
5093 # we could use dirty() but we can ignore merge and branch trivia
5094 c = repo[None]
5094 c = repo[None]
5095 if c.modified() or c.added() or c.removed():
5095 if c.modified() or c.added() or c.removed():
5096 raise util.Abort(_("uncommitted local changes"))
5096 raise util.Abort(_("uncommitted local changes"))
5097
5097
5098 if date:
5098 if date:
5099 if rev is not None:
5099 if rev is not None:
5100 raise util.Abort(_("you can't specify a revision and a date"))
5100 raise util.Abort(_("you can't specify a revision and a date"))
5101 rev = cmdutil.finddate(ui, repo, date)
5101 rev = cmdutil.finddate(ui, repo, date)
5102
5102
5103 if clean or check:
5103 if clean or check:
5104 ret = hg.clean(repo, rev)
5104 ret = hg.clean(repo, rev)
5105 else:
5105 else:
5106 ret = hg.update(repo, rev)
5106 ret = hg.update(repo, rev)
5107
5107
5108 if brev in repo._bookmarks:
5108 if brev in repo._bookmarks:
5109 bookmarks.setcurrent(repo, brev)
5109 bookmarks.setcurrent(repo, brev)
5110
5110
5111 return ret
5111 return ret
5112
5112
5113 @command('verify', [])
5113 @command('verify', [])
5114 def verify(ui, repo):
5114 def verify(ui, repo):
5115 """verify the integrity of the repository
5115 """verify the integrity of the repository
5116
5116
5117 Verify the integrity of the current repository.
5117 Verify the integrity of the current repository.
5118
5118
5119 This will perform an extensive check of the repository's
5119 This will perform an extensive check of the repository's
5120 integrity, validating the hashes and checksums of each entry in
5120 integrity, validating the hashes and checksums of each entry in
5121 the changelog, manifest, and tracked files, as well as the
5121 the changelog, manifest, and tracked files, as well as the
5122 integrity of their crosslinks and indices.
5122 integrity of their crosslinks and indices.
5123
5123
5124 Returns 0 on success, 1 if errors are encountered.
5124 Returns 0 on success, 1 if errors are encountered.
5125 """
5125 """
5126 return hg.verify(repo)
5126 return hg.verify(repo)
5127
5127
5128 @command('version', [])
5128 @command('version', [])
5129 def version_(ui):
5129 def version_(ui):
5130 """output version and copyright information"""
5130 """output version and copyright information"""
5131 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5131 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5132 % util.version())
5132 % util.version())
5133 ui.status(_(
5133 ui.status(_(
5134 "(see http://mercurial.selenic.com for more information)\n"
5134 "(see http://mercurial.selenic.com for more information)\n"
5135 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
5135 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
5136 "This is free software; see the source for copying conditions. "
5136 "This is free software; see the source for copying conditions. "
5137 "There is NO\nwarranty; "
5137 "There is NO\nwarranty; "
5138 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5138 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5139 ))
5139 ))
5140
5140
5141 norepo = ("clone init version help debugcommands debugcomplete"
5141 norepo = ("clone init version help debugcommands debugcomplete"
5142 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5142 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5143 " debugknown debuggetbundle debugbundle")
5143 " debugknown debuggetbundle debugbundle")
5144 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5144 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5145 " debugdata debugindex debugindexdot debugrevlog")
5145 " debugdata debugindex debugindexdot debugrevlog")
@@ -1,711 +1,711 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import util, error, osutil, revset, similar
9 import util, error, osutil, revset, similar
10 import match as matchmod
10 import match as matchmod
11 import os, errno, stat, sys, glob
11 import os, errno, stat, sys, glob
12
12
13 def checkfilename(f):
13 def checkfilename(f):
14 '''Check that the filename f is an acceptable filename for a tracked file'''
14 '''Check that the filename f is an acceptable filename for a tracked file'''
15 if '\r' in f or '\n' in f:
15 if '\r' in f or '\n' in f:
16 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
16 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
17
17
18 def checkportable(ui, f):
18 def checkportable(ui, f):
19 '''Check if filename f is portable and warn or abort depending on config'''
19 '''Check if filename f is portable and warn or abort depending on config'''
20 checkfilename(f)
20 checkfilename(f)
21 abort, warn = checkportabilityalert(ui)
21 abort, warn = checkportabilityalert(ui)
22 if abort or warn:
22 if abort or warn:
23 msg = util.checkwinfilename(f)
23 msg = util.checkwinfilename(f)
24 if msg:
24 if msg:
25 msg = "%s: %r" % (msg, f)
25 msg = "%s: %r" % (msg, f)
26 if abort:
26 if abort:
27 raise util.Abort(msg)
27 raise util.Abort(msg)
28 ui.warn(_("warning: %s\n") % msg)
28 ui.warn(_("warning: %s\n") % msg)
29
29
30 def checkportabilityalert(ui):
30 def checkportabilityalert(ui):
31 '''check if the user's config requests nothing, a warning, or abort for
31 '''check if the user's config requests nothing, a warning, or abort for
32 non-portable filenames'''
32 non-portable filenames'''
33 val = ui.config('ui', 'portablefilenames', 'warn')
33 val = ui.config('ui', 'portablefilenames', 'warn')
34 lval = val.lower()
34 lval = val.lower()
35 bval = util.parsebool(val)
35 bval = util.parsebool(val)
36 abort = os.name == 'nt' or lval == 'abort'
36 abort = os.name == 'nt' or lval == 'abort'
37 warn = bval or lval == 'warn'
37 warn = bval or lval == 'warn'
38 if bval is None and not (warn or abort or lval == 'ignore'):
38 if bval is None and not (warn or abort or lval == 'ignore'):
39 raise error.ConfigError(
39 raise error.ConfigError(
40 _("ui.portablefilenames value is invalid ('%s')") % val)
40 _("ui.portablefilenames value is invalid ('%s')") % val)
41 return abort, warn
41 return abort, warn
42
42
43 class casecollisionauditor(object):
43 class casecollisionauditor(object):
44 def __init__(self, ui, abort, existingiter):
44 def __init__(self, ui, abort, existingiter):
45 self._ui = ui
45 self._ui = ui
46 self._abort = abort
46 self._abort = abort
47 self._map = {}
47 self._map = {}
48 for f in existingiter:
48 for f in existingiter:
49 self._map[f.lower()] = f
49 self._map[f.lower()] = f
50
50
51 def __call__(self, f):
51 def __call__(self, f):
52 fl = f.lower()
52 fl = f.lower()
53 map = self._map
53 map = self._map
54 if fl in map and map[fl] != f:
54 if fl in map and map[fl] != f:
55 msg = _('possible case-folding collision for %s') % f
55 msg = _('possible case-folding collision for %s') % f
56 if self._abort:
56 if self._abort:
57 raise util.Abort(msg)
57 raise util.Abort(msg)
58 self._ui.warn(_("warning: %s\n") % msg)
58 self._ui.warn(_("warning: %s\n") % msg)
59 map[fl] = f
59 map[fl] = f
60
60
61 class pathauditor(object):
61 class pathauditor(object):
62 '''ensure that a filesystem path contains no banned components.
62 '''ensure that a filesystem path contains no banned components.
63 the following properties of a path are checked:
63 the following properties of a path are checked:
64
64
65 - ends with a directory separator
65 - ends with a directory separator
66 - under top-level .hg
66 - under top-level .hg
67 - starts at the root of a windows drive
67 - starts at the root of a windows drive
68 - contains ".."
68 - contains ".."
69 - traverses a symlink (e.g. a/symlink_here/b)
69 - traverses a symlink (e.g. a/symlink_here/b)
70 - inside a nested repository (a callback can be used to approve
70 - inside a nested repository (a callback can be used to approve
71 some nested repositories, e.g., subrepositories)
71 some nested repositories, e.g., subrepositories)
72 '''
72 '''
73
73
74 def __init__(self, root, callback=None):
74 def __init__(self, root, callback=None):
75 self.audited = set()
75 self.audited = set()
76 self.auditeddir = set()
76 self.auditeddir = set()
77 self.root = root
77 self.root = root
78 self.callback = callback
78 self.callback = callback
79
79
80 def __call__(self, path):
80 def __call__(self, path):
81 '''Check the relative path.
81 '''Check the relative path.
82 path may contain a pattern (e.g. foodir/**.txt)'''
82 path may contain a pattern (e.g. foodir/**.txt)'''
83
83
84 if path in self.audited:
84 if path in self.audited:
85 return
85 return
86 # AIX ignores "/" at end of path, others raise EISDIR.
86 # AIX ignores "/" at end of path, others raise EISDIR.
87 if util.endswithsep(path):
87 if util.endswithsep(path):
88 raise util.Abort(_("path ends in directory separator: %s") % path)
88 raise util.Abort(_("path ends in directory separator: %s") % path)
89 normpath = os.path.normcase(path)
89 normpath = os.path.normcase(path)
90 parts = util.splitpath(normpath)
90 parts = util.splitpath(normpath)
91 if (os.path.splitdrive(path)[0]
91 if (os.path.splitdrive(path)[0]
92 or parts[0].lower() in ('.hg', '.hg.', '')
92 or parts[0].lower() in ('.hg', '.hg.', '')
93 or os.pardir in parts):
93 or os.pardir in parts):
94 raise util.Abort(_("path contains illegal component: %s") % path)
94 raise util.Abort(_("path contains illegal component: %s") % path)
95 if '.hg' in path.lower():
95 if '.hg' in path.lower():
96 lparts = [p.lower() for p in parts]
96 lparts = [p.lower() for p in parts]
97 for p in '.hg', '.hg.':
97 for p in '.hg', '.hg.':
98 if p in lparts[1:]:
98 if p in lparts[1:]:
99 pos = lparts.index(p)
99 pos = lparts.index(p)
100 base = os.path.join(*parts[:pos])
100 base = os.path.join(*parts[:pos])
101 raise util.Abort(_('path %r is inside nested repo %r')
101 raise util.Abort(_('path %r is inside nested repo %r')
102 % (path, base))
102 % (path, base))
103
103
104 parts.pop()
104 parts.pop()
105 prefixes = []
105 prefixes = []
106 while parts:
106 while parts:
107 prefix = os.sep.join(parts)
107 prefix = os.sep.join(parts)
108 if prefix in self.auditeddir:
108 if prefix in self.auditeddir:
109 break
109 break
110 curpath = os.path.join(self.root, prefix)
110 curpath = os.path.join(self.root, prefix)
111 try:
111 try:
112 st = os.lstat(curpath)
112 st = os.lstat(curpath)
113 except OSError, err:
113 except OSError, err:
114 # EINVAL can be raised as invalid path syntax under win32.
114 # EINVAL can be raised as invalid path syntax under win32.
115 # They must be ignored for patterns can be checked too.
115 # They must be ignored for patterns can be checked too.
116 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
116 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
117 raise
117 raise
118 else:
118 else:
119 if stat.S_ISLNK(st.st_mode):
119 if stat.S_ISLNK(st.st_mode):
120 raise util.Abort(
120 raise util.Abort(
121 _('path %r traverses symbolic link %r')
121 _('path %r traverses symbolic link %r')
122 % (path, prefix))
122 % (path, prefix))
123 elif (stat.S_ISDIR(st.st_mode) and
123 elif (stat.S_ISDIR(st.st_mode) and
124 os.path.isdir(os.path.join(curpath, '.hg'))):
124 os.path.isdir(os.path.join(curpath, '.hg'))):
125 if not self.callback or not self.callback(curpath):
125 if not self.callback or not self.callback(curpath):
126 raise util.Abort(_('path %r is inside nested repo %r') %
126 raise util.Abort(_('path %r is inside nested repo %r') %
127 (path, prefix))
127 (path, prefix))
128 prefixes.append(prefix)
128 prefixes.append(prefix)
129 parts.pop()
129 parts.pop()
130
130
131 self.audited.add(path)
131 self.audited.add(path)
132 # only add prefixes to the cache after checking everything: we don't
132 # only add prefixes to the cache after checking everything: we don't
133 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
133 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
134 self.auditeddir.update(prefixes)
134 self.auditeddir.update(prefixes)
135
135
136 class abstractopener(object):
136 class abstractopener(object):
137 """Abstract base class; cannot be instantiated"""
137 """Abstract base class; cannot be instantiated"""
138
138
139 def __init__(self, *args, **kwargs):
139 def __init__(self, *args, **kwargs):
140 '''Prevent instantiation; don't call this from subclasses.'''
140 '''Prevent instantiation; don't call this from subclasses.'''
141 raise NotImplementedError('attempted instantiating ' + str(type(self)))
141 raise NotImplementedError('attempted instantiating ' + str(type(self)))
142
142
143 def read(self, path):
143 def read(self, path):
144 fp = self(path, 'rb')
144 fp = self(path, 'rb')
145 try:
145 try:
146 return fp.read()
146 return fp.read()
147 finally:
147 finally:
148 fp.close()
148 fp.close()
149
149
150 def write(self, path, data):
150 def write(self, path, data):
151 fp = self(path, 'wb')
151 fp = self(path, 'wb')
152 try:
152 try:
153 return fp.write(data)
153 return fp.write(data)
154 finally:
154 finally:
155 fp.close()
155 fp.close()
156
156
157 def append(self, path, data):
157 def append(self, path, data):
158 fp = self(path, 'ab')
158 fp = self(path, 'ab')
159 try:
159 try:
160 return fp.write(data)
160 return fp.write(data)
161 finally:
161 finally:
162 fp.close()
162 fp.close()
163
163
164 class opener(abstractopener):
164 class opener(abstractopener):
165 '''Open files relative to a base directory
165 '''Open files relative to a base directory
166
166
167 This class is used to hide the details of COW semantics and
167 This class is used to hide the details of COW semantics and
168 remote file access from higher level code.
168 remote file access from higher level code.
169 '''
169 '''
170 def __init__(self, base, audit=True):
170 def __init__(self, base, audit=True):
171 self.base = base
171 self.base = base
172 if audit:
172 if audit:
173 self.auditor = pathauditor(base)
173 self.auditor = pathauditor(base)
174 else:
174 else:
175 self.auditor = util.always
175 self.auditor = util.always
176 self.createmode = None
176 self.createmode = None
177 self._trustnlink = None
177 self._trustnlink = None
178
178
179 @util.propertycache
179 @util.propertycache
180 def _cansymlink(self):
180 def _cansymlink(self):
181 return util.checklink(self.base)
181 return util.checklink(self.base)
182
182
183 def _fixfilemode(self, name):
183 def _fixfilemode(self, name):
184 if self.createmode is None:
184 if self.createmode is None:
185 return
185 return
186 os.chmod(name, self.createmode & 0666)
186 os.chmod(name, self.createmode & 0666)
187
187
188 def __call__(self, path, mode="r", text=False, atomictemp=False):
188 def __call__(self, path, mode="r", text=False, atomictemp=False):
189 r = util.checkosfilename(path)
189 r = util.checkosfilename(path)
190 if r:
190 if r:
191 raise util.Abort("%s: %r" % (r, path))
191 raise util.Abort("%s: %r" % (r, path))
192 self.auditor(path)
192 self.auditor(path)
193 f = os.path.join(self.base, path)
193 f = os.path.join(self.base, path)
194
194
195 if not text and "b" not in mode:
195 if not text and "b" not in mode:
196 mode += "b" # for that other OS
196 mode += "b" # for that other OS
197
197
198 nlink = -1
198 nlink = -1
199 dirname, basename = os.path.split(f)
199 dirname, basename = os.path.split(f)
200 # If basename is empty, then the path is malformed because it points
200 # If basename is empty, then the path is malformed because it points
201 # to a directory. Let the posixfile() call below raise IOError.
201 # to a directory. Let the posixfile() call below raise IOError.
202 if basename and mode not in ('r', 'rb'):
202 if basename and mode not in ('r', 'rb'):
203 if atomictemp:
203 if atomictemp:
204 if not os.path.isdir(dirname):
204 if not os.path.isdir(dirname):
205 util.makedirs(dirname, self.createmode)
205 util.makedirs(dirname, self.createmode)
206 return util.atomictempfile(f, mode, self.createmode)
206 return util.atomictempfile(f, mode, self.createmode)
207 try:
207 try:
208 if 'w' in mode:
208 if 'w' in mode:
209 util.unlink(f)
209 util.unlink(f)
210 nlink = 0
210 nlink = 0
211 else:
211 else:
212 # nlinks() may behave differently for files on Windows
212 # nlinks() may behave differently for files on Windows
213 # shares if the file is open.
213 # shares if the file is open.
214 fd = util.posixfile(f)
214 fd = util.posixfile(f)
215 nlink = util.nlinks(f)
215 nlink = util.nlinks(f)
216 if nlink < 1:
216 if nlink < 1:
217 nlink = 2 # force mktempcopy (issue1922)
217 nlink = 2 # force mktempcopy (issue1922)
218 fd.close()
218 fd.close()
219 except (OSError, IOError), e:
219 except (OSError, IOError), e:
220 if e.errno != errno.ENOENT:
220 if e.errno != errno.ENOENT:
221 raise
221 raise
222 nlink = 0
222 nlink = 0
223 if not os.path.isdir(dirname):
223 if not os.path.isdir(dirname):
224 util.makedirs(dirname, self.createmode)
224 util.makedirs(dirname, self.createmode)
225 if nlink > 0:
225 if nlink > 0:
226 if self._trustnlink is None:
226 if self._trustnlink is None:
227 self._trustnlink = nlink > 1 or util.checknlink(f)
227 self._trustnlink = nlink > 1 or util.checknlink(f)
228 if nlink > 1 or not self._trustnlink:
228 if nlink > 1 or not self._trustnlink:
229 util.rename(util.mktempcopy(f), f)
229 util.rename(util.mktempcopy(f), f)
230 fp = util.posixfile(f, mode)
230 fp = util.posixfile(f, mode)
231 if nlink == 0:
231 if nlink == 0:
232 self._fixfilemode(f)
232 self._fixfilemode(f)
233 return fp
233 return fp
234
234
235 def symlink(self, src, dst):
235 def symlink(self, src, dst):
236 self.auditor(dst)
236 self.auditor(dst)
237 linkname = os.path.join(self.base, dst)
237 linkname = os.path.join(self.base, dst)
238 try:
238 try:
239 os.unlink(linkname)
239 os.unlink(linkname)
240 except OSError:
240 except OSError:
241 pass
241 pass
242
242
243 dirname = os.path.dirname(linkname)
243 dirname = os.path.dirname(linkname)
244 if not os.path.exists(dirname):
244 if not os.path.exists(dirname):
245 util.makedirs(dirname, self.createmode)
245 util.makedirs(dirname, self.createmode)
246
246
247 if self._cansymlink:
247 if self._cansymlink:
248 try:
248 try:
249 os.symlink(src, linkname)
249 os.symlink(src, linkname)
250 except OSError, err:
250 except OSError, err:
251 raise OSError(err.errno, _('could not symlink to %r: %s') %
251 raise OSError(err.errno, _('could not symlink to %r: %s') %
252 (src, err.strerror), linkname)
252 (src, err.strerror), linkname)
253 else:
253 else:
254 f = self(dst, "w")
254 f = self(dst, "w")
255 f.write(src)
255 f.write(src)
256 f.close()
256 f.close()
257 self._fixfilemode(dst)
257 self._fixfilemode(dst)
258
258
259 def audit(self, path):
259 def audit(self, path):
260 self.auditor(path)
260 self.auditor(path)
261
261
262 class filteropener(abstractopener):
262 class filteropener(abstractopener):
263 '''Wrapper opener for filtering filenames with a function.'''
263 '''Wrapper opener for filtering filenames with a function.'''
264
264
265 def __init__(self, opener, filter):
265 def __init__(self, opener, filter):
266 self._filter = filter
266 self._filter = filter
267 self._orig = opener
267 self._orig = opener
268
268
269 def __call__(self, path, *args, **kwargs):
269 def __call__(self, path, *args, **kwargs):
270 return self._orig(self._filter(path), *args, **kwargs)
270 return self._orig(self._filter(path), *args, **kwargs)
271
271
272 def canonpath(root, cwd, myname, auditor=None):
272 def canonpath(root, cwd, myname, auditor=None):
273 '''return the canonical path of myname, given cwd and root'''
273 '''return the canonical path of myname, given cwd and root'''
274 if util.endswithsep(root):
274 if util.endswithsep(root):
275 rootsep = root
275 rootsep = root
276 else:
276 else:
277 rootsep = root + os.sep
277 rootsep = root + os.sep
278 name = myname
278 name = myname
279 if not os.path.isabs(name):
279 if not os.path.isabs(name):
280 name = os.path.join(root, cwd, name)
280 name = os.path.join(root, cwd, name)
281 name = os.path.normpath(name)
281 name = os.path.normpath(name)
282 if auditor is None:
282 if auditor is None:
283 auditor = pathauditor(root)
283 auditor = pathauditor(root)
284 if name != rootsep and name.startswith(rootsep):
284 if name != rootsep and name.startswith(rootsep):
285 name = name[len(rootsep):]
285 name = name[len(rootsep):]
286 auditor(name)
286 auditor(name)
287 return util.pconvert(name)
287 return util.pconvert(name)
288 elif name == root:
288 elif name == root:
289 return ''
289 return ''
290 else:
290 else:
291 # Determine whether `name' is in the hierarchy at or beneath `root',
291 # Determine whether `name' is in the hierarchy at or beneath `root',
292 # by iterating name=dirname(name) until that causes no change (can't
292 # by iterating name=dirname(name) until that causes no change (can't
293 # check name == '/', because that doesn't work on windows). For each
293 # check name == '/', because that doesn't work on windows). For each
294 # `name', compare dev/inode numbers. If they match, the list `rel'
294 # `name', compare dev/inode numbers. If they match, the list `rel'
295 # holds the reversed list of components making up the relative file
295 # holds the reversed list of components making up the relative file
296 # name we want.
296 # name we want.
297 root_st = os.stat(root)
297 root_st = os.stat(root)
298 rel = []
298 rel = []
299 while True:
299 while True:
300 try:
300 try:
301 name_st = os.stat(name)
301 name_st = os.stat(name)
302 except OSError:
302 except OSError:
303 break
303 break
304 if util.samestat(name_st, root_st):
304 if util.samestat(name_st, root_st):
305 if not rel:
305 if not rel:
306 # name was actually the same as root (maybe a symlink)
306 # name was actually the same as root (maybe a symlink)
307 return ''
307 return ''
308 rel.reverse()
308 rel.reverse()
309 name = os.path.join(*rel)
309 name = os.path.join(*rel)
310 auditor(name)
310 auditor(name)
311 return util.pconvert(name)
311 return util.pconvert(name)
312 dirname, basename = os.path.split(name)
312 dirname, basename = os.path.split(name)
313 rel.append(basename)
313 rel.append(basename)
314 if dirname == name:
314 if dirname == name:
315 break
315 break
316 name = dirname
316 name = dirname
317
317
318 raise util.Abort('%s not under root' % myname)
318 raise util.Abort('%s not under root' % myname)
319
319
320 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
320 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
321 '''yield every hg repository under path, recursively.'''
321 '''yield every hg repository under path, recursively.'''
322 def errhandler(err):
322 def errhandler(err):
323 if err.filename == path:
323 if err.filename == path:
324 raise err
324 raise err
325 if followsym and hasattr(os.path, 'samestat'):
325 if followsym and hasattr(os.path, 'samestat'):
326 def adddir(dirlst, dirname):
326 def adddir(dirlst, dirname):
327 match = False
327 match = False
328 samestat = os.path.samestat
328 samestat = os.path.samestat
329 dirstat = os.stat(dirname)
329 dirstat = os.stat(dirname)
330 for lstdirstat in dirlst:
330 for lstdirstat in dirlst:
331 if samestat(dirstat, lstdirstat):
331 if samestat(dirstat, lstdirstat):
332 match = True
332 match = True
333 break
333 break
334 if not match:
334 if not match:
335 dirlst.append(dirstat)
335 dirlst.append(dirstat)
336 return not match
336 return not match
337 else:
337 else:
338 followsym = False
338 followsym = False
339
339
340 if (seen_dirs is None) and followsym:
340 if (seen_dirs is None) and followsym:
341 seen_dirs = []
341 seen_dirs = []
342 adddir(seen_dirs, path)
342 adddir(seen_dirs, path)
343 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
343 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
344 dirs.sort()
344 dirs.sort()
345 if '.hg' in dirs:
345 if '.hg' in dirs:
346 yield root # found a repository
346 yield root # found a repository
347 qroot = os.path.join(root, '.hg', 'patches')
347 qroot = os.path.join(root, '.hg', 'patches')
348 if os.path.isdir(os.path.join(qroot, '.hg')):
348 if os.path.isdir(os.path.join(qroot, '.hg')):
349 yield qroot # we have a patch queue repo here
349 yield qroot # we have a patch queue repo here
350 if recurse:
350 if recurse:
351 # avoid recursing inside the .hg directory
351 # avoid recursing inside the .hg directory
352 dirs.remove('.hg')
352 dirs.remove('.hg')
353 else:
353 else:
354 dirs[:] = [] # don't descend further
354 dirs[:] = [] # don't descend further
355 elif followsym:
355 elif followsym:
356 newdirs = []
356 newdirs = []
357 for d in dirs:
357 for d in dirs:
358 fname = os.path.join(root, d)
358 fname = os.path.join(root, d)
359 if adddir(seen_dirs, fname):
359 if adddir(seen_dirs, fname):
360 if os.path.islink(fname):
360 if os.path.islink(fname):
361 for hgname in walkrepos(fname, True, seen_dirs):
361 for hgname in walkrepos(fname, True, seen_dirs):
362 yield hgname
362 yield hgname
363 else:
363 else:
364 newdirs.append(d)
364 newdirs.append(d)
365 dirs[:] = newdirs
365 dirs[:] = newdirs
366
366
367 def osrcpath():
367 def osrcpath():
368 '''return default os-specific hgrc search path'''
368 '''return default os-specific hgrc search path'''
369 path = systemrcpath()
369 path = systemrcpath()
370 path.extend(userrcpath())
370 path.extend(userrcpath())
371 path = [os.path.normpath(f) for f in path]
371 path = [os.path.normpath(f) for f in path]
372 return path
372 return path
373
373
374 _rcpath = None
374 _rcpath = None
375
375
376 def rcpath():
376 def rcpath():
377 '''return hgrc search path. if env var HGRCPATH is set, use it.
377 '''return hgrc search path. if env var HGRCPATH is set, use it.
378 for each item in path, if directory, use files ending in .rc,
378 for each item in path, if directory, use files ending in .rc,
379 else use item.
379 else use item.
380 make HGRCPATH empty to only look in .hg/hgrc of current repo.
380 make HGRCPATH empty to only look in .hg/hgrc of current repo.
381 if no HGRCPATH, use default os-specific path.'''
381 if no HGRCPATH, use default os-specific path.'''
382 global _rcpath
382 global _rcpath
383 if _rcpath is None:
383 if _rcpath is None:
384 if 'HGRCPATH' in os.environ:
384 if 'HGRCPATH' in os.environ:
385 _rcpath = []
385 _rcpath = []
386 for p in os.environ['HGRCPATH'].split(os.pathsep):
386 for p in os.environ['HGRCPATH'].split(os.pathsep):
387 if not p:
387 if not p:
388 continue
388 continue
389 p = util.expandpath(p)
389 p = util.expandpath(p)
390 if os.path.isdir(p):
390 if os.path.isdir(p):
391 for f, kind in osutil.listdir(p):
391 for f, kind in osutil.listdir(p):
392 if f.endswith('.rc'):
392 if f.endswith('.rc'):
393 _rcpath.append(os.path.join(p, f))
393 _rcpath.append(os.path.join(p, f))
394 else:
394 else:
395 _rcpath.append(p)
395 _rcpath.append(p)
396 else:
396 else:
397 _rcpath = osrcpath()
397 _rcpath = osrcpath()
398 return _rcpath
398 return _rcpath
399
399
400 if os.name != 'nt':
400 if os.name != 'nt':
401
401
402 def rcfiles(path):
402 def rcfiles(path):
403 rcs = [os.path.join(path, 'hgrc')]
403 rcs = [os.path.join(path, 'hgrc')]
404 rcdir = os.path.join(path, 'hgrc.d')
404 rcdir = os.path.join(path, 'hgrc.d')
405 try:
405 try:
406 rcs.extend([os.path.join(rcdir, f)
406 rcs.extend([os.path.join(rcdir, f)
407 for f, kind in osutil.listdir(rcdir)
407 for f, kind in osutil.listdir(rcdir)
408 if f.endswith(".rc")])
408 if f.endswith(".rc")])
409 except OSError:
409 except OSError:
410 pass
410 pass
411 return rcs
411 return rcs
412
412
413 def systemrcpath():
413 def systemrcpath():
414 path = []
414 path = []
415 # old mod_python does not set sys.argv
415 # old mod_python does not set sys.argv
416 if len(getattr(sys, 'argv', [])) > 0:
416 if len(getattr(sys, 'argv', [])) > 0:
417 p = os.path.dirname(os.path.dirname(sys.argv[0]))
417 p = os.path.dirname(os.path.dirname(sys.argv[0]))
418 path.extend(rcfiles(os.path.join(p, 'etc/mercurial')))
418 path.extend(rcfiles(os.path.join(p, 'etc/mercurial')))
419 path.extend(rcfiles('/etc/mercurial'))
419 path.extend(rcfiles('/etc/mercurial'))
420 return path
420 return path
421
421
422 def userrcpath():
422 def userrcpath():
423 return [os.path.expanduser('~/.hgrc')]
423 return [os.path.expanduser('~/.hgrc')]
424
424
425 else:
425 else:
426
426
427 _HKEY_LOCAL_MACHINE = 0x80000002L
427 _HKEY_LOCAL_MACHINE = 0x80000002L
428
428
429 def systemrcpath():
429 def systemrcpath():
430 '''return default os-specific hgrc search path'''
430 '''return default os-specific hgrc search path'''
431 rcpath = []
431 rcpath = []
432 filename = util.executablepath()
432 filename = util.executablepath()
433 # Use mercurial.ini found in directory with hg.exe
433 # Use mercurial.ini found in directory with hg.exe
434 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
434 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
435 if os.path.isfile(progrc):
435 if os.path.isfile(progrc):
436 rcpath.append(progrc)
436 rcpath.append(progrc)
437 return rcpath
437 return rcpath
438 # Use hgrc.d found in directory with hg.exe
438 # Use hgrc.d found in directory with hg.exe
439 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
439 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
440 if os.path.isdir(progrcd):
440 if os.path.isdir(progrcd):
441 for f, kind in osutil.listdir(progrcd):
441 for f, kind in osutil.listdir(progrcd):
442 if f.endswith('.rc'):
442 if f.endswith('.rc'):
443 rcpath.append(os.path.join(progrcd, f))
443 rcpath.append(os.path.join(progrcd, f))
444 return rcpath
444 return rcpath
445 # else look for a system rcpath in the registry
445 # else look for a system rcpath in the registry
446 value = util.lookupreg('SOFTWARE\\Mercurial', None,
446 value = util.lookupreg('SOFTWARE\\Mercurial', None,
447 _HKEY_LOCAL_MACHINE)
447 _HKEY_LOCAL_MACHINE)
448 if not isinstance(value, str) or not value:
448 if not isinstance(value, str) or not value:
449 return rcpath
449 return rcpath
450 value = value.replace('/', os.sep)
450 value = value.replace('/', os.sep)
451 for p in value.split(os.pathsep):
451 for p in value.split(os.pathsep):
452 if p.lower().endswith('mercurial.ini'):
452 if p.lower().endswith('mercurial.ini'):
453 rcpath.append(p)
453 rcpath.append(p)
454 elif os.path.isdir(p):
454 elif os.path.isdir(p):
455 for f, kind in osutil.listdir(p):
455 for f, kind in osutil.listdir(p):
456 if f.endswith('.rc'):
456 if f.endswith('.rc'):
457 rcpath.append(os.path.join(p, f))
457 rcpath.append(os.path.join(p, f))
458 return rcpath
458 return rcpath
459
459
460 def userrcpath():
460 def userrcpath():
461 '''return os-specific hgrc search path to the user dir'''
461 '''return os-specific hgrc search path to the user dir'''
462 home = os.path.expanduser('~')
462 home = os.path.expanduser('~')
463 path = [os.path.join(home, 'mercurial.ini'),
463 path = [os.path.join(home, 'mercurial.ini'),
464 os.path.join(home, '.hgrc')]
464 os.path.join(home, '.hgrc')]
465 userprofile = os.environ.get('USERPROFILE')
465 userprofile = os.environ.get('USERPROFILE')
466 if userprofile:
466 if userprofile:
467 path.append(os.path.join(userprofile, 'mercurial.ini'))
467 path.append(os.path.join(userprofile, 'mercurial.ini'))
468 path.append(os.path.join(userprofile, '.hgrc'))
468 path.append(os.path.join(userprofile, '.hgrc'))
469 return path
469 return path
470
470
471 def revsingle(repo, revspec, default='.'):
471 def revsingle(repo, revspec, default='.'):
472 if not revspec:
472 if not revspec:
473 return repo[default]
473 return repo[default]
474
474
475 l = revrange(repo, [revspec])
475 l = revrange(repo, [revspec])
476 if len(l) < 1:
476 if len(l) < 1:
477 raise util.Abort(_('empty revision set'))
477 raise util.Abort(_('empty revision set'))
478 return repo[l[-1]]
478 return repo[l[-1]]
479
479
480 def revpair(repo, revs):
480 def revpair(repo, revs):
481 if not revs:
481 if not revs:
482 return repo.dirstate.p1(), None
482 return repo.dirstate.p1(), None
483
483
484 l = revrange(repo, revs)
484 l = revrange(repo, revs)
485
485
486 if len(l) == 0:
486 if len(l) == 0:
487 return repo.dirstate.p1(), None
487 return repo.dirstate.p1(), None
488
488
489 if len(l) == 1:
489 if len(l) == 1:
490 return repo.lookup(l[0]), None
490 return repo.lookup(l[0]), None
491
491
492 return repo.lookup(l[0]), repo.lookup(l[-1])
492 return repo.lookup(l[0]), repo.lookup(l[-1])
493
493
494 _revrangesep = ':'
494 _revrangesep = ':'
495
495
496 def revrange(repo, revs):
496 def revrange(repo, revs):
497 """Yield revision as strings from a list of revision specifications."""
497 """Yield revision as strings from a list of revision specifications."""
498
498
499 def revfix(repo, val, defval):
499 def revfix(repo, val, defval):
500 if not val and val != 0 and defval is not None:
500 if not val and val != 0 and defval is not None:
501 return defval
501 return defval
502 return repo.changelog.rev(repo.lookup(val))
502 return repo.changelog.rev(repo.lookup(val))
503
503
504 seen, l = set(), []
504 seen, l = set(), []
505 for spec in revs:
505 for spec in revs:
506 # attempt to parse old-style ranges first to deal with
506 # attempt to parse old-style ranges first to deal with
507 # things like old-tag which contain query metacharacters
507 # things like old-tag which contain query metacharacters
508 try:
508 try:
509 if isinstance(spec, int):
509 if isinstance(spec, int):
510 seen.add(spec)
510 seen.add(spec)
511 l.append(spec)
511 l.append(spec)
512 continue
512 continue
513
513
514 if _revrangesep in spec:
514 if _revrangesep in spec:
515 start, end = spec.split(_revrangesep, 1)
515 start, end = spec.split(_revrangesep, 1)
516 start = revfix(repo, start, 0)
516 start = revfix(repo, start, 0)
517 end = revfix(repo, end, len(repo) - 1)
517 end = revfix(repo, end, len(repo) - 1)
518 step = start > end and -1 or 1
518 step = start > end and -1 or 1
519 for rev in xrange(start, end + step, step):
519 for rev in xrange(start, end + step, step):
520 if rev in seen:
520 if rev in seen:
521 continue
521 continue
522 seen.add(rev)
522 seen.add(rev)
523 l.append(rev)
523 l.append(rev)
524 continue
524 continue
525 elif spec and spec in repo: # single unquoted rev
525 elif spec and spec in repo: # single unquoted rev
526 rev = revfix(repo, spec, None)
526 rev = revfix(repo, spec, None)
527 if rev in seen:
527 if rev in seen:
528 continue
528 continue
529 seen.add(rev)
529 seen.add(rev)
530 l.append(rev)
530 l.append(rev)
531 continue
531 continue
532 except error.RepoLookupError:
532 except error.RepoLookupError:
533 pass
533 pass
534
534
535 # fall through to new-style queries if old-style fails
535 # fall through to new-style queries if old-style fails
536 m = revset.match(repo.ui, spec)
536 m = revset.match(repo.ui, spec)
537 for r in m(repo, range(len(repo))):
537 for r in m(repo, range(len(repo))):
538 if r not in seen:
538 if r not in seen:
539 l.append(r)
539 l.append(r)
540 seen.update(l)
540 seen.update(l)
541
541
542 return l
542 return l
543
543
544 def expandpats(pats):
544 def expandpats(pats):
545 if not util.expandglobs:
545 if not util.expandglobs:
546 return list(pats)
546 return list(pats)
547 ret = []
547 ret = []
548 for p in pats:
548 for p in pats:
549 kind, name = matchmod._patsplit(p, None)
549 kind, name = matchmod._patsplit(p, None)
550 if kind is None:
550 if kind is None:
551 try:
551 try:
552 globbed = glob.glob(name)
552 globbed = glob.glob(name)
553 except re.error:
553 except re.error:
554 globbed = [name]
554 globbed = [name]
555 if globbed:
555 if globbed:
556 ret.extend(globbed)
556 ret.extend(globbed)
557 continue
557 continue
558 ret.append(p)
558 ret.append(p)
559 return ret
559 return ret
560
560
561 def match(ctxorrepo, pats=[], opts={}, globbed=False, default='relpath'):
561 def match(ctxorrepo, pats=[], opts={}, globbed=False, default='relpath'):
562 if pats == ("",):
562 if pats == ("",):
563 pats = []
563 pats = []
564 if not globbed and default == 'relpath':
564 if not globbed and default == 'relpath':
565 pats = expandpats(pats or [])
565 pats = expandpats(pats or [])
566
566
567 if hasattr(ctxorrepo, 'match'):
567 if hasattr(ctxorrepo, 'match'):
568 ctx = ctxorrepo
568 ctx = ctxorrepo
569 else:
569 else:
570 # will eventually abort here
570 # will eventually abort here
571 ctx = ctxorrepo[None]
571 ctx = ctxorrepo[None]
572
572
573 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
573 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
574 default)
574 default)
575 def badfn(f, msg):
575 def badfn(f, msg):
576 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
576 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
577 m.bad = badfn
577 m.bad = badfn
578 return m
578 return m
579
579
580 def matchall(repo):
580 def matchall(repo):
581 return matchmod.always(repo.root, repo.getcwd())
581 return matchmod.always(repo.root, repo.getcwd())
582
582
583 def matchfiles(repo, files):
583 def matchfiles(repo, files):
584 return matchmod.exact(repo.root, repo.getcwd(), files)
584 return matchmod.exact(repo.root, repo.getcwd(), files)
585
585
586 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
586 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
587 if dry_run is None:
587 if dry_run is None:
588 dry_run = opts.get('dry_run')
588 dry_run = opts.get('dry_run')
589 if similarity is None:
589 if similarity is None:
590 similarity = float(opts.get('similarity') or 0)
590 similarity = float(opts.get('similarity') or 0)
591 # we'd use status here, except handling of symlinks and ignore is tricky
591 # we'd use status here, except handling of symlinks and ignore is tricky
592 added, unknown, deleted, removed = [], [], [], []
592 added, unknown, deleted, removed = [], [], [], []
593 audit_path = pathauditor(repo.root)
593 audit_path = pathauditor(repo.root)
594 m = match(repo, pats, opts)
594 m = match(repo[None], pats, opts)
595 for abs in repo.walk(m):
595 for abs in repo.walk(m):
596 target = repo.wjoin(abs)
596 target = repo.wjoin(abs)
597 good = True
597 good = True
598 try:
598 try:
599 audit_path(abs)
599 audit_path(abs)
600 except (OSError, util.Abort):
600 except (OSError, util.Abort):
601 good = False
601 good = False
602 rel = m.rel(abs)
602 rel = m.rel(abs)
603 exact = m.exact(abs)
603 exact = m.exact(abs)
604 if good and abs not in repo.dirstate:
604 if good and abs not in repo.dirstate:
605 unknown.append(abs)
605 unknown.append(abs)
606 if repo.ui.verbose or not exact:
606 if repo.ui.verbose or not exact:
607 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
607 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
608 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
608 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
609 or (os.path.isdir(target) and not os.path.islink(target))):
609 or (os.path.isdir(target) and not os.path.islink(target))):
610 deleted.append(abs)
610 deleted.append(abs)
611 if repo.ui.verbose or not exact:
611 if repo.ui.verbose or not exact:
612 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
612 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
613 # for finding renames
613 # for finding renames
614 elif repo.dirstate[abs] == 'r':
614 elif repo.dirstate[abs] == 'r':
615 removed.append(abs)
615 removed.append(abs)
616 elif repo.dirstate[abs] == 'a':
616 elif repo.dirstate[abs] == 'a':
617 added.append(abs)
617 added.append(abs)
618 copies = {}
618 copies = {}
619 if similarity > 0:
619 if similarity > 0:
620 for old, new, score in similar.findrenames(repo,
620 for old, new, score in similar.findrenames(repo,
621 added + unknown, removed + deleted, similarity):
621 added + unknown, removed + deleted, similarity):
622 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
622 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
623 repo.ui.status(_('recording removal of %s as rename to %s '
623 repo.ui.status(_('recording removal of %s as rename to %s '
624 '(%d%% similar)\n') %
624 '(%d%% similar)\n') %
625 (m.rel(old), m.rel(new), score * 100))
625 (m.rel(old), m.rel(new), score * 100))
626 copies[new] = old
626 copies[new] = old
627
627
628 if not dry_run:
628 if not dry_run:
629 wctx = repo[None]
629 wctx = repo[None]
630 wlock = repo.wlock()
630 wlock = repo.wlock()
631 try:
631 try:
632 wctx.forget(deleted)
632 wctx.forget(deleted)
633 wctx.add(unknown)
633 wctx.add(unknown)
634 for new, old in copies.iteritems():
634 for new, old in copies.iteritems():
635 wctx.copy(old, new)
635 wctx.copy(old, new)
636 finally:
636 finally:
637 wlock.release()
637 wlock.release()
638
638
639 def updatedir(ui, repo, patches, similarity=0):
639 def updatedir(ui, repo, patches, similarity=0):
640 '''Update dirstate after patch application according to metadata'''
640 '''Update dirstate after patch application according to metadata'''
641 if not patches:
641 if not patches:
642 return []
642 return []
643 copies = []
643 copies = []
644 removes = set()
644 removes = set()
645 cfiles = patches.keys()
645 cfiles = patches.keys()
646 cwd = repo.getcwd()
646 cwd = repo.getcwd()
647 if cwd:
647 if cwd:
648 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
648 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
649 for f in patches:
649 for f in patches:
650 gp = patches[f]
650 gp = patches[f]
651 if not gp:
651 if not gp:
652 continue
652 continue
653 if gp.op == 'RENAME':
653 if gp.op == 'RENAME':
654 copies.append((gp.oldpath, gp.path))
654 copies.append((gp.oldpath, gp.path))
655 removes.add(gp.oldpath)
655 removes.add(gp.oldpath)
656 elif gp.op == 'COPY':
656 elif gp.op == 'COPY':
657 copies.append((gp.oldpath, gp.path))
657 copies.append((gp.oldpath, gp.path))
658 elif gp.op == 'DELETE':
658 elif gp.op == 'DELETE':
659 removes.add(gp.path)
659 removes.add(gp.path)
660
660
661 wctx = repo[None]
661 wctx = repo[None]
662 for src, dst in copies:
662 for src, dst in copies:
663 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
663 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
664 if (not similarity) and removes:
664 if (not similarity) and removes:
665 wctx.remove(sorted(removes), True)
665 wctx.remove(sorted(removes), True)
666
666
667 for f in patches:
667 for f in patches:
668 gp = patches[f]
668 gp = patches[f]
669 if gp and gp.mode:
669 if gp and gp.mode:
670 islink, isexec = gp.mode
670 islink, isexec = gp.mode
671 dst = repo.wjoin(gp.path)
671 dst = repo.wjoin(gp.path)
672 # patch won't create empty files
672 # patch won't create empty files
673 if gp.op == 'ADD' and not os.path.lexists(dst):
673 if gp.op == 'ADD' and not os.path.lexists(dst):
674 flags = (isexec and 'x' or '') + (islink and 'l' or '')
674 flags = (isexec and 'x' or '') + (islink and 'l' or '')
675 repo.wwrite(gp.path, '', flags)
675 repo.wwrite(gp.path, '', flags)
676 util.setflags(dst, islink, isexec)
676 util.setflags(dst, islink, isexec)
677 addremove(repo, cfiles, similarity=similarity)
677 addremove(repo, cfiles, similarity=similarity)
678 files = patches.keys()
678 files = patches.keys()
679 files.extend([r for r in removes if r not in files])
679 files.extend([r for r in removes if r not in files])
680 return sorted(files)
680 return sorted(files)
681
681
682 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
682 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
683 """Update the dirstate to reflect the intent of copying src to dst. For
683 """Update the dirstate to reflect the intent of copying src to dst. For
684 different reasons it might not end with dst being marked as copied from src.
684 different reasons it might not end with dst being marked as copied from src.
685 """
685 """
686 origsrc = repo.dirstate.copied(src) or src
686 origsrc = repo.dirstate.copied(src) or src
687 if dst == origsrc: # copying back a copy?
687 if dst == origsrc: # copying back a copy?
688 if repo.dirstate[dst] not in 'mn' and not dryrun:
688 if repo.dirstate[dst] not in 'mn' and not dryrun:
689 repo.dirstate.normallookup(dst)
689 repo.dirstate.normallookup(dst)
690 else:
690 else:
691 if repo.dirstate[origsrc] == 'a' and origsrc == src:
691 if repo.dirstate[origsrc] == 'a' and origsrc == src:
692 if not ui.quiet:
692 if not ui.quiet:
693 ui.warn(_("%s has not been committed yet, so no copy "
693 ui.warn(_("%s has not been committed yet, so no copy "
694 "data will be stored for %s.\n")
694 "data will be stored for %s.\n")
695 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
695 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
696 if repo.dirstate[dst] in '?r' and not dryrun:
696 if repo.dirstate[dst] in '?r' and not dryrun:
697 wctx.add([dst])
697 wctx.add([dst])
698 elif not dryrun:
698 elif not dryrun:
699 wctx.copy(origsrc, dst)
699 wctx.copy(origsrc, dst)
700
700
701 def readrequires(opener, supported):
701 def readrequires(opener, supported):
702 '''Reads and parses .hg/requires and checks if all entries found
702 '''Reads and parses .hg/requires and checks if all entries found
703 are in the list of supported features.'''
703 are in the list of supported features.'''
704 requirements = set(opener.read("requires").splitlines())
704 requirements = set(opener.read("requires").splitlines())
705 for r in requirements:
705 for r in requirements:
706 if r not in supported:
706 if r not in supported:
707 if not r or not r[0].isalnum():
707 if not r or not r[0].isalnum():
708 raise error.RequirementError(_(".hg/requires file is corrupt"))
708 raise error.RequirementError(_(".hg/requires file is corrupt"))
709 raise error.RequirementError(_("unknown repository format: "
709 raise error.RequirementError(_("unknown repository format: "
710 "requires feature '%s' (upgrade Mercurial)") % r)
710 "requires feature '%s' (upgrade Mercurial)") % r)
711 return requirements
711 return requirements
@@ -1,46 +1,46 b''
1 # Extension dedicated to test patch.diff() upgrade modes
1 # Extension dedicated to test patch.diff() upgrade modes
2 #
2 #
3 #
3 #
4 from mercurial import scmutil, patch, util
4 from mercurial import scmutil, patch, util
5
5
6 def autodiff(ui, repo, *pats, **opts):
6 def autodiff(ui, repo, *pats, **opts):
7 diffopts = patch.diffopts(ui, opts)
7 diffopts = patch.diffopts(ui, opts)
8 git = opts.get('git', 'no')
8 git = opts.get('git', 'no')
9 brokenfiles = set()
9 brokenfiles = set()
10 losedatafn = None
10 losedatafn = None
11 if git in ('yes', 'no'):
11 if git in ('yes', 'no'):
12 diffopts.git = git == 'yes'
12 diffopts.git = git == 'yes'
13 diffopts.upgrade = False
13 diffopts.upgrade = False
14 elif git == 'auto':
14 elif git == 'auto':
15 diffopts.git = False
15 diffopts.git = False
16 diffopts.upgrade = True
16 diffopts.upgrade = True
17 elif git == 'warn':
17 elif git == 'warn':
18 diffopts.git = False
18 diffopts.git = False
19 diffopts.upgrade = True
19 diffopts.upgrade = True
20 def losedatafn(fn=None, **kwargs):
20 def losedatafn(fn=None, **kwargs):
21 brokenfiles.add(fn)
21 brokenfiles.add(fn)
22 return True
22 return True
23 elif git == 'abort':
23 elif git == 'abort':
24 diffopts.git = False
24 diffopts.git = False
25 diffopts.upgrade = True
25 diffopts.upgrade = True
26 def losedatafn(fn=None, **kwargs):
26 def losedatafn(fn=None, **kwargs):
27 raise util.Abort('losing data for %s' % fn)
27 raise util.Abort('losing data for %s' % fn)
28 else:
28 else:
29 raise util.Abort('--git must be yes, no or auto')
29 raise util.Abort('--git must be yes, no or auto')
30
30
31 node1, node2 = scmutil.revpair(repo, [])
31 node1, node2 = scmutil.revpair(repo, [])
32 m = scmutil.match(repo, pats, opts)
32 m = scmutil.match(repo[node2], pats, opts)
33 it = patch.diff(repo, node1, node2, match=m, opts=diffopts,
33 it = patch.diff(repo, node1, node2, match=m, opts=diffopts,
34 losedatafn=losedatafn)
34 losedatafn=losedatafn)
35 for chunk in it:
35 for chunk in it:
36 ui.write(chunk)
36 ui.write(chunk)
37 for fn in sorted(brokenfiles):
37 for fn in sorted(brokenfiles):
38 ui.write('data lost for: %s\n' % fn)
38 ui.write('data lost for: %s\n' % fn)
39
39
40 cmdtable = {
40 cmdtable = {
41 "autodiff":
41 "autodiff":
42 (autodiff,
42 (autodiff,
43 [('', 'git', '', 'git upgrade mode (yes/no/auto/warn/abort)'),
43 [('', 'git', '', 'git upgrade mode (yes/no/auto/warn/abort)'),
44 ],
44 ],
45 '[OPTION]... [FILE]...'),
45 '[OPTION]... [FILE]...'),
46 }
46 }
General Comments 0
You need to be logged in to leave comments. Login now