##// END OF EJS Templates
Use both the from and to name in mdiff.unidiff....
Dustin Sallings -
r5482:e5eedd74 default
parent child Browse files
Show More
@@ -1,200 +1,200 b''
1 # churn.py - create a graph showing who changed the most lines
1 # churn.py - create a graph showing who changed the most lines
2 #
2 #
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7 #
7 #
8 #
8 #
9 # Aliases map file format is simple one alias per line in the following
9 # Aliases map file format is simple one alias per line in the following
10 # format:
10 # format:
11 #
11 #
12 # <alias email> <actual email>
12 # <alias email> <actual email>
13
13
14 from mercurial.i18n import gettext as _
14 from mercurial.i18n import gettext as _
15 from mercurial import hg, mdiff, cmdutil, ui, util, templater, node
15 from mercurial import hg, mdiff, cmdutil, ui, util, templater, node
16 import os, sys
16 import os, sys
17
17
18 def get_tty_width():
18 def get_tty_width():
19 if 'COLUMNS' in os.environ:
19 if 'COLUMNS' in os.environ:
20 try:
20 try:
21 return int(os.environ['COLUMNS'])
21 return int(os.environ['COLUMNS'])
22 except ValueError:
22 except ValueError:
23 pass
23 pass
24 try:
24 try:
25 import termios, array, fcntl
25 import termios, array, fcntl
26 for dev in (sys.stdout, sys.stdin):
26 for dev in (sys.stdout, sys.stdin):
27 try:
27 try:
28 fd = dev.fileno()
28 fd = dev.fileno()
29 if not os.isatty(fd):
29 if not os.isatty(fd):
30 continue
30 continue
31 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
31 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
32 return array.array('h', arri)[1]
32 return array.array('h', arri)[1]
33 except ValueError:
33 except ValueError:
34 pass
34 pass
35 except ImportError:
35 except ImportError:
36 pass
36 pass
37 return 80
37 return 80
38
38
39 def __gather(ui, repo, node1, node2):
39 def __gather(ui, repo, node1, node2):
40 def dirtywork(f, mmap1, mmap2):
40 def dirtywork(f, mmap1, mmap2):
41 lines = 0
41 lines = 0
42
42
43 to = mmap1 and repo.file(f).read(mmap1[f]) or None
43 to = mmap1 and repo.file(f).read(mmap1[f]) or None
44 tn = mmap2 and repo.file(f).read(mmap2[f]) or None
44 tn = mmap2 and repo.file(f).read(mmap2[f]) or None
45
45
46 diff = mdiff.unidiff(to, "", tn, "", f).split("\n")
46 diff = mdiff.unidiff(to, "", tn, "", f, f).split("\n")
47
47
48 for line in diff:
48 for line in diff:
49 if not line:
49 if not line:
50 continue # skip EOF
50 continue # skip EOF
51 if line.startswith(" "):
51 if line.startswith(" "):
52 continue # context line
52 continue # context line
53 if line.startswith("--- ") or line.startswith("+++ "):
53 if line.startswith("--- ") or line.startswith("+++ "):
54 continue # begining of diff
54 continue # begining of diff
55 if line.startswith("@@ "):
55 if line.startswith("@@ "):
56 continue # info line
56 continue # info line
57
57
58 # changed lines
58 # changed lines
59 lines += 1
59 lines += 1
60
60
61 return lines
61 return lines
62
62
63 ##
63 ##
64
64
65 lines = 0
65 lines = 0
66
66
67 changes = repo.status(node1, node2, None, util.always)[:5]
67 changes = repo.status(node1, node2, None, util.always)[:5]
68
68
69 modified, added, removed, deleted, unknown = changes
69 modified, added, removed, deleted, unknown = changes
70
70
71 who = repo.changelog.read(node2)[1]
71 who = repo.changelog.read(node2)[1]
72 who = templater.email(who) # get the email of the person
72 who = templater.email(who) # get the email of the person
73
73
74 mmap1 = repo.manifest.read(repo.changelog.read(node1)[0])
74 mmap1 = repo.manifest.read(repo.changelog.read(node1)[0])
75 mmap2 = repo.manifest.read(repo.changelog.read(node2)[0])
75 mmap2 = repo.manifest.read(repo.changelog.read(node2)[0])
76 for f in modified:
76 for f in modified:
77 lines += dirtywork(f, mmap1, mmap2)
77 lines += dirtywork(f, mmap1, mmap2)
78
78
79 for f in added:
79 for f in added:
80 lines += dirtywork(f, None, mmap2)
80 lines += dirtywork(f, None, mmap2)
81
81
82 for f in removed:
82 for f in removed:
83 lines += dirtywork(f, mmap1, None)
83 lines += dirtywork(f, mmap1, None)
84
84
85 for f in deleted:
85 for f in deleted:
86 lines += dirtywork(f, mmap1, mmap2)
86 lines += dirtywork(f, mmap1, mmap2)
87
87
88 for f in unknown:
88 for f in unknown:
89 lines += dirtywork(f, mmap1, mmap2)
89 lines += dirtywork(f, mmap1, mmap2)
90
90
91 return (who, lines)
91 return (who, lines)
92
92
93 def gather_stats(ui, repo, amap, revs=None, progress=False):
93 def gather_stats(ui, repo, amap, revs=None, progress=False):
94 stats = {}
94 stats = {}
95
95
96 cl = repo.changelog
96 cl = repo.changelog
97
97
98 if not revs:
98 if not revs:
99 revs = range(0, cl.count())
99 revs = range(0, cl.count())
100
100
101 nr_revs = len(revs)
101 nr_revs = len(revs)
102 cur_rev = 0
102 cur_rev = 0
103
103
104 for rev in revs:
104 for rev in revs:
105 cur_rev += 1 # next revision
105 cur_rev += 1 # next revision
106
106
107 node2 = cl.node(rev)
107 node2 = cl.node(rev)
108 node1 = cl.parents(node2)[0]
108 node1 = cl.parents(node2)[0]
109
109
110 if cl.parents(node2)[1] != node.nullid:
110 if cl.parents(node2)[1] != node.nullid:
111 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
111 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
112 continue
112 continue
113
113
114 who, lines = __gather(ui, repo, node1, node2)
114 who, lines = __gather(ui, repo, node1, node2)
115
115
116 # remap the owner if possible
116 # remap the owner if possible
117 if amap.has_key(who):
117 if amap.has_key(who):
118 ui.note("using '%s' alias for '%s'\n" % (amap[who], who))
118 ui.note("using '%s' alias for '%s'\n" % (amap[who], who))
119 who = amap[who]
119 who = amap[who]
120
120
121 if not stats.has_key(who):
121 if not stats.has_key(who):
122 stats[who] = 0
122 stats[who] = 0
123 stats[who] += lines
123 stats[who] += lines
124
124
125 ui.note("rev %d: %d lines by %s\n" % (rev, lines, who))
125 ui.note("rev %d: %d lines by %s\n" % (rev, lines, who))
126
126
127 if progress:
127 if progress:
128 if int(100.0*(cur_rev - 1)/nr_revs) < int(100.0*cur_rev/nr_revs):
128 if int(100.0*(cur_rev - 1)/nr_revs) < int(100.0*cur_rev/nr_revs):
129 ui.write("%d%%.." % (int(100.0*cur_rev/nr_revs),))
129 ui.write("%d%%.." % (int(100.0*cur_rev/nr_revs),))
130 sys.stdout.flush()
130 sys.stdout.flush()
131
131
132 if progress:
132 if progress:
133 ui.write("done\n")
133 ui.write("done\n")
134 sys.stdout.flush()
134 sys.stdout.flush()
135
135
136 return stats
136 return stats
137
137
138 def churn(ui, repo, **opts):
138 def churn(ui, repo, **opts):
139 "Graphs the number of lines changed"
139 "Graphs the number of lines changed"
140
140
141 def pad(s, l):
141 def pad(s, l):
142 if len(s) < l:
142 if len(s) < l:
143 return s + " " * (l-len(s))
143 return s + " " * (l-len(s))
144 return s[0:l]
144 return s[0:l]
145
145
146 def graph(n, maximum, width, char):
146 def graph(n, maximum, width, char):
147 n = int(n * width / float(maximum))
147 n = int(n * width / float(maximum))
148
148
149 return char * (n)
149 return char * (n)
150
150
151 def get_aliases(f):
151 def get_aliases(f):
152 aliases = {}
152 aliases = {}
153
153
154 for l in f.readlines():
154 for l in f.readlines():
155 l = l.strip()
155 l = l.strip()
156 alias, actual = l.split(" ")
156 alias, actual = l.split(" ")
157 aliases[alias] = actual
157 aliases[alias] = actual
158
158
159 return aliases
159 return aliases
160
160
161 amap = {}
161 amap = {}
162 aliases = opts.get('aliases')
162 aliases = opts.get('aliases')
163 if aliases:
163 if aliases:
164 try:
164 try:
165 f = open(aliases,"r")
165 f = open(aliases,"r")
166 except OSError, e:
166 except OSError, e:
167 print "Error: " + e
167 print "Error: " + e
168 return
168 return
169
169
170 amap = get_aliases(f)
170 amap = get_aliases(f)
171 f.close()
171 f.close()
172
172
173 revs = [int(r) for r in cmdutil.revrange(repo, opts['rev'])]
173 revs = [int(r) for r in cmdutil.revrange(repo, opts['rev'])]
174 revs.sort()
174 revs.sort()
175 stats = gather_stats(ui, repo, amap, revs, opts.get('progress'))
175 stats = gather_stats(ui, repo, amap, revs, opts.get('progress'))
176
176
177 # make a list of tuples (name, lines) and sort it in descending order
177 # make a list of tuples (name, lines) and sort it in descending order
178 ordered = stats.items()
178 ordered = stats.items()
179 ordered.sort(lambda x, y: cmp(y[1], x[1]))
179 ordered.sort(lambda x, y: cmp(y[1], x[1]))
180
180
181 maximum = ordered[0][1]
181 maximum = ordered[0][1]
182
182
183 width = get_tty_width()
183 width = get_tty_width()
184 ui.note(_("assuming %i character terminal\n") % width)
184 ui.note(_("assuming %i character terminal\n") % width)
185 width -= 1
185 width -= 1
186
186
187 for i in ordered:
187 for i in ordered:
188 person = i[0]
188 person = i[0]
189 lines = i[1]
189 lines = i[1]
190 print "%s %6d %s" % (pad(person, 20), lines,
190 print "%s %6d %s" % (pad(person, 20), lines,
191 graph(lines, maximum, width - 20 - 1 - 6 - 2 - 2, '*'))
191 graph(lines, maximum, width - 20 - 1 - 6 - 2 - 2, '*'))
192
192
193 cmdtable = {
193 cmdtable = {
194 "churn":
194 "churn":
195 (churn,
195 (churn,
196 [('r', 'rev', [], _('limit statistics to the specified revisions')),
196 [('r', 'rev', [], _('limit statistics to the specified revisions')),
197 ('', 'aliases', '', _('file with email aliases')),
197 ('', 'aliases', '', _('file with email aliases')),
198 ('', 'progress', None, _('show progress'))],
198 ('', 'progress', None, _('show progress'))],
199 'hg churn [-r revision range] [-a file] [--progress]'),
199 'hg churn [-r revision range] [-a file] [--progress]'),
200 }
200 }
@@ -1,3165 +1,3166 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from i18n import _
9 from i18n import _
10 import os, re, sys, urllib
10 import os, re, sys, urllib
11 import hg, util, revlog, bundlerepo, extensions
11 import hg, util, revlog, bundlerepo, extensions
12 import difflib, patch, time, help, mdiff, tempfile
12 import difflib, patch, time, help, mdiff, tempfile
13 import errno, version, socket
13 import errno, version, socket
14 import archival, changegroup, cmdutil, hgweb.server, sshserver
14 import archival, changegroup, cmdutil, hgweb.server, sshserver
15
15
16 # Commands start here, listed alphabetically
16 # Commands start here, listed alphabetically
17
17
18 def add(ui, repo, *pats, **opts):
18 def add(ui, repo, *pats, **opts):
19 """add the specified files on the next commit
19 """add the specified files on the next commit
20
20
21 Schedule files to be version controlled and added to the repository.
21 Schedule files to be version controlled and added to the repository.
22
22
23 The files will be added to the repository at the next commit. To
23 The files will be added to the repository at the next commit. To
24 undo an add before that, see hg revert.
24 undo an add before that, see hg revert.
25
25
26 If no names are given, add all files in the repository.
26 If no names are given, add all files in the repository.
27 """
27 """
28
28
29 names = []
29 names = []
30 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
30 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
31 if exact:
31 if exact:
32 if ui.verbose:
32 if ui.verbose:
33 ui.status(_('adding %s\n') % rel)
33 ui.status(_('adding %s\n') % rel)
34 names.append(abs)
34 names.append(abs)
35 elif abs not in repo.dirstate:
35 elif abs not in repo.dirstate:
36 ui.status(_('adding %s\n') % rel)
36 ui.status(_('adding %s\n') % rel)
37 names.append(abs)
37 names.append(abs)
38 if not opts.get('dry_run'):
38 if not opts.get('dry_run'):
39 repo.add(names)
39 repo.add(names)
40
40
41 def addremove(ui, repo, *pats, **opts):
41 def addremove(ui, repo, *pats, **opts):
42 """add all new files, delete all missing files
42 """add all new files, delete all missing files
43
43
44 Add all new files and remove all missing files from the repository.
44 Add all new files and remove all missing files from the repository.
45
45
46 New files are ignored if they match any of the patterns in .hgignore. As
46 New files are ignored if they match any of the patterns in .hgignore. As
47 with add, these changes take effect at the next commit.
47 with add, these changes take effect at the next commit.
48
48
49 Use the -s option to detect renamed files. With a parameter > 0,
49 Use the -s option to detect renamed files. With a parameter > 0,
50 this compares every removed file with every added file and records
50 this compares every removed file with every added file and records
51 those similar enough as renames. This option takes a percentage
51 those similar enough as renames. This option takes a percentage
52 between 0 (disabled) and 100 (files must be identical) as its
52 between 0 (disabled) and 100 (files must be identical) as its
53 parameter. Detecting renamed files this way can be expensive.
53 parameter. Detecting renamed files this way can be expensive.
54 """
54 """
55 try:
55 try:
56 sim = float(opts.get('similarity') or 0)
56 sim = float(opts.get('similarity') or 0)
57 except ValueError:
57 except ValueError:
58 raise util.Abort(_('similarity must be a number'))
58 raise util.Abort(_('similarity must be a number'))
59 if sim < 0 or sim > 100:
59 if sim < 0 or sim > 100:
60 raise util.Abort(_('similarity must be between 0 and 100'))
60 raise util.Abort(_('similarity must be between 0 and 100'))
61 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
61 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
62
62
63 def annotate(ui, repo, *pats, **opts):
63 def annotate(ui, repo, *pats, **opts):
64 """show changeset information per file line
64 """show changeset information per file line
65
65
66 List changes in files, showing the revision id responsible for each line
66 List changes in files, showing the revision id responsible for each line
67
67
68 This command is useful to discover who did a change or when a change took
68 This command is useful to discover who did a change or when a change took
69 place.
69 place.
70
70
71 Without the -a option, annotate will avoid processing files it
71 Without the -a option, annotate will avoid processing files it
72 detects as binary. With -a, annotate will generate an annotation
72 detects as binary. With -a, annotate will generate an annotation
73 anyway, probably with undesirable results.
73 anyway, probably with undesirable results.
74 """
74 """
75 getdate = util.cachefunc(lambda x: util.datestr(x[0].date()))
75 getdate = util.cachefunc(lambda x: util.datestr(x[0].date()))
76
76
77 if not pats:
77 if not pats:
78 raise util.Abort(_('at least one file name or pattern required'))
78 raise util.Abort(_('at least one file name or pattern required'))
79
79
80 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
80 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
81 ('number', lambda x: str(x[0].rev())),
81 ('number', lambda x: str(x[0].rev())),
82 ('changeset', lambda x: short(x[0].node())),
82 ('changeset', lambda x: short(x[0].node())),
83 ('date', getdate),
83 ('date', getdate),
84 ('follow', lambda x: x[0].path()),
84 ('follow', lambda x: x[0].path()),
85 ]
85 ]
86
86
87 if (not opts['user'] and not opts['changeset'] and not opts['date']
87 if (not opts['user'] and not opts['changeset'] and not opts['date']
88 and not opts['follow']):
88 and not opts['follow']):
89 opts['number'] = 1
89 opts['number'] = 1
90
90
91 linenumber = opts.get('line_number') is not None
91 linenumber = opts.get('line_number') is not None
92 if (linenumber and (not opts['changeset']) and (not opts['number'])):
92 if (linenumber and (not opts['changeset']) and (not opts['number'])):
93 raise util.Abort(_('at least one of -n/-c is required for -l'))
93 raise util.Abort(_('at least one of -n/-c is required for -l'))
94
94
95 funcmap = [func for op, func in opmap if opts.get(op)]
95 funcmap = [func for op, func in opmap if opts.get(op)]
96 if linenumber:
96 if linenumber:
97 lastfunc = funcmap[-1]
97 lastfunc = funcmap[-1]
98 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
98 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
99
99
100 ctx = repo.changectx(opts['rev'])
100 ctx = repo.changectx(opts['rev'])
101
101
102 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
102 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
103 node=ctx.node()):
103 node=ctx.node()):
104 fctx = ctx.filectx(abs)
104 fctx = ctx.filectx(abs)
105 if not opts['text'] and util.binary(fctx.data()):
105 if not opts['text'] and util.binary(fctx.data()):
106 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
106 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
107 continue
107 continue
108
108
109 lines = fctx.annotate(follow=opts.get('follow'),
109 lines = fctx.annotate(follow=opts.get('follow'),
110 linenumber=linenumber)
110 linenumber=linenumber)
111 pieces = []
111 pieces = []
112
112
113 for f in funcmap:
113 for f in funcmap:
114 l = [f(n) for n, dummy in lines]
114 l = [f(n) for n, dummy in lines]
115 if l:
115 if l:
116 m = max(map(len, l))
116 m = max(map(len, l))
117 pieces.append(["%*s" % (m, x) for x in l])
117 pieces.append(["%*s" % (m, x) for x in l])
118
118
119 if pieces:
119 if pieces:
120 for p, l in zip(zip(*pieces), lines):
120 for p, l in zip(zip(*pieces), lines):
121 ui.write("%s: %s" % (" ".join(p), l[1]))
121 ui.write("%s: %s" % (" ".join(p), l[1]))
122
122
123 def archive(ui, repo, dest, **opts):
123 def archive(ui, repo, dest, **opts):
124 '''create unversioned archive of a repository revision
124 '''create unversioned archive of a repository revision
125
125
126 By default, the revision used is the parent of the working
126 By default, the revision used is the parent of the working
127 directory; use "-r" to specify a different revision.
127 directory; use "-r" to specify a different revision.
128
128
129 To specify the type of archive to create, use "-t". Valid
129 To specify the type of archive to create, use "-t". Valid
130 types are:
130 types are:
131
131
132 "files" (default): a directory full of files
132 "files" (default): a directory full of files
133 "tar": tar archive, uncompressed
133 "tar": tar archive, uncompressed
134 "tbz2": tar archive, compressed using bzip2
134 "tbz2": tar archive, compressed using bzip2
135 "tgz": tar archive, compressed using gzip
135 "tgz": tar archive, compressed using gzip
136 "uzip": zip archive, uncompressed
136 "uzip": zip archive, uncompressed
137 "zip": zip archive, compressed using deflate
137 "zip": zip archive, compressed using deflate
138
138
139 The exact name of the destination archive or directory is given
139 The exact name of the destination archive or directory is given
140 using a format string; see "hg help export" for details.
140 using a format string; see "hg help export" for details.
141
141
142 Each member added to an archive file has a directory prefix
142 Each member added to an archive file has a directory prefix
143 prepended. Use "-p" to specify a format string for the prefix.
143 prepended. Use "-p" to specify a format string for the prefix.
144 The default is the basename of the archive, with suffixes removed.
144 The default is the basename of the archive, with suffixes removed.
145 '''
145 '''
146
146
147 ctx = repo.changectx(opts['rev'])
147 ctx = repo.changectx(opts['rev'])
148 if not ctx:
148 if not ctx:
149 raise util.Abort(_('repository has no revisions'))
149 raise util.Abort(_('repository has no revisions'))
150 node = ctx.node()
150 node = ctx.node()
151 dest = cmdutil.make_filename(repo, dest, node)
151 dest = cmdutil.make_filename(repo, dest, node)
152 if os.path.realpath(dest) == repo.root:
152 if os.path.realpath(dest) == repo.root:
153 raise util.Abort(_('repository root cannot be destination'))
153 raise util.Abort(_('repository root cannot be destination'))
154 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
154 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
155 kind = opts.get('type') or 'files'
155 kind = opts.get('type') or 'files'
156 prefix = opts['prefix']
156 prefix = opts['prefix']
157 if dest == '-':
157 if dest == '-':
158 if kind == 'files':
158 if kind == 'files':
159 raise util.Abort(_('cannot archive plain files to stdout'))
159 raise util.Abort(_('cannot archive plain files to stdout'))
160 dest = sys.stdout
160 dest = sys.stdout
161 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
161 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
162 prefix = cmdutil.make_filename(repo, prefix, node)
162 prefix = cmdutil.make_filename(repo, prefix, node)
163 archival.archive(repo, dest, node, kind, not opts['no_decode'],
163 archival.archive(repo, dest, node, kind, not opts['no_decode'],
164 matchfn, prefix)
164 matchfn, prefix)
165
165
166 def backout(ui, repo, node=None, rev=None, **opts):
166 def backout(ui, repo, node=None, rev=None, **opts):
167 '''reverse effect of earlier changeset
167 '''reverse effect of earlier changeset
168
168
169 Commit the backed out changes as a new changeset. The new
169 Commit the backed out changes as a new changeset. The new
170 changeset is a child of the backed out changeset.
170 changeset is a child of the backed out changeset.
171
171
172 If you back out a changeset other than the tip, a new head is
172 If you back out a changeset other than the tip, a new head is
173 created. This head is the parent of the working directory. If
173 created. This head is the parent of the working directory. If
174 you back out an old changeset, your working directory will appear
174 you back out an old changeset, your working directory will appear
175 old after the backout. You should merge the backout changeset
175 old after the backout. You should merge the backout changeset
176 with another head.
176 with another head.
177
177
178 The --merge option remembers the parent of the working directory
178 The --merge option remembers the parent of the working directory
179 before starting the backout, then merges the new head with that
179 before starting the backout, then merges the new head with that
180 changeset afterwards. This saves you from doing the merge by
180 changeset afterwards. This saves you from doing the merge by
181 hand. The result of this merge is not committed, as for a normal
181 hand. The result of this merge is not committed, as for a normal
182 merge.'''
182 merge.'''
183 if rev and node:
183 if rev and node:
184 raise util.Abort(_("please specify just one revision"))
184 raise util.Abort(_("please specify just one revision"))
185
185
186 if not rev:
186 if not rev:
187 rev = node
187 rev = node
188
188
189 if not rev:
189 if not rev:
190 raise util.Abort(_("please specify a revision to backout"))
190 raise util.Abort(_("please specify a revision to backout"))
191
191
192 cmdutil.bail_if_changed(repo)
192 cmdutil.bail_if_changed(repo)
193 op1, op2 = repo.dirstate.parents()
193 op1, op2 = repo.dirstate.parents()
194 if op2 != nullid:
194 if op2 != nullid:
195 raise util.Abort(_('outstanding uncommitted merge'))
195 raise util.Abort(_('outstanding uncommitted merge'))
196 node = repo.lookup(rev)
196 node = repo.lookup(rev)
197 p1, p2 = repo.changelog.parents(node)
197 p1, p2 = repo.changelog.parents(node)
198 if p1 == nullid:
198 if p1 == nullid:
199 raise util.Abort(_('cannot back out a change with no parents'))
199 raise util.Abort(_('cannot back out a change with no parents'))
200 if p2 != nullid:
200 if p2 != nullid:
201 if not opts['parent']:
201 if not opts['parent']:
202 raise util.Abort(_('cannot back out a merge changeset without '
202 raise util.Abort(_('cannot back out a merge changeset without '
203 '--parent'))
203 '--parent'))
204 p = repo.lookup(opts['parent'])
204 p = repo.lookup(opts['parent'])
205 if p not in (p1, p2):
205 if p not in (p1, p2):
206 raise util.Abort(_('%s is not a parent of %s') %
206 raise util.Abort(_('%s is not a parent of %s') %
207 (short(p), short(node)))
207 (short(p), short(node)))
208 parent = p
208 parent = p
209 else:
209 else:
210 if opts['parent']:
210 if opts['parent']:
211 raise util.Abort(_('cannot use --parent on non-merge changeset'))
211 raise util.Abort(_('cannot use --parent on non-merge changeset'))
212 parent = p1
212 parent = p1
213 hg.clean(repo, node, show_stats=False)
213 hg.clean(repo, node, show_stats=False)
214 revert_opts = opts.copy()
214 revert_opts = opts.copy()
215 revert_opts['date'] = None
215 revert_opts['date'] = None
216 revert_opts['all'] = True
216 revert_opts['all'] = True
217 revert_opts['rev'] = hex(parent)
217 revert_opts['rev'] = hex(parent)
218 revert(ui, repo, **revert_opts)
218 revert(ui, repo, **revert_opts)
219 commit_opts = opts.copy()
219 commit_opts = opts.copy()
220 commit_opts['addremove'] = False
220 commit_opts['addremove'] = False
221 if not commit_opts['message'] and not commit_opts['logfile']:
221 if not commit_opts['message'] and not commit_opts['logfile']:
222 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
222 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
223 commit_opts['force_editor'] = True
223 commit_opts['force_editor'] = True
224 commit(ui, repo, **commit_opts)
224 commit(ui, repo, **commit_opts)
225 def nice(node):
225 def nice(node):
226 return '%d:%s' % (repo.changelog.rev(node), short(node))
226 return '%d:%s' % (repo.changelog.rev(node), short(node))
227 ui.status(_('changeset %s backs out changeset %s\n') %
227 ui.status(_('changeset %s backs out changeset %s\n') %
228 (nice(repo.changelog.tip()), nice(node)))
228 (nice(repo.changelog.tip()), nice(node)))
229 if op1 != node:
229 if op1 != node:
230 if opts['merge']:
230 if opts['merge']:
231 ui.status(_('merging with changeset %s\n') % nice(op1))
231 ui.status(_('merging with changeset %s\n') % nice(op1))
232 hg.merge(repo, hex(op1))
232 hg.merge(repo, hex(op1))
233 else:
233 else:
234 ui.status(_('the backout changeset is a new head - '
234 ui.status(_('the backout changeset is a new head - '
235 'do not forget to merge\n'))
235 'do not forget to merge\n'))
236 ui.status(_('(use "backout --merge" '
236 ui.status(_('(use "backout --merge" '
237 'if you want to auto-merge)\n'))
237 'if you want to auto-merge)\n'))
238
238
239 def branch(ui, repo, label=None, **opts):
239 def branch(ui, repo, label=None, **opts):
240 """set or show the current branch name
240 """set or show the current branch name
241
241
242 With no argument, show the current branch name. With one argument,
242 With no argument, show the current branch name. With one argument,
243 set the working directory branch name (the branch does not exist in
243 set the working directory branch name (the branch does not exist in
244 the repository until the next commit).
244 the repository until the next commit).
245
245
246 Unless --force is specified, branch will not let you set a
246 Unless --force is specified, branch will not let you set a
247 branch name that shadows an existing branch.
247 branch name that shadows an existing branch.
248 """
248 """
249
249
250 if label:
250 if label:
251 if not opts.get('force') and label in repo.branchtags():
251 if not opts.get('force') and label in repo.branchtags():
252 if label not in [p.branch() for p in repo.workingctx().parents()]:
252 if label not in [p.branch() for p in repo.workingctx().parents()]:
253 raise util.Abort(_('a branch of the same name already exists'
253 raise util.Abort(_('a branch of the same name already exists'
254 ' (use --force to override)'))
254 ' (use --force to override)'))
255 repo.dirstate.setbranch(util.fromlocal(label))
255 repo.dirstate.setbranch(util.fromlocal(label))
256 ui.status(_('marked working directory as branch %s\n') % label)
256 ui.status(_('marked working directory as branch %s\n') % label)
257 else:
257 else:
258 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
258 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
259
259
260 def branches(ui, repo, active=False):
260 def branches(ui, repo, active=False):
261 """list repository named branches
261 """list repository named branches
262
262
263 List the repository's named branches, indicating which ones are
263 List the repository's named branches, indicating which ones are
264 inactive. If active is specified, only show active branches.
264 inactive. If active is specified, only show active branches.
265
265
266 A branch is considered active if it contains unmerged heads.
266 A branch is considered active if it contains unmerged heads.
267 """
267 """
268 b = repo.branchtags()
268 b = repo.branchtags()
269 heads = dict.fromkeys(repo.heads(), 1)
269 heads = dict.fromkeys(repo.heads(), 1)
270 l = [((n in heads), repo.changelog.rev(n), n, t) for t, n in b.items()]
270 l = [((n in heads), repo.changelog.rev(n), n, t) for t, n in b.items()]
271 l.sort()
271 l.sort()
272 l.reverse()
272 l.reverse()
273 for ishead, r, n, t in l:
273 for ishead, r, n, t in l:
274 if active and not ishead:
274 if active and not ishead:
275 # If we're only displaying active branches, abort the loop on
275 # If we're only displaying active branches, abort the loop on
276 # encountering the first inactive head
276 # encountering the first inactive head
277 break
277 break
278 else:
278 else:
279 hexfunc = ui.debugflag and hex or short
279 hexfunc = ui.debugflag and hex or short
280 if ui.quiet:
280 if ui.quiet:
281 ui.write("%s\n" % t)
281 ui.write("%s\n" % t)
282 else:
282 else:
283 spaces = " " * (30 - util.locallen(t))
283 spaces = " " * (30 - util.locallen(t))
284 # The code only gets here if inactive branches are being
284 # The code only gets here if inactive branches are being
285 # displayed or the branch is active.
285 # displayed or the branch is active.
286 isinactive = ((not ishead) and " (inactive)") or ''
286 isinactive = ((not ishead) and " (inactive)") or ''
287 ui.write("%s%s %s:%s%s\n" % (t, spaces, r, hexfunc(n), isinactive))
287 ui.write("%s%s %s:%s%s\n" % (t, spaces, r, hexfunc(n), isinactive))
288
288
289 def bundle(ui, repo, fname, dest=None, **opts):
289 def bundle(ui, repo, fname, dest=None, **opts):
290 """create a changegroup file
290 """create a changegroup file
291
291
292 Generate a compressed changegroup file collecting changesets not
292 Generate a compressed changegroup file collecting changesets not
293 found in the other repository.
293 found in the other repository.
294
294
295 If no destination repository is specified the destination is assumed
295 If no destination repository is specified the destination is assumed
296 to have all the nodes specified by one or more --base parameters.
296 to have all the nodes specified by one or more --base parameters.
297
297
298 The bundle file can then be transferred using conventional means and
298 The bundle file can then be transferred using conventional means and
299 applied to another repository with the unbundle or pull command.
299 applied to another repository with the unbundle or pull command.
300 This is useful when direct push and pull are not available or when
300 This is useful when direct push and pull are not available or when
301 exporting an entire repository is undesirable.
301 exporting an entire repository is undesirable.
302
302
303 Applying bundles preserves all changeset contents including
303 Applying bundles preserves all changeset contents including
304 permissions, copy/rename information, and revision history.
304 permissions, copy/rename information, and revision history.
305 """
305 """
306 revs = opts.get('rev') or None
306 revs = opts.get('rev') or None
307 if revs:
307 if revs:
308 revs = [repo.lookup(rev) for rev in revs]
308 revs = [repo.lookup(rev) for rev in revs]
309 base = opts.get('base')
309 base = opts.get('base')
310 if base:
310 if base:
311 if dest:
311 if dest:
312 raise util.Abort(_("--base is incompatible with specifiying "
312 raise util.Abort(_("--base is incompatible with specifiying "
313 "a destination"))
313 "a destination"))
314 base = [repo.lookup(rev) for rev in base]
314 base = [repo.lookup(rev) for rev in base]
315 # create the right base
315 # create the right base
316 # XXX: nodesbetween / changegroup* should be "fixed" instead
316 # XXX: nodesbetween / changegroup* should be "fixed" instead
317 o = []
317 o = []
318 has = {nullid: None}
318 has = {nullid: None}
319 for n in base:
319 for n in base:
320 has.update(repo.changelog.reachable(n))
320 has.update(repo.changelog.reachable(n))
321 if revs:
321 if revs:
322 visit = list(revs)
322 visit = list(revs)
323 else:
323 else:
324 visit = repo.changelog.heads()
324 visit = repo.changelog.heads()
325 seen = {}
325 seen = {}
326 while visit:
326 while visit:
327 n = visit.pop(0)
327 n = visit.pop(0)
328 parents = [p for p in repo.changelog.parents(n) if p not in has]
328 parents = [p for p in repo.changelog.parents(n) if p not in has]
329 if len(parents) == 0:
329 if len(parents) == 0:
330 o.insert(0, n)
330 o.insert(0, n)
331 else:
331 else:
332 for p in parents:
332 for p in parents:
333 if p not in seen:
333 if p not in seen:
334 seen[p] = 1
334 seen[p] = 1
335 visit.append(p)
335 visit.append(p)
336 else:
336 else:
337 cmdutil.setremoteconfig(ui, opts)
337 cmdutil.setremoteconfig(ui, opts)
338 dest, revs, checkout = hg.parseurl(
338 dest, revs, checkout = hg.parseurl(
339 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
339 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
340 other = hg.repository(ui, dest)
340 other = hg.repository(ui, dest)
341 o = repo.findoutgoing(other, force=opts['force'])
341 o = repo.findoutgoing(other, force=opts['force'])
342
342
343 if revs:
343 if revs:
344 cg = repo.changegroupsubset(o, revs, 'bundle')
344 cg = repo.changegroupsubset(o, revs, 'bundle')
345 else:
345 else:
346 cg = repo.changegroup(o, 'bundle')
346 cg = repo.changegroup(o, 'bundle')
347 changegroup.writebundle(cg, fname, "HG10BZ")
347 changegroup.writebundle(cg, fname, "HG10BZ")
348
348
349 def cat(ui, repo, file1, *pats, **opts):
349 def cat(ui, repo, file1, *pats, **opts):
350 """output the current or given revision of files
350 """output the current or given revision of files
351
351
352 Print the specified files as they were at the given revision.
352 Print the specified files as they were at the given revision.
353 If no revision is given, the parent of the working directory is used,
353 If no revision is given, the parent of the working directory is used,
354 or tip if no revision is checked out.
354 or tip if no revision is checked out.
355
355
356 Output may be to a file, in which case the name of the file is
356 Output may be to a file, in which case the name of the file is
357 given using a format string. The formatting rules are the same as
357 given using a format string. The formatting rules are the same as
358 for the export command, with the following additions:
358 for the export command, with the following additions:
359
359
360 %s basename of file being printed
360 %s basename of file being printed
361 %d dirname of file being printed, or '.' if in repo root
361 %d dirname of file being printed, or '.' if in repo root
362 %p root-relative path name of file being printed
362 %p root-relative path name of file being printed
363 """
363 """
364 ctx = repo.changectx(opts['rev'])
364 ctx = repo.changectx(opts['rev'])
365 err = 1
365 err = 1
366 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
366 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
367 ctx.node()):
367 ctx.node()):
368 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
368 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
369 fp.write(ctx.filectx(abs).data())
369 fp.write(ctx.filectx(abs).data())
370 err = 0
370 err = 0
371 return err
371 return err
372
372
373 def clone(ui, source, dest=None, **opts):
373 def clone(ui, source, dest=None, **opts):
374 """make a copy of an existing repository
374 """make a copy of an existing repository
375
375
376 Create a copy of an existing repository in a new directory.
376 Create a copy of an existing repository in a new directory.
377
377
378 If no destination directory name is specified, it defaults to the
378 If no destination directory name is specified, it defaults to the
379 basename of the source.
379 basename of the source.
380
380
381 The location of the source is added to the new repository's
381 The location of the source is added to the new repository's
382 .hg/hgrc file, as the default to be used for future pulls.
382 .hg/hgrc file, as the default to be used for future pulls.
383
383
384 For efficiency, hardlinks are used for cloning whenever the source
384 For efficiency, hardlinks are used for cloning whenever the source
385 and destination are on the same filesystem (note this applies only
385 and destination are on the same filesystem (note this applies only
386 to the repository data, not to the checked out files). Some
386 to the repository data, not to the checked out files). Some
387 filesystems, such as AFS, implement hardlinking incorrectly, but
387 filesystems, such as AFS, implement hardlinking incorrectly, but
388 do not report errors. In these cases, use the --pull option to
388 do not report errors. In these cases, use the --pull option to
389 avoid hardlinking.
389 avoid hardlinking.
390
390
391 You can safely clone repositories and checked out files using full
391 You can safely clone repositories and checked out files using full
392 hardlinks with
392 hardlinks with
393
393
394 $ cp -al REPO REPOCLONE
394 $ cp -al REPO REPOCLONE
395
395
396 which is the fastest way to clone. However, the operation is not
396 which is the fastest way to clone. However, the operation is not
397 atomic (making sure REPO is not modified during the operation is
397 atomic (making sure REPO is not modified during the operation is
398 up to you) and you have to make sure your editor breaks hardlinks
398 up to you) and you have to make sure your editor breaks hardlinks
399 (Emacs and most Linux Kernel tools do so).
399 (Emacs and most Linux Kernel tools do so).
400
400
401 If you use the -r option to clone up to a specific revision, no
401 If you use the -r option to clone up to a specific revision, no
402 subsequent revisions will be present in the cloned repository.
402 subsequent revisions will be present in the cloned repository.
403 This option implies --pull, even on local repositories.
403 This option implies --pull, even on local repositories.
404
404
405 See pull for valid source format details.
405 See pull for valid source format details.
406
406
407 It is possible to specify an ssh:// URL as the destination, but no
407 It is possible to specify an ssh:// URL as the destination, but no
408 .hg/hgrc and working directory will be created on the remote side.
408 .hg/hgrc and working directory will be created on the remote side.
409 Look at the help text for the pull command for important details
409 Look at the help text for the pull command for important details
410 about ssh:// URLs.
410 about ssh:// URLs.
411 """
411 """
412 cmdutil.setremoteconfig(ui, opts)
412 cmdutil.setremoteconfig(ui, opts)
413 hg.clone(ui, source, dest,
413 hg.clone(ui, source, dest,
414 pull=opts['pull'],
414 pull=opts['pull'],
415 stream=opts['uncompressed'],
415 stream=opts['uncompressed'],
416 rev=opts['rev'],
416 rev=opts['rev'],
417 update=not opts['noupdate'])
417 update=not opts['noupdate'])
418
418
419 def commit(ui, repo, *pats, **opts):
419 def commit(ui, repo, *pats, **opts):
420 """commit the specified files or all outstanding changes
420 """commit the specified files or all outstanding changes
421
421
422 Commit changes to the given files into the repository.
422 Commit changes to the given files into the repository.
423
423
424 If a list of files is omitted, all changes reported by "hg status"
424 If a list of files is omitted, all changes reported by "hg status"
425 will be committed.
425 will be committed.
426
426
427 If no commit message is specified, the editor configured in your hgrc
427 If no commit message is specified, the editor configured in your hgrc
428 or in the EDITOR environment variable is started to enter a message.
428 or in the EDITOR environment variable is started to enter a message.
429 """
429 """
430 def commitfunc(ui, repo, files, message, match, opts):
430 def commitfunc(ui, repo, files, message, match, opts):
431 return repo.commit(files, message, opts['user'], opts['date'], match,
431 return repo.commit(files, message, opts['user'], opts['date'], match,
432 force_editor=opts.get('force_editor'))
432 force_editor=opts.get('force_editor'))
433 cmdutil.commit(ui, repo, commitfunc, pats, opts)
433 cmdutil.commit(ui, repo, commitfunc, pats, opts)
434
434
435 def docopy(ui, repo, pats, opts):
435 def docopy(ui, repo, pats, opts):
436 # called with the repo lock held
436 # called with the repo lock held
437 #
437 #
438 # hgsep => pathname that uses "/" to separate directories
438 # hgsep => pathname that uses "/" to separate directories
439 # ossep => pathname that uses os.sep to separate directories
439 # ossep => pathname that uses os.sep to separate directories
440 cwd = repo.getcwd()
440 cwd = repo.getcwd()
441 errors = 0
441 errors = 0
442 copied = []
442 copied = []
443 targets = {}
443 targets = {}
444
444
445 # abs: hgsep
445 # abs: hgsep
446 # rel: ossep
446 # rel: ossep
447 # return: hgsep
447 # return: hgsep
448 def okaytocopy(abs, rel, exact):
448 def okaytocopy(abs, rel, exact):
449 reasons = {'?': _('is not managed'),
449 reasons = {'?': _('is not managed'),
450 'r': _('has been marked for remove')}
450 'r': _('has been marked for remove')}
451 state = repo.dirstate[abs]
451 state = repo.dirstate[abs]
452 reason = reasons.get(state)
452 reason = reasons.get(state)
453 if reason:
453 if reason:
454 if exact:
454 if exact:
455 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
455 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
456 else:
456 else:
457 if state == 'a':
457 if state == 'a':
458 origsrc = repo.dirstate.copied(abs)
458 origsrc = repo.dirstate.copied(abs)
459 if origsrc is not None:
459 if origsrc is not None:
460 return origsrc
460 return origsrc
461 return abs
461 return abs
462
462
463 # origsrc: hgsep
463 # origsrc: hgsep
464 # abssrc: hgsep
464 # abssrc: hgsep
465 # relsrc: ossep
465 # relsrc: ossep
466 # otarget: ossep
466 # otarget: ossep
467 def copy(origsrc, abssrc, relsrc, otarget, exact):
467 def copy(origsrc, abssrc, relsrc, otarget, exact):
468 abstarget = util.canonpath(repo.root, cwd, otarget)
468 abstarget = util.canonpath(repo.root, cwd, otarget)
469 reltarget = repo.pathto(abstarget, cwd)
469 reltarget = repo.pathto(abstarget, cwd)
470 prevsrc = targets.get(abstarget)
470 prevsrc = targets.get(abstarget)
471 src = repo.wjoin(abssrc)
471 src = repo.wjoin(abssrc)
472 target = repo.wjoin(abstarget)
472 target = repo.wjoin(abstarget)
473 if prevsrc is not None:
473 if prevsrc is not None:
474 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
474 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
475 (reltarget, repo.pathto(abssrc, cwd),
475 (reltarget, repo.pathto(abssrc, cwd),
476 repo.pathto(prevsrc, cwd)))
476 repo.pathto(prevsrc, cwd)))
477 return
477 return
478 if (not opts['after'] and os.path.exists(target) or
478 if (not opts['after'] and os.path.exists(target) or
479 opts['after'] and repo.dirstate[abstarget] in 'mn'):
479 opts['after'] and repo.dirstate[abstarget] in 'mn'):
480 if not opts['force']:
480 if not opts['force']:
481 ui.warn(_('%s: not overwriting - file exists\n') %
481 ui.warn(_('%s: not overwriting - file exists\n') %
482 reltarget)
482 reltarget)
483 return
483 return
484 if not opts['after'] and not opts.get('dry_run'):
484 if not opts['after'] and not opts.get('dry_run'):
485 os.unlink(target)
485 os.unlink(target)
486 if opts['after']:
486 if opts['after']:
487 if not os.path.exists(target):
487 if not os.path.exists(target):
488 return
488 return
489 else:
489 else:
490 targetdir = os.path.dirname(target) or '.'
490 targetdir = os.path.dirname(target) or '.'
491 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
491 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
492 os.makedirs(targetdir)
492 os.makedirs(targetdir)
493 try:
493 try:
494 restore = repo.dirstate[abstarget] == 'r'
494 restore = repo.dirstate[abstarget] == 'r'
495 if restore and not opts.get('dry_run'):
495 if restore and not opts.get('dry_run'):
496 repo.undelete([abstarget])
496 repo.undelete([abstarget])
497 try:
497 try:
498 if not opts.get('dry_run'):
498 if not opts.get('dry_run'):
499 util.copyfile(src, target)
499 util.copyfile(src, target)
500 restore = False
500 restore = False
501 finally:
501 finally:
502 if restore:
502 if restore:
503 repo.remove([abstarget])
503 repo.remove([abstarget])
504 except IOError, inst:
504 except IOError, inst:
505 if inst.errno == errno.ENOENT:
505 if inst.errno == errno.ENOENT:
506 ui.warn(_('%s: deleted in working copy\n') % relsrc)
506 ui.warn(_('%s: deleted in working copy\n') % relsrc)
507 else:
507 else:
508 ui.warn(_('%s: cannot copy - %s\n') %
508 ui.warn(_('%s: cannot copy - %s\n') %
509 (relsrc, inst.strerror))
509 (relsrc, inst.strerror))
510 errors += 1
510 errors += 1
511 return
511 return
512 if ui.verbose or not exact:
512 if ui.verbose or not exact:
513 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
513 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
514 targets[abstarget] = abssrc
514 targets[abstarget] = abssrc
515 if abstarget != origsrc:
515 if abstarget != origsrc:
516 if repo.dirstate[origsrc] == 'a':
516 if repo.dirstate[origsrc] == 'a':
517 if not ui.quiet:
517 if not ui.quiet:
518 ui.warn(_("%s has not been committed yet, so no copy "
518 ui.warn(_("%s has not been committed yet, so no copy "
519 "data will be stored for %s.\n")
519 "data will be stored for %s.\n")
520 % (repo.pathto(origsrc, cwd), reltarget))
520 % (repo.pathto(origsrc, cwd), reltarget))
521 if abstarget not in repo.dirstate and not opts.get('dry_run'):
521 if abstarget not in repo.dirstate and not opts.get('dry_run'):
522 repo.add([abstarget])
522 repo.add([abstarget])
523 elif not opts.get('dry_run'):
523 elif not opts.get('dry_run'):
524 repo.copy(origsrc, abstarget)
524 repo.copy(origsrc, abstarget)
525 copied.append((abssrc, relsrc, exact))
525 copied.append((abssrc, relsrc, exact))
526
526
527 # pat: ossep
527 # pat: ossep
528 # dest ossep
528 # dest ossep
529 # srcs: list of (hgsep, hgsep, ossep, bool)
529 # srcs: list of (hgsep, hgsep, ossep, bool)
530 # return: function that takes hgsep and returns ossep
530 # return: function that takes hgsep and returns ossep
531 def targetpathfn(pat, dest, srcs):
531 def targetpathfn(pat, dest, srcs):
532 if os.path.isdir(pat):
532 if os.path.isdir(pat):
533 abspfx = util.canonpath(repo.root, cwd, pat)
533 abspfx = util.canonpath(repo.root, cwd, pat)
534 abspfx = util.localpath(abspfx)
534 abspfx = util.localpath(abspfx)
535 if destdirexists:
535 if destdirexists:
536 striplen = len(os.path.split(abspfx)[0])
536 striplen = len(os.path.split(abspfx)[0])
537 else:
537 else:
538 striplen = len(abspfx)
538 striplen = len(abspfx)
539 if striplen:
539 if striplen:
540 striplen += len(os.sep)
540 striplen += len(os.sep)
541 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
541 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
542 elif destdirexists:
542 elif destdirexists:
543 res = lambda p: os.path.join(dest,
543 res = lambda p: os.path.join(dest,
544 os.path.basename(util.localpath(p)))
544 os.path.basename(util.localpath(p)))
545 else:
545 else:
546 res = lambda p: dest
546 res = lambda p: dest
547 return res
547 return res
548
548
549 # pat: ossep
549 # pat: ossep
550 # dest ossep
550 # dest ossep
551 # srcs: list of (hgsep, hgsep, ossep, bool)
551 # srcs: list of (hgsep, hgsep, ossep, bool)
552 # return: function that takes hgsep and returns ossep
552 # return: function that takes hgsep and returns ossep
553 def targetpathafterfn(pat, dest, srcs):
553 def targetpathafterfn(pat, dest, srcs):
554 if util.patkind(pat, None)[0]:
554 if util.patkind(pat, None)[0]:
555 # a mercurial pattern
555 # a mercurial pattern
556 res = lambda p: os.path.join(dest,
556 res = lambda p: os.path.join(dest,
557 os.path.basename(util.localpath(p)))
557 os.path.basename(util.localpath(p)))
558 else:
558 else:
559 abspfx = util.canonpath(repo.root, cwd, pat)
559 abspfx = util.canonpath(repo.root, cwd, pat)
560 if len(abspfx) < len(srcs[0][0]):
560 if len(abspfx) < len(srcs[0][0]):
561 # A directory. Either the target path contains the last
561 # A directory. Either the target path contains the last
562 # component of the source path or it does not.
562 # component of the source path or it does not.
563 def evalpath(striplen):
563 def evalpath(striplen):
564 score = 0
564 score = 0
565 for s in srcs:
565 for s in srcs:
566 t = os.path.join(dest, util.localpath(s[0])[striplen:])
566 t = os.path.join(dest, util.localpath(s[0])[striplen:])
567 if os.path.exists(t):
567 if os.path.exists(t):
568 score += 1
568 score += 1
569 return score
569 return score
570
570
571 abspfx = util.localpath(abspfx)
571 abspfx = util.localpath(abspfx)
572 striplen = len(abspfx)
572 striplen = len(abspfx)
573 if striplen:
573 if striplen:
574 striplen += len(os.sep)
574 striplen += len(os.sep)
575 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
575 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
576 score = evalpath(striplen)
576 score = evalpath(striplen)
577 striplen1 = len(os.path.split(abspfx)[0])
577 striplen1 = len(os.path.split(abspfx)[0])
578 if striplen1:
578 if striplen1:
579 striplen1 += len(os.sep)
579 striplen1 += len(os.sep)
580 if evalpath(striplen1) > score:
580 if evalpath(striplen1) > score:
581 striplen = striplen1
581 striplen = striplen1
582 res = lambda p: os.path.join(dest,
582 res = lambda p: os.path.join(dest,
583 util.localpath(p)[striplen:])
583 util.localpath(p)[striplen:])
584 else:
584 else:
585 # a file
585 # a file
586 if destdirexists:
586 if destdirexists:
587 res = lambda p: os.path.join(dest,
587 res = lambda p: os.path.join(dest,
588 os.path.basename(util.localpath(p)))
588 os.path.basename(util.localpath(p)))
589 else:
589 else:
590 res = lambda p: dest
590 res = lambda p: dest
591 return res
591 return res
592
592
593
593
594 pats = util.expand_glob(pats)
594 pats = util.expand_glob(pats)
595 if not pats:
595 if not pats:
596 raise util.Abort(_('no source or destination specified'))
596 raise util.Abort(_('no source or destination specified'))
597 if len(pats) == 1:
597 if len(pats) == 1:
598 raise util.Abort(_('no destination specified'))
598 raise util.Abort(_('no destination specified'))
599 dest = pats.pop()
599 dest = pats.pop()
600 destdirexists = os.path.isdir(dest)
600 destdirexists = os.path.isdir(dest)
601 if not destdirexists:
601 if not destdirexists:
602 if len(pats) > 1 or util.patkind(pats[0], None)[0]:
602 if len(pats) > 1 or util.patkind(pats[0], None)[0]:
603 raise util.Abort(_('with multiple sources, destination must be an '
603 raise util.Abort(_('with multiple sources, destination must be an '
604 'existing directory'))
604 'existing directory'))
605 if dest.endswith(os.sep) or os.altsep and dest.endswith(os.altsep):
605 if dest.endswith(os.sep) or os.altsep and dest.endswith(os.altsep):
606 raise util.Abort(_('destination %s is not a directory') % dest)
606 raise util.Abort(_('destination %s is not a directory') % dest)
607 if opts['after']:
607 if opts['after']:
608 tfn = targetpathafterfn
608 tfn = targetpathafterfn
609 else:
609 else:
610 tfn = targetpathfn
610 tfn = targetpathfn
611 copylist = []
611 copylist = []
612 for pat in pats:
612 for pat in pats:
613 srcs = []
613 srcs = []
614 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts,
614 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts,
615 globbed=True):
615 globbed=True):
616 origsrc = okaytocopy(abssrc, relsrc, exact)
616 origsrc = okaytocopy(abssrc, relsrc, exact)
617 if origsrc:
617 if origsrc:
618 srcs.append((origsrc, abssrc, relsrc, exact))
618 srcs.append((origsrc, abssrc, relsrc, exact))
619 if not srcs:
619 if not srcs:
620 continue
620 continue
621 copylist.append((tfn(pat, dest, srcs), srcs))
621 copylist.append((tfn(pat, dest, srcs), srcs))
622 if not copylist:
622 if not copylist:
623 raise util.Abort(_('no files to copy'))
623 raise util.Abort(_('no files to copy'))
624
624
625 for targetpath, srcs in copylist:
625 for targetpath, srcs in copylist:
626 for origsrc, abssrc, relsrc, exact in srcs:
626 for origsrc, abssrc, relsrc, exact in srcs:
627 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
627 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
628
628
629 if errors:
629 if errors:
630 ui.warn(_('(consider using --after)\n'))
630 ui.warn(_('(consider using --after)\n'))
631 return errors, copied
631 return errors, copied
632
632
633 def copy(ui, repo, *pats, **opts):
633 def copy(ui, repo, *pats, **opts):
634 """mark files as copied for the next commit
634 """mark files as copied for the next commit
635
635
636 Mark dest as having copies of source files. If dest is a
636 Mark dest as having copies of source files. If dest is a
637 directory, copies are put in that directory. If dest is a file,
637 directory, copies are put in that directory. If dest is a file,
638 there can only be one source.
638 there can only be one source.
639
639
640 By default, this command copies the contents of files as they
640 By default, this command copies the contents of files as they
641 stand in the working directory. If invoked with --after, the
641 stand in the working directory. If invoked with --after, the
642 operation is recorded, but no copying is performed.
642 operation is recorded, but no copying is performed.
643
643
644 This command takes effect in the next commit. To undo a copy
644 This command takes effect in the next commit. To undo a copy
645 before that, see hg revert.
645 before that, see hg revert.
646 """
646 """
647 wlock = repo.wlock(False)
647 wlock = repo.wlock(False)
648 try:
648 try:
649 errs, copied = docopy(ui, repo, pats, opts)
649 errs, copied = docopy(ui, repo, pats, opts)
650 finally:
650 finally:
651 del wlock
651 del wlock
652 return errs
652 return errs
653
653
654 def debugancestor(ui, index, rev1, rev2):
654 def debugancestor(ui, index, rev1, rev2):
655 """find the ancestor revision of two revisions in a given index"""
655 """find the ancestor revision of two revisions in a given index"""
656 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
656 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
657 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
657 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
658 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
658 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
659
659
660 def debugcomplete(ui, cmd='', **opts):
660 def debugcomplete(ui, cmd='', **opts):
661 """returns the completion list associated with the given command"""
661 """returns the completion list associated with the given command"""
662
662
663 if opts['options']:
663 if opts['options']:
664 options = []
664 options = []
665 otables = [globalopts]
665 otables = [globalopts]
666 if cmd:
666 if cmd:
667 aliases, entry = cmdutil.findcmd(ui, cmd, table)
667 aliases, entry = cmdutil.findcmd(ui, cmd, table)
668 otables.append(entry[1])
668 otables.append(entry[1])
669 for t in otables:
669 for t in otables:
670 for o in t:
670 for o in t:
671 if o[0]:
671 if o[0]:
672 options.append('-%s' % o[0])
672 options.append('-%s' % o[0])
673 options.append('--%s' % o[1])
673 options.append('--%s' % o[1])
674 ui.write("%s\n" % "\n".join(options))
674 ui.write("%s\n" % "\n".join(options))
675 return
675 return
676
676
677 clist = cmdutil.findpossible(ui, cmd, table).keys()
677 clist = cmdutil.findpossible(ui, cmd, table).keys()
678 clist.sort()
678 clist.sort()
679 ui.write("%s\n" % "\n".join(clist))
679 ui.write("%s\n" % "\n".join(clist))
680
680
681 def debugrebuildstate(ui, repo, rev=""):
681 def debugrebuildstate(ui, repo, rev=""):
682 """rebuild the dirstate as it would look like for the given revision"""
682 """rebuild the dirstate as it would look like for the given revision"""
683 if rev == "":
683 if rev == "":
684 rev = repo.changelog.tip()
684 rev = repo.changelog.tip()
685 ctx = repo.changectx(rev)
685 ctx = repo.changectx(rev)
686 files = ctx.manifest()
686 files = ctx.manifest()
687 wlock = repo.wlock()
687 wlock = repo.wlock()
688 try:
688 try:
689 repo.dirstate.rebuild(rev, files)
689 repo.dirstate.rebuild(rev, files)
690 finally:
690 finally:
691 del wlock
691 del wlock
692
692
693 def debugcheckstate(ui, repo):
693 def debugcheckstate(ui, repo):
694 """validate the correctness of the current dirstate"""
694 """validate the correctness of the current dirstate"""
695 parent1, parent2 = repo.dirstate.parents()
695 parent1, parent2 = repo.dirstate.parents()
696 m1 = repo.changectx(parent1).manifest()
696 m1 = repo.changectx(parent1).manifest()
697 m2 = repo.changectx(parent2).manifest()
697 m2 = repo.changectx(parent2).manifest()
698 errors = 0
698 errors = 0
699 for f in repo.dirstate:
699 for f in repo.dirstate:
700 state = repo.dirstate[f]
700 state = repo.dirstate[f]
701 if state in "nr" and f not in m1:
701 if state in "nr" and f not in m1:
702 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
702 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
703 errors += 1
703 errors += 1
704 if state in "a" and f in m1:
704 if state in "a" and f in m1:
705 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
705 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
706 errors += 1
706 errors += 1
707 if state in "m" and f not in m1 and f not in m2:
707 if state in "m" and f not in m1 and f not in m2:
708 ui.warn(_("%s in state %s, but not in either manifest\n") %
708 ui.warn(_("%s in state %s, but not in either manifest\n") %
709 (f, state))
709 (f, state))
710 errors += 1
710 errors += 1
711 for f in m1:
711 for f in m1:
712 state = repo.dirstate[f]
712 state = repo.dirstate[f]
713 if state not in "nrm":
713 if state not in "nrm":
714 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
714 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
715 errors += 1
715 errors += 1
716 if errors:
716 if errors:
717 error = _(".hg/dirstate inconsistent with current parent's manifest")
717 error = _(".hg/dirstate inconsistent with current parent's manifest")
718 raise util.Abort(error)
718 raise util.Abort(error)
719
719
720 def showconfig(ui, repo, *values, **opts):
720 def showconfig(ui, repo, *values, **opts):
721 """show combined config settings from all hgrc files
721 """show combined config settings from all hgrc files
722
722
723 With no args, print names and values of all config items.
723 With no args, print names and values of all config items.
724
724
725 With one arg of the form section.name, print just the value of
725 With one arg of the form section.name, print just the value of
726 that config item.
726 that config item.
727
727
728 With multiple args, print names and values of all config items
728 With multiple args, print names and values of all config items
729 with matching section names."""
729 with matching section names."""
730
730
731 untrusted = bool(opts.get('untrusted'))
731 untrusted = bool(opts.get('untrusted'))
732 if values:
732 if values:
733 if len([v for v in values if '.' in v]) > 1:
733 if len([v for v in values if '.' in v]) > 1:
734 raise util.Abort(_('only one config item permitted'))
734 raise util.Abort(_('only one config item permitted'))
735 for section, name, value in ui.walkconfig(untrusted=untrusted):
735 for section, name, value in ui.walkconfig(untrusted=untrusted):
736 sectname = section + '.' + name
736 sectname = section + '.' + name
737 if values:
737 if values:
738 for v in values:
738 for v in values:
739 if v == section:
739 if v == section:
740 ui.write('%s=%s\n' % (sectname, value))
740 ui.write('%s=%s\n' % (sectname, value))
741 elif v == sectname:
741 elif v == sectname:
742 ui.write(value, '\n')
742 ui.write(value, '\n')
743 else:
743 else:
744 ui.write('%s=%s\n' % (sectname, value))
744 ui.write('%s=%s\n' % (sectname, value))
745
745
746 def debugsetparents(ui, repo, rev1, rev2=None):
746 def debugsetparents(ui, repo, rev1, rev2=None):
747 """manually set the parents of the current working directory
747 """manually set the parents of the current working directory
748
748
749 This is useful for writing repository conversion tools, but should
749 This is useful for writing repository conversion tools, but should
750 be used with care.
750 be used with care.
751 """
751 """
752
752
753 if not rev2:
753 if not rev2:
754 rev2 = hex(nullid)
754 rev2 = hex(nullid)
755
755
756 wlock = repo.wlock()
756 wlock = repo.wlock()
757 try:
757 try:
758 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
758 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
759 finally:
759 finally:
760 del wlock
760 del wlock
761
761
762 def debugstate(ui, repo):
762 def debugstate(ui, repo):
763 """show the contents of the current dirstate"""
763 """show the contents of the current dirstate"""
764 k = repo.dirstate._map.items()
764 k = repo.dirstate._map.items()
765 k.sort()
765 k.sort()
766 for file_, ent in k:
766 for file_, ent in k:
767 if ent[3] == -1:
767 if ent[3] == -1:
768 # Pad or slice to locale representation
768 # Pad or slice to locale representation
769 locale_len = len(time.strftime("%x %X", time.localtime(0)))
769 locale_len = len(time.strftime("%x %X", time.localtime(0)))
770 timestr = 'unset'
770 timestr = 'unset'
771 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
771 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
772 else:
772 else:
773 timestr = time.strftime("%x %X", time.localtime(ent[3]))
773 timestr = time.strftime("%x %X", time.localtime(ent[3]))
774 if ent[1] & 020000:
774 if ent[1] & 020000:
775 mode = 'lnk'
775 mode = 'lnk'
776 else:
776 else:
777 mode = '%3o' % (ent[1] & 0777)
777 mode = '%3o' % (ent[1] & 0777)
778 ui.write("%c %s %10d %s %s\n" % (ent[0], mode, ent[2], timestr, file_))
778 ui.write("%c %s %10d %s %s\n" % (ent[0], mode, ent[2], timestr, file_))
779 for f in repo.dirstate.copies():
779 for f in repo.dirstate.copies():
780 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
780 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
781
781
782 def debugdata(ui, file_, rev):
782 def debugdata(ui, file_, rev):
783 """dump the contents of a data file revision"""
783 """dump the contents of a data file revision"""
784 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
784 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
785 try:
785 try:
786 ui.write(r.revision(r.lookup(rev)))
786 ui.write(r.revision(r.lookup(rev)))
787 except KeyError:
787 except KeyError:
788 raise util.Abort(_('invalid revision identifier %s') % rev)
788 raise util.Abort(_('invalid revision identifier %s') % rev)
789
789
790 def debugdate(ui, date, range=None, **opts):
790 def debugdate(ui, date, range=None, **opts):
791 """parse and display a date"""
791 """parse and display a date"""
792 if opts["extended"]:
792 if opts["extended"]:
793 d = util.parsedate(date, util.extendeddateformats)
793 d = util.parsedate(date, util.extendeddateformats)
794 else:
794 else:
795 d = util.parsedate(date)
795 d = util.parsedate(date)
796 ui.write("internal: %s %s\n" % d)
796 ui.write("internal: %s %s\n" % d)
797 ui.write("standard: %s\n" % util.datestr(d))
797 ui.write("standard: %s\n" % util.datestr(d))
798 if range:
798 if range:
799 m = util.matchdate(range)
799 m = util.matchdate(range)
800 ui.write("match: %s\n" % m(d[0]))
800 ui.write("match: %s\n" % m(d[0]))
801
801
802 def debugindex(ui, file_):
802 def debugindex(ui, file_):
803 """dump the contents of an index file"""
803 """dump the contents of an index file"""
804 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
804 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
805 ui.write(" rev offset length base linkrev" +
805 ui.write(" rev offset length base linkrev" +
806 " nodeid p1 p2\n")
806 " nodeid p1 p2\n")
807 for i in xrange(r.count()):
807 for i in xrange(r.count()):
808 node = r.node(i)
808 node = r.node(i)
809 try:
809 try:
810 pp = r.parents(node)
810 pp = r.parents(node)
811 except:
811 except:
812 pp = [nullid, nullid]
812 pp = [nullid, nullid]
813 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
813 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
814 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
814 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
815 short(node), short(pp[0]), short(pp[1])))
815 short(node), short(pp[0]), short(pp[1])))
816
816
817 def debugindexdot(ui, file_):
817 def debugindexdot(ui, file_):
818 """dump an index DAG as a .dot file"""
818 """dump an index DAG as a .dot file"""
819 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
819 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
820 ui.write("digraph G {\n")
820 ui.write("digraph G {\n")
821 for i in xrange(r.count()):
821 for i in xrange(r.count()):
822 node = r.node(i)
822 node = r.node(i)
823 pp = r.parents(node)
823 pp = r.parents(node)
824 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
824 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
825 if pp[1] != nullid:
825 if pp[1] != nullid:
826 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
826 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
827 ui.write("}\n")
827 ui.write("}\n")
828
828
829 def debuginstall(ui):
829 def debuginstall(ui):
830 '''test Mercurial installation'''
830 '''test Mercurial installation'''
831
831
832 def writetemp(contents):
832 def writetemp(contents):
833 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
833 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
834 f = os.fdopen(fd, "wb")
834 f = os.fdopen(fd, "wb")
835 f.write(contents)
835 f.write(contents)
836 f.close()
836 f.close()
837 return name
837 return name
838
838
839 problems = 0
839 problems = 0
840
840
841 # encoding
841 # encoding
842 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
842 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
843 try:
843 try:
844 util.fromlocal("test")
844 util.fromlocal("test")
845 except util.Abort, inst:
845 except util.Abort, inst:
846 ui.write(" %s\n" % inst)
846 ui.write(" %s\n" % inst)
847 ui.write(_(" (check that your locale is properly set)\n"))
847 ui.write(_(" (check that your locale is properly set)\n"))
848 problems += 1
848 problems += 1
849
849
850 # compiled modules
850 # compiled modules
851 ui.status(_("Checking extensions...\n"))
851 ui.status(_("Checking extensions...\n"))
852 try:
852 try:
853 import bdiff, mpatch, base85
853 import bdiff, mpatch, base85
854 except Exception, inst:
854 except Exception, inst:
855 ui.write(" %s\n" % inst)
855 ui.write(" %s\n" % inst)
856 ui.write(_(" One or more extensions could not be found"))
856 ui.write(_(" One or more extensions could not be found"))
857 ui.write(_(" (check that you compiled the extensions)\n"))
857 ui.write(_(" (check that you compiled the extensions)\n"))
858 problems += 1
858 problems += 1
859
859
860 # templates
860 # templates
861 ui.status(_("Checking templates...\n"))
861 ui.status(_("Checking templates...\n"))
862 try:
862 try:
863 import templater
863 import templater
864 t = templater.templater(templater.templatepath("map-cmdline.default"))
864 t = templater.templater(templater.templatepath("map-cmdline.default"))
865 except Exception, inst:
865 except Exception, inst:
866 ui.write(" %s\n" % inst)
866 ui.write(" %s\n" % inst)
867 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
867 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
868 problems += 1
868 problems += 1
869
869
870 # patch
870 # patch
871 ui.status(_("Checking patch...\n"))
871 ui.status(_("Checking patch...\n"))
872 patchproblems = 0
872 patchproblems = 0
873 a = "1\n2\n3\n4\n"
873 a = "1\n2\n3\n4\n"
874 b = "1\n2\n3\ninsert\n4\n"
874 b = "1\n2\n3\ninsert\n4\n"
875 fa = writetemp(a)
875 fa = writetemp(a)
876 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa))
876 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
877 os.path.basename(fa))
877 fd = writetemp(d)
878 fd = writetemp(d)
878
879
879 files = {}
880 files = {}
880 try:
881 try:
881 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
882 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
882 except util.Abort, e:
883 except util.Abort, e:
883 ui.write(_(" patch call failed:\n"))
884 ui.write(_(" patch call failed:\n"))
884 ui.write(" " + str(e) + "\n")
885 ui.write(" " + str(e) + "\n")
885 patchproblems += 1
886 patchproblems += 1
886 else:
887 else:
887 if list(files) != [os.path.basename(fa)]:
888 if list(files) != [os.path.basename(fa)]:
888 ui.write(_(" unexpected patch output!\n"))
889 ui.write(_(" unexpected patch output!\n"))
889 patchproblems += 1
890 patchproblems += 1
890 a = file(fa).read()
891 a = file(fa).read()
891 if a != b:
892 if a != b:
892 ui.write(_(" patch test failed!\n"))
893 ui.write(_(" patch test failed!\n"))
893 patchproblems += 1
894 patchproblems += 1
894
895
895 if patchproblems:
896 if patchproblems:
896 if ui.config('ui', 'patch'):
897 if ui.config('ui', 'patch'):
897 ui.write(_(" (Current patch tool may be incompatible with patch,"
898 ui.write(_(" (Current patch tool may be incompatible with patch,"
898 " or misconfigured. Please check your .hgrc file)\n"))
899 " or misconfigured. Please check your .hgrc file)\n"))
899 else:
900 else:
900 ui.write(_(" Internal patcher failure, please report this error"
901 ui.write(_(" Internal patcher failure, please report this error"
901 " to http://www.selenic.com/mercurial/bts\n"))
902 " to http://www.selenic.com/mercurial/bts\n"))
902 problems += patchproblems
903 problems += patchproblems
903
904
904 os.unlink(fa)
905 os.unlink(fa)
905 os.unlink(fd)
906 os.unlink(fd)
906
907
907 # merge helper
908 # merge helper
908 ui.status(_("Checking merge helper...\n"))
909 ui.status(_("Checking merge helper...\n"))
909 cmd = (os.environ.get("HGMERGE") or ui.config("ui", "merge")
910 cmd = (os.environ.get("HGMERGE") or ui.config("ui", "merge")
910 or "hgmerge")
911 or "hgmerge")
911 cmdpath = util.find_exe(cmd) or util.find_exe(cmd.split()[0])
912 cmdpath = util.find_exe(cmd) or util.find_exe(cmd.split()[0])
912 if not cmdpath:
913 if not cmdpath:
913 if cmd == 'hgmerge':
914 if cmd == 'hgmerge':
914 ui.write(_(" No merge helper set and can't find default"
915 ui.write(_(" No merge helper set and can't find default"
915 " hgmerge script in PATH\n"))
916 " hgmerge script in PATH\n"))
916 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
917 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
917 else:
918 else:
918 ui.write(_(" Can't find merge helper '%s' in PATH\n") % cmd)
919 ui.write(_(" Can't find merge helper '%s' in PATH\n") % cmd)
919 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
920 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
920 problems += 1
921 problems += 1
921 else:
922 else:
922 # actually attempt a patch here
923 # actually attempt a patch here
923 fa = writetemp("1\n2\n3\n4\n")
924 fa = writetemp("1\n2\n3\n4\n")
924 fl = writetemp("1\n2\n3\ninsert\n4\n")
925 fl = writetemp("1\n2\n3\ninsert\n4\n")
925 fr = writetemp("begin\n1\n2\n3\n4\n")
926 fr = writetemp("begin\n1\n2\n3\n4\n")
926 r = util.system('%s "%s" "%s" "%s"' % (cmd, fl, fa, fr))
927 r = util.system('%s "%s" "%s" "%s"' % (cmd, fl, fa, fr))
927 if r:
928 if r:
928 ui.write(_(" Got unexpected merge error %d!\n") % r)
929 ui.write(_(" Got unexpected merge error %d!\n") % r)
929 problems += 1
930 problems += 1
930 m = file(fl).read()
931 m = file(fl).read()
931 if m != "begin\n1\n2\n3\ninsert\n4\n":
932 if m != "begin\n1\n2\n3\ninsert\n4\n":
932 ui.write(_(" Got unexpected merge results!\n"))
933 ui.write(_(" Got unexpected merge results!\n"))
933 ui.write(_(" (your merge helper may have the"
934 ui.write(_(" (your merge helper may have the"
934 " wrong argument order)\n"))
935 " wrong argument order)\n"))
935 ui.write(_(" Result: %r\n") % m)
936 ui.write(_(" Result: %r\n") % m)
936 problems += 1
937 problems += 1
937 os.unlink(fa)
938 os.unlink(fa)
938 os.unlink(fl)
939 os.unlink(fl)
939 os.unlink(fr)
940 os.unlink(fr)
940
941
941 # editor
942 # editor
942 ui.status(_("Checking commit editor...\n"))
943 ui.status(_("Checking commit editor...\n"))
943 editor = (os.environ.get("HGEDITOR") or
944 editor = (os.environ.get("HGEDITOR") or
944 ui.config("ui", "editor") or
945 ui.config("ui", "editor") or
945 os.environ.get("EDITOR", "vi"))
946 os.environ.get("EDITOR", "vi"))
946 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
947 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
947 if not cmdpath:
948 if not cmdpath:
948 if editor == 'vi':
949 if editor == 'vi':
949 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
950 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
950 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
951 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
951 else:
952 else:
952 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
953 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
953 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
954 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
954 problems += 1
955 problems += 1
955
956
956 # check username
957 # check username
957 ui.status(_("Checking username...\n"))
958 ui.status(_("Checking username...\n"))
958 user = os.environ.get("HGUSER")
959 user = os.environ.get("HGUSER")
959 if user is None:
960 if user is None:
960 user = ui.config("ui", "username")
961 user = ui.config("ui", "username")
961 if user is None:
962 if user is None:
962 user = os.environ.get("EMAIL")
963 user = os.environ.get("EMAIL")
963 if not user:
964 if not user:
964 ui.warn(" ")
965 ui.warn(" ")
965 ui.username()
966 ui.username()
966 ui.write(_(" (specify a username in your .hgrc file)\n"))
967 ui.write(_(" (specify a username in your .hgrc file)\n"))
967
968
968 if not problems:
969 if not problems:
969 ui.status(_("No problems detected\n"))
970 ui.status(_("No problems detected\n"))
970 else:
971 else:
971 ui.write(_("%s problems detected,"
972 ui.write(_("%s problems detected,"
972 " please check your install!\n") % problems)
973 " please check your install!\n") % problems)
973
974
974 return problems
975 return problems
975
976
976 def debugrename(ui, repo, file1, *pats, **opts):
977 def debugrename(ui, repo, file1, *pats, **opts):
977 """dump rename information"""
978 """dump rename information"""
978
979
979 ctx = repo.changectx(opts.get('rev', 'tip'))
980 ctx = repo.changectx(opts.get('rev', 'tip'))
980 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
981 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
981 ctx.node()):
982 ctx.node()):
982 m = ctx.filectx(abs).renamed()
983 m = ctx.filectx(abs).renamed()
983 if m:
984 if m:
984 ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1])))
985 ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1])))
985 else:
986 else:
986 ui.write(_("%s not renamed\n") % rel)
987 ui.write(_("%s not renamed\n") % rel)
987
988
988 def debugwalk(ui, repo, *pats, **opts):
989 def debugwalk(ui, repo, *pats, **opts):
989 """show how files match on given patterns"""
990 """show how files match on given patterns"""
990 items = list(cmdutil.walk(repo, pats, opts))
991 items = list(cmdutil.walk(repo, pats, opts))
991 if not items:
992 if not items:
992 return
993 return
993 fmt = '%%s %%-%ds %%-%ds %%s' % (
994 fmt = '%%s %%-%ds %%-%ds %%s' % (
994 max([len(abs) for (src, abs, rel, exact) in items]),
995 max([len(abs) for (src, abs, rel, exact) in items]),
995 max([len(rel) for (src, abs, rel, exact) in items]))
996 max([len(rel) for (src, abs, rel, exact) in items]))
996 for src, abs, rel, exact in items:
997 for src, abs, rel, exact in items:
997 line = fmt % (src, abs, rel, exact and 'exact' or '')
998 line = fmt % (src, abs, rel, exact and 'exact' or '')
998 ui.write("%s\n" % line.rstrip())
999 ui.write("%s\n" % line.rstrip())
999
1000
1000 def diff(ui, repo, *pats, **opts):
1001 def diff(ui, repo, *pats, **opts):
1001 """diff repository (or selected files)
1002 """diff repository (or selected files)
1002
1003
1003 Show differences between revisions for the specified files.
1004 Show differences between revisions for the specified files.
1004
1005
1005 Differences between files are shown using the unified diff format.
1006 Differences between files are shown using the unified diff format.
1006
1007
1007 NOTE: diff may generate unexpected results for merges, as it will
1008 NOTE: diff may generate unexpected results for merges, as it will
1008 default to comparing against the working directory's first parent
1009 default to comparing against the working directory's first parent
1009 changeset if no revisions are specified.
1010 changeset if no revisions are specified.
1010
1011
1011 When two revision arguments are given, then changes are shown
1012 When two revision arguments are given, then changes are shown
1012 between those revisions. If only one revision is specified then
1013 between those revisions. If only one revision is specified then
1013 that revision is compared to the working directory, and, when no
1014 that revision is compared to the working directory, and, when no
1014 revisions are specified, the working directory files are compared
1015 revisions are specified, the working directory files are compared
1015 to its parent.
1016 to its parent.
1016
1017
1017 Without the -a option, diff will avoid generating diffs of files
1018 Without the -a option, diff will avoid generating diffs of files
1018 it detects as binary. With -a, diff will generate a diff anyway,
1019 it detects as binary. With -a, diff will generate a diff anyway,
1019 probably with undesirable results.
1020 probably with undesirable results.
1020 """
1021 """
1021 node1, node2 = cmdutil.revpair(repo, opts['rev'])
1022 node1, node2 = cmdutil.revpair(repo, opts['rev'])
1022
1023
1023 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1024 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1024
1025
1025 patch.diff(repo, node1, node2, fns, match=matchfn,
1026 patch.diff(repo, node1, node2, fns, match=matchfn,
1026 opts=patch.diffopts(ui, opts))
1027 opts=patch.diffopts(ui, opts))
1027
1028
1028 def export(ui, repo, *changesets, **opts):
1029 def export(ui, repo, *changesets, **opts):
1029 """dump the header and diffs for one or more changesets
1030 """dump the header and diffs for one or more changesets
1030
1031
1031 Print the changeset header and diffs for one or more revisions.
1032 Print the changeset header and diffs for one or more revisions.
1032
1033
1033 The information shown in the changeset header is: author,
1034 The information shown in the changeset header is: author,
1034 changeset hash, parent(s) and commit comment.
1035 changeset hash, parent(s) and commit comment.
1035
1036
1036 NOTE: export may generate unexpected diff output for merge changesets,
1037 NOTE: export may generate unexpected diff output for merge changesets,
1037 as it will compare the merge changeset against its first parent only.
1038 as it will compare the merge changeset against its first parent only.
1038
1039
1039 Output may be to a file, in which case the name of the file is
1040 Output may be to a file, in which case the name of the file is
1040 given using a format string. The formatting rules are as follows:
1041 given using a format string. The formatting rules are as follows:
1041
1042
1042 %% literal "%" character
1043 %% literal "%" character
1043 %H changeset hash (40 bytes of hexadecimal)
1044 %H changeset hash (40 bytes of hexadecimal)
1044 %N number of patches being generated
1045 %N number of patches being generated
1045 %R changeset revision number
1046 %R changeset revision number
1046 %b basename of the exporting repository
1047 %b basename of the exporting repository
1047 %h short-form changeset hash (12 bytes of hexadecimal)
1048 %h short-form changeset hash (12 bytes of hexadecimal)
1048 %n zero-padded sequence number, starting at 1
1049 %n zero-padded sequence number, starting at 1
1049 %r zero-padded changeset revision number
1050 %r zero-padded changeset revision number
1050
1051
1051 Without the -a option, export will avoid generating diffs of files
1052 Without the -a option, export will avoid generating diffs of files
1052 it detects as binary. With -a, export will generate a diff anyway,
1053 it detects as binary. With -a, export will generate a diff anyway,
1053 probably with undesirable results.
1054 probably with undesirable results.
1054
1055
1055 With the --switch-parent option, the diff will be against the second
1056 With the --switch-parent option, the diff will be against the second
1056 parent. It can be useful to review a merge.
1057 parent. It can be useful to review a merge.
1057 """
1058 """
1058 if not changesets:
1059 if not changesets:
1059 raise util.Abort(_("export requires at least one changeset"))
1060 raise util.Abort(_("export requires at least one changeset"))
1060 revs = cmdutil.revrange(repo, changesets)
1061 revs = cmdutil.revrange(repo, changesets)
1061 if len(revs) > 1:
1062 if len(revs) > 1:
1062 ui.note(_('exporting patches:\n'))
1063 ui.note(_('exporting patches:\n'))
1063 else:
1064 else:
1064 ui.note(_('exporting patch:\n'))
1065 ui.note(_('exporting patch:\n'))
1065 patch.export(repo, revs, template=opts['output'],
1066 patch.export(repo, revs, template=opts['output'],
1066 switch_parent=opts['switch_parent'],
1067 switch_parent=opts['switch_parent'],
1067 opts=patch.diffopts(ui, opts))
1068 opts=patch.diffopts(ui, opts))
1068
1069
1069 def grep(ui, repo, pattern, *pats, **opts):
1070 def grep(ui, repo, pattern, *pats, **opts):
1070 """search for a pattern in specified files and revisions
1071 """search for a pattern in specified files and revisions
1071
1072
1072 Search revisions of files for a regular expression.
1073 Search revisions of files for a regular expression.
1073
1074
1074 This command behaves differently than Unix grep. It only accepts
1075 This command behaves differently than Unix grep. It only accepts
1075 Python/Perl regexps. It searches repository history, not the
1076 Python/Perl regexps. It searches repository history, not the
1076 working directory. It always prints the revision number in which
1077 working directory. It always prints the revision number in which
1077 a match appears.
1078 a match appears.
1078
1079
1079 By default, grep only prints output for the first revision of a
1080 By default, grep only prints output for the first revision of a
1080 file in which it finds a match. To get it to print every revision
1081 file in which it finds a match. To get it to print every revision
1081 that contains a change in match status ("-" for a match that
1082 that contains a change in match status ("-" for a match that
1082 becomes a non-match, or "+" for a non-match that becomes a match),
1083 becomes a non-match, or "+" for a non-match that becomes a match),
1083 use the --all flag.
1084 use the --all flag.
1084 """
1085 """
1085 reflags = 0
1086 reflags = 0
1086 if opts['ignore_case']:
1087 if opts['ignore_case']:
1087 reflags |= re.I
1088 reflags |= re.I
1088 try:
1089 try:
1089 regexp = re.compile(pattern, reflags)
1090 regexp = re.compile(pattern, reflags)
1090 except Exception, inst:
1091 except Exception, inst:
1091 ui.warn(_("grep: invalid match pattern: %s!\n") % inst)
1092 ui.warn(_("grep: invalid match pattern: %s!\n") % inst)
1092 return None
1093 return None
1093 sep, eol = ':', '\n'
1094 sep, eol = ':', '\n'
1094 if opts['print0']:
1095 if opts['print0']:
1095 sep = eol = '\0'
1096 sep = eol = '\0'
1096
1097
1097 fcache = {}
1098 fcache = {}
1098 def getfile(fn):
1099 def getfile(fn):
1099 if fn not in fcache:
1100 if fn not in fcache:
1100 fcache[fn] = repo.file(fn)
1101 fcache[fn] = repo.file(fn)
1101 return fcache[fn]
1102 return fcache[fn]
1102
1103
1103 def matchlines(body):
1104 def matchlines(body):
1104 begin = 0
1105 begin = 0
1105 linenum = 0
1106 linenum = 0
1106 while True:
1107 while True:
1107 match = regexp.search(body, begin)
1108 match = regexp.search(body, begin)
1108 if not match:
1109 if not match:
1109 break
1110 break
1110 mstart, mend = match.span()
1111 mstart, mend = match.span()
1111 linenum += body.count('\n', begin, mstart) + 1
1112 linenum += body.count('\n', begin, mstart) + 1
1112 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1113 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1113 lend = body.find('\n', mend)
1114 lend = body.find('\n', mend)
1114 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1115 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1115 begin = lend + 1
1116 begin = lend + 1
1116
1117
1117 class linestate(object):
1118 class linestate(object):
1118 def __init__(self, line, linenum, colstart, colend):
1119 def __init__(self, line, linenum, colstart, colend):
1119 self.line = line
1120 self.line = line
1120 self.linenum = linenum
1121 self.linenum = linenum
1121 self.colstart = colstart
1122 self.colstart = colstart
1122 self.colend = colend
1123 self.colend = colend
1123
1124
1124 def __eq__(self, other):
1125 def __eq__(self, other):
1125 return self.line == other.line
1126 return self.line == other.line
1126
1127
1127 matches = {}
1128 matches = {}
1128 copies = {}
1129 copies = {}
1129 def grepbody(fn, rev, body):
1130 def grepbody(fn, rev, body):
1130 matches[rev].setdefault(fn, [])
1131 matches[rev].setdefault(fn, [])
1131 m = matches[rev][fn]
1132 m = matches[rev][fn]
1132 for lnum, cstart, cend, line in matchlines(body):
1133 for lnum, cstart, cend, line in matchlines(body):
1133 s = linestate(line, lnum, cstart, cend)
1134 s = linestate(line, lnum, cstart, cend)
1134 m.append(s)
1135 m.append(s)
1135
1136
1136 def difflinestates(a, b):
1137 def difflinestates(a, b):
1137 sm = difflib.SequenceMatcher(None, a, b)
1138 sm = difflib.SequenceMatcher(None, a, b)
1138 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1139 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1139 if tag == 'insert':
1140 if tag == 'insert':
1140 for i in xrange(blo, bhi):
1141 for i in xrange(blo, bhi):
1141 yield ('+', b[i])
1142 yield ('+', b[i])
1142 elif tag == 'delete':
1143 elif tag == 'delete':
1143 for i in xrange(alo, ahi):
1144 for i in xrange(alo, ahi):
1144 yield ('-', a[i])
1145 yield ('-', a[i])
1145 elif tag == 'replace':
1146 elif tag == 'replace':
1146 for i in xrange(alo, ahi):
1147 for i in xrange(alo, ahi):
1147 yield ('-', a[i])
1148 yield ('-', a[i])
1148 for i in xrange(blo, bhi):
1149 for i in xrange(blo, bhi):
1149 yield ('+', b[i])
1150 yield ('+', b[i])
1150
1151
1151 prev = {}
1152 prev = {}
1152 def display(fn, rev, states, prevstates):
1153 def display(fn, rev, states, prevstates):
1153 found = False
1154 found = False
1154 filerevmatches = {}
1155 filerevmatches = {}
1155 r = prev.get(fn, -1)
1156 r = prev.get(fn, -1)
1156 if opts['all']:
1157 if opts['all']:
1157 iter = difflinestates(states, prevstates)
1158 iter = difflinestates(states, prevstates)
1158 else:
1159 else:
1159 iter = [('', l) for l in prevstates]
1160 iter = [('', l) for l in prevstates]
1160 for change, l in iter:
1161 for change, l in iter:
1161 cols = [fn, str(r)]
1162 cols = [fn, str(r)]
1162 if opts['line_number']:
1163 if opts['line_number']:
1163 cols.append(str(l.linenum))
1164 cols.append(str(l.linenum))
1164 if opts['all']:
1165 if opts['all']:
1165 cols.append(change)
1166 cols.append(change)
1166 if opts['user']:
1167 if opts['user']:
1167 cols.append(ui.shortuser(get(r)[1]))
1168 cols.append(ui.shortuser(get(r)[1]))
1168 if opts['files_with_matches']:
1169 if opts['files_with_matches']:
1169 c = (fn, r)
1170 c = (fn, r)
1170 if c in filerevmatches:
1171 if c in filerevmatches:
1171 continue
1172 continue
1172 filerevmatches[c] = 1
1173 filerevmatches[c] = 1
1173 else:
1174 else:
1174 cols.append(l.line)
1175 cols.append(l.line)
1175 ui.write(sep.join(cols), eol)
1176 ui.write(sep.join(cols), eol)
1176 found = True
1177 found = True
1177 return found
1178 return found
1178
1179
1179 fstate = {}
1180 fstate = {}
1180 skip = {}
1181 skip = {}
1181 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1182 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1182 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1183 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1183 found = False
1184 found = False
1184 follow = opts.get('follow')
1185 follow = opts.get('follow')
1185 for st, rev, fns in changeiter:
1186 for st, rev, fns in changeiter:
1186 if st == 'window':
1187 if st == 'window':
1187 matches.clear()
1188 matches.clear()
1188 elif st == 'add':
1189 elif st == 'add':
1189 mf = repo.changectx(rev).manifest()
1190 mf = repo.changectx(rev).manifest()
1190 matches[rev] = {}
1191 matches[rev] = {}
1191 for fn in fns:
1192 for fn in fns:
1192 if fn in skip:
1193 if fn in skip:
1193 continue
1194 continue
1194 try:
1195 try:
1195 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1196 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1196 fstate.setdefault(fn, [])
1197 fstate.setdefault(fn, [])
1197 if follow:
1198 if follow:
1198 copied = getfile(fn).renamed(mf[fn])
1199 copied = getfile(fn).renamed(mf[fn])
1199 if copied:
1200 if copied:
1200 copies.setdefault(rev, {})[fn] = copied[0]
1201 copies.setdefault(rev, {})[fn] = copied[0]
1201 except KeyError:
1202 except KeyError:
1202 pass
1203 pass
1203 elif st == 'iter':
1204 elif st == 'iter':
1204 states = matches[rev].items()
1205 states = matches[rev].items()
1205 states.sort()
1206 states.sort()
1206 for fn, m in states:
1207 for fn, m in states:
1207 copy = copies.get(rev, {}).get(fn)
1208 copy = copies.get(rev, {}).get(fn)
1208 if fn in skip:
1209 if fn in skip:
1209 if copy:
1210 if copy:
1210 skip[copy] = True
1211 skip[copy] = True
1211 continue
1212 continue
1212 if fn in prev or fstate[fn]:
1213 if fn in prev or fstate[fn]:
1213 r = display(fn, rev, m, fstate[fn])
1214 r = display(fn, rev, m, fstate[fn])
1214 found = found or r
1215 found = found or r
1215 if r and not opts['all']:
1216 if r and not opts['all']:
1216 skip[fn] = True
1217 skip[fn] = True
1217 if copy:
1218 if copy:
1218 skip[copy] = True
1219 skip[copy] = True
1219 fstate[fn] = m
1220 fstate[fn] = m
1220 if copy:
1221 if copy:
1221 fstate[copy] = m
1222 fstate[copy] = m
1222 prev[fn] = rev
1223 prev[fn] = rev
1223
1224
1224 fstate = fstate.items()
1225 fstate = fstate.items()
1225 fstate.sort()
1226 fstate.sort()
1226 for fn, state in fstate:
1227 for fn, state in fstate:
1227 if fn in skip:
1228 if fn in skip:
1228 continue
1229 continue
1229 if fn not in copies.get(prev[fn], {}):
1230 if fn not in copies.get(prev[fn], {}):
1230 found = display(fn, rev, {}, state) or found
1231 found = display(fn, rev, {}, state) or found
1231 return (not found and 1) or 0
1232 return (not found and 1) or 0
1232
1233
1233 def heads(ui, repo, *branchrevs, **opts):
1234 def heads(ui, repo, *branchrevs, **opts):
1234 """show current repository heads or show branch heads
1235 """show current repository heads or show branch heads
1235
1236
1236 With no arguments, show all repository head changesets.
1237 With no arguments, show all repository head changesets.
1237
1238
1238 If branch or revisions names are given this will show the heads of
1239 If branch or revisions names are given this will show the heads of
1239 the specified branches or the branches those revisions are tagged
1240 the specified branches or the branches those revisions are tagged
1240 with.
1241 with.
1241
1242
1242 Repository "heads" are changesets that don't have child
1243 Repository "heads" are changesets that don't have child
1243 changesets. They are where development generally takes place and
1244 changesets. They are where development generally takes place and
1244 are the usual targets for update and merge operations.
1245 are the usual targets for update and merge operations.
1245
1246
1246 Branch heads are changesets that have a given branch tag, but have
1247 Branch heads are changesets that have a given branch tag, but have
1247 no child changesets with that tag. They are usually where
1248 no child changesets with that tag. They are usually where
1248 development on the given branch takes place.
1249 development on the given branch takes place.
1249 """
1250 """
1250 if opts['rev']:
1251 if opts['rev']:
1251 start = repo.lookup(opts['rev'])
1252 start = repo.lookup(opts['rev'])
1252 else:
1253 else:
1253 start = None
1254 start = None
1254 if not branchrevs:
1255 if not branchrevs:
1255 # Assume we're looking repo-wide heads if no revs were specified.
1256 # Assume we're looking repo-wide heads if no revs were specified.
1256 heads = repo.heads(start)
1257 heads = repo.heads(start)
1257 else:
1258 else:
1258 heads = []
1259 heads = []
1259 visitedset = util.set()
1260 visitedset = util.set()
1260 for branchrev in branchrevs:
1261 for branchrev in branchrevs:
1261 branch = repo.changectx(branchrev).branch()
1262 branch = repo.changectx(branchrev).branch()
1262 if branch in visitedset:
1263 if branch in visitedset:
1263 continue
1264 continue
1264 visitedset.add(branch)
1265 visitedset.add(branch)
1265 bheads = repo.branchheads(branch, start)
1266 bheads = repo.branchheads(branch, start)
1266 if not bheads:
1267 if not bheads:
1267 if branch != branchrev:
1268 if branch != branchrev:
1268 ui.warn(_("no changes on branch %s containing %s are "
1269 ui.warn(_("no changes on branch %s containing %s are "
1269 "reachable from %s\n")
1270 "reachable from %s\n")
1270 % (branch, branchrev, opts['rev']))
1271 % (branch, branchrev, opts['rev']))
1271 else:
1272 else:
1272 ui.warn(_("no changes on branch %s are reachable from %s\n")
1273 ui.warn(_("no changes on branch %s are reachable from %s\n")
1273 % (branch, opts['rev']))
1274 % (branch, opts['rev']))
1274 heads.extend(bheads)
1275 heads.extend(bheads)
1275 if not heads:
1276 if not heads:
1276 return 1
1277 return 1
1277 displayer = cmdutil.show_changeset(ui, repo, opts)
1278 displayer = cmdutil.show_changeset(ui, repo, opts)
1278 for n in heads:
1279 for n in heads:
1279 displayer.show(changenode=n)
1280 displayer.show(changenode=n)
1280
1281
1281 def help_(ui, name=None, with_version=False):
1282 def help_(ui, name=None, with_version=False):
1282 """show help for a command, extension, or list of commands
1283 """show help for a command, extension, or list of commands
1283
1284
1284 With no arguments, print a list of commands and short help.
1285 With no arguments, print a list of commands and short help.
1285
1286
1286 Given a command name, print help for that command.
1287 Given a command name, print help for that command.
1287
1288
1288 Given an extension name, print help for that extension, and the
1289 Given an extension name, print help for that extension, and the
1289 commands it provides."""
1290 commands it provides."""
1290 option_lists = []
1291 option_lists = []
1291
1292
1292 def addglobalopts(aliases):
1293 def addglobalopts(aliases):
1293 if ui.verbose:
1294 if ui.verbose:
1294 option_lists.append((_("global options:"), globalopts))
1295 option_lists.append((_("global options:"), globalopts))
1295 if name == 'shortlist':
1296 if name == 'shortlist':
1296 option_lists.append((_('use "hg help" for the full list '
1297 option_lists.append((_('use "hg help" for the full list '
1297 'of commands'), ()))
1298 'of commands'), ()))
1298 else:
1299 else:
1299 if name == 'shortlist':
1300 if name == 'shortlist':
1300 msg = _('use "hg help" for the full list of commands '
1301 msg = _('use "hg help" for the full list of commands '
1301 'or "hg -v" for details')
1302 'or "hg -v" for details')
1302 elif aliases:
1303 elif aliases:
1303 msg = _('use "hg -v help%s" to show aliases and '
1304 msg = _('use "hg -v help%s" to show aliases and '
1304 'global options') % (name and " " + name or "")
1305 'global options') % (name and " " + name or "")
1305 else:
1306 else:
1306 msg = _('use "hg -v help %s" to show global options') % name
1307 msg = _('use "hg -v help %s" to show global options') % name
1307 option_lists.append((msg, ()))
1308 option_lists.append((msg, ()))
1308
1309
1309 def helpcmd(name):
1310 def helpcmd(name):
1310 if with_version:
1311 if with_version:
1311 version_(ui)
1312 version_(ui)
1312 ui.write('\n')
1313 ui.write('\n')
1313 aliases, i = cmdutil.findcmd(ui, name, table)
1314 aliases, i = cmdutil.findcmd(ui, name, table)
1314 # synopsis
1315 # synopsis
1315 ui.write("%s\n\n" % i[2])
1316 ui.write("%s\n\n" % i[2])
1316
1317
1317 # description
1318 # description
1318 doc = i[0].__doc__
1319 doc = i[0].__doc__
1319 if not doc:
1320 if not doc:
1320 doc = _("(No help text available)")
1321 doc = _("(No help text available)")
1321 if ui.quiet:
1322 if ui.quiet:
1322 doc = doc.splitlines(0)[0]
1323 doc = doc.splitlines(0)[0]
1323 ui.write("%s\n" % doc.rstrip())
1324 ui.write("%s\n" % doc.rstrip())
1324
1325
1325 if not ui.quiet:
1326 if not ui.quiet:
1326 # aliases
1327 # aliases
1327 if len(aliases) > 1:
1328 if len(aliases) > 1:
1328 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1329 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1329
1330
1330 # options
1331 # options
1331 if i[1]:
1332 if i[1]:
1332 option_lists.append((_("options:\n"), i[1]))
1333 option_lists.append((_("options:\n"), i[1]))
1333
1334
1334 addglobalopts(False)
1335 addglobalopts(False)
1335
1336
1336 def helplist(header, select=None):
1337 def helplist(header, select=None):
1337 h = {}
1338 h = {}
1338 cmds = {}
1339 cmds = {}
1339 for c, e in table.items():
1340 for c, e in table.items():
1340 f = c.split("|", 1)[0]
1341 f = c.split("|", 1)[0]
1341 if select and not select(f):
1342 if select and not select(f):
1342 continue
1343 continue
1343 if name == "shortlist" and not f.startswith("^"):
1344 if name == "shortlist" and not f.startswith("^"):
1344 continue
1345 continue
1345 f = f.lstrip("^")
1346 f = f.lstrip("^")
1346 if not ui.debugflag and f.startswith("debug"):
1347 if not ui.debugflag and f.startswith("debug"):
1347 continue
1348 continue
1348 doc = e[0].__doc__
1349 doc = e[0].__doc__
1349 if not doc:
1350 if not doc:
1350 doc = _("(No help text available)")
1351 doc = _("(No help text available)")
1351 h[f] = doc.splitlines(0)[0].rstrip()
1352 h[f] = doc.splitlines(0)[0].rstrip()
1352 cmds[f] = c.lstrip("^")
1353 cmds[f] = c.lstrip("^")
1353
1354
1354 if not h:
1355 if not h:
1355 ui.status(_('no commands defined\n'))
1356 ui.status(_('no commands defined\n'))
1356 return
1357 return
1357
1358
1358 ui.status(header)
1359 ui.status(header)
1359 fns = h.keys()
1360 fns = h.keys()
1360 fns.sort()
1361 fns.sort()
1361 m = max(map(len, fns))
1362 m = max(map(len, fns))
1362 for f in fns:
1363 for f in fns:
1363 if ui.verbose:
1364 if ui.verbose:
1364 commands = cmds[f].replace("|",", ")
1365 commands = cmds[f].replace("|",", ")
1365 ui.write(" %s:\n %s\n"%(commands, h[f]))
1366 ui.write(" %s:\n %s\n"%(commands, h[f]))
1366 else:
1367 else:
1367 ui.write(' %-*s %s\n' % (m, f, h[f]))
1368 ui.write(' %-*s %s\n' % (m, f, h[f]))
1368
1369
1369 if not ui.quiet:
1370 if not ui.quiet:
1370 addglobalopts(True)
1371 addglobalopts(True)
1371
1372
1372 def helptopic(name):
1373 def helptopic(name):
1373 v = None
1374 v = None
1374 for i in help.helptable:
1375 for i in help.helptable:
1375 l = i.split('|')
1376 l = i.split('|')
1376 if name in l:
1377 if name in l:
1377 v = i
1378 v = i
1378 header = l[-1]
1379 header = l[-1]
1379 if not v:
1380 if not v:
1380 raise cmdutil.UnknownCommand(name)
1381 raise cmdutil.UnknownCommand(name)
1381
1382
1382 # description
1383 # description
1383 doc = help.helptable[v]
1384 doc = help.helptable[v]
1384 if not doc:
1385 if not doc:
1385 doc = _("(No help text available)")
1386 doc = _("(No help text available)")
1386 if callable(doc):
1387 if callable(doc):
1387 doc = doc()
1388 doc = doc()
1388
1389
1389 ui.write("%s\n" % header)
1390 ui.write("%s\n" % header)
1390 ui.write("%s\n" % doc.rstrip())
1391 ui.write("%s\n" % doc.rstrip())
1391
1392
1392 def helpext(name):
1393 def helpext(name):
1393 try:
1394 try:
1394 mod = extensions.find(name)
1395 mod = extensions.find(name)
1395 except KeyError:
1396 except KeyError:
1396 raise cmdutil.UnknownCommand(name)
1397 raise cmdutil.UnknownCommand(name)
1397
1398
1398 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1399 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1399 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1400 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1400 for d in doc[1:]:
1401 for d in doc[1:]:
1401 ui.write(d, '\n')
1402 ui.write(d, '\n')
1402
1403
1403 ui.status('\n')
1404 ui.status('\n')
1404
1405
1405 try:
1406 try:
1406 ct = mod.cmdtable
1407 ct = mod.cmdtable
1407 except AttributeError:
1408 except AttributeError:
1408 ct = {}
1409 ct = {}
1409
1410
1410 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1411 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1411 helplist(_('list of commands:\n\n'), modcmds.has_key)
1412 helplist(_('list of commands:\n\n'), modcmds.has_key)
1412
1413
1413 if name and name != 'shortlist':
1414 if name and name != 'shortlist':
1414 i = None
1415 i = None
1415 for f in (helpcmd, helptopic, helpext):
1416 for f in (helpcmd, helptopic, helpext):
1416 try:
1417 try:
1417 f(name)
1418 f(name)
1418 i = None
1419 i = None
1419 break
1420 break
1420 except cmdutil.UnknownCommand, inst:
1421 except cmdutil.UnknownCommand, inst:
1421 i = inst
1422 i = inst
1422 if i:
1423 if i:
1423 raise i
1424 raise i
1424
1425
1425 else:
1426 else:
1426 # program name
1427 # program name
1427 if ui.verbose or with_version:
1428 if ui.verbose or with_version:
1428 version_(ui)
1429 version_(ui)
1429 else:
1430 else:
1430 ui.status(_("Mercurial Distributed SCM\n"))
1431 ui.status(_("Mercurial Distributed SCM\n"))
1431 ui.status('\n')
1432 ui.status('\n')
1432
1433
1433 # list of commands
1434 # list of commands
1434 if name == "shortlist":
1435 if name == "shortlist":
1435 header = _('basic commands:\n\n')
1436 header = _('basic commands:\n\n')
1436 else:
1437 else:
1437 header = _('list of commands:\n\n')
1438 header = _('list of commands:\n\n')
1438
1439
1439 helplist(header)
1440 helplist(header)
1440
1441
1441 # list all option lists
1442 # list all option lists
1442 opt_output = []
1443 opt_output = []
1443 for title, options in option_lists:
1444 for title, options in option_lists:
1444 opt_output.append(("\n%s" % title, None))
1445 opt_output.append(("\n%s" % title, None))
1445 for shortopt, longopt, default, desc in options:
1446 for shortopt, longopt, default, desc in options:
1446 if "DEPRECATED" in desc and not ui.verbose: continue
1447 if "DEPRECATED" in desc and not ui.verbose: continue
1447 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1448 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1448 longopt and " --%s" % longopt),
1449 longopt and " --%s" % longopt),
1449 "%s%s" % (desc,
1450 "%s%s" % (desc,
1450 default
1451 default
1451 and _(" (default: %s)") % default
1452 and _(" (default: %s)") % default
1452 or "")))
1453 or "")))
1453
1454
1454 if opt_output:
1455 if opt_output:
1455 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1456 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1456 for first, second in opt_output:
1457 for first, second in opt_output:
1457 if second:
1458 if second:
1458 ui.write(" %-*s %s\n" % (opts_len, first, second))
1459 ui.write(" %-*s %s\n" % (opts_len, first, second))
1459 else:
1460 else:
1460 ui.write("%s\n" % first)
1461 ui.write("%s\n" % first)
1461
1462
1462 def identify(ui, repo, source=None,
1463 def identify(ui, repo, source=None,
1463 rev=None, num=None, id=None, branch=None, tags=None):
1464 rev=None, num=None, id=None, branch=None, tags=None):
1464 """identify the working copy or specified revision
1465 """identify the working copy or specified revision
1465
1466
1466 With no revision, print a summary of the current state of the repo.
1467 With no revision, print a summary of the current state of the repo.
1467
1468
1468 With a path, do a lookup in another repository.
1469 With a path, do a lookup in another repository.
1469
1470
1470 This summary identifies the repository state using one or two parent
1471 This summary identifies the repository state using one or two parent
1471 hash identifiers, followed by a "+" if there are uncommitted changes
1472 hash identifiers, followed by a "+" if there are uncommitted changes
1472 in the working directory, a list of tags for this revision and a branch
1473 in the working directory, a list of tags for this revision and a branch
1473 name for non-default branches.
1474 name for non-default branches.
1474 """
1475 """
1475
1476
1476 if not repo and not source:
1477 if not repo and not source:
1477 raise util.Abort(_("There is no Mercurial repository here "
1478 raise util.Abort(_("There is no Mercurial repository here "
1478 "(.hg not found)"))
1479 "(.hg not found)"))
1479
1480
1480 hexfunc = ui.debugflag and hex or short
1481 hexfunc = ui.debugflag and hex or short
1481 default = not (num or id or branch or tags)
1482 default = not (num or id or branch or tags)
1482 output = []
1483 output = []
1483
1484
1484 if source:
1485 if source:
1485 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1486 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1486 srepo = hg.repository(ui, source)
1487 srepo = hg.repository(ui, source)
1487 if not rev and revs:
1488 if not rev and revs:
1488 rev = revs[0]
1489 rev = revs[0]
1489 if not rev:
1490 if not rev:
1490 rev = "tip"
1491 rev = "tip"
1491 if num or branch or tags:
1492 if num or branch or tags:
1492 raise util.Abort(
1493 raise util.Abort(
1493 "can't query remote revision number, branch, or tags")
1494 "can't query remote revision number, branch, or tags")
1494 output = [hexfunc(srepo.lookup(rev))]
1495 output = [hexfunc(srepo.lookup(rev))]
1495 elif not rev:
1496 elif not rev:
1496 ctx = repo.workingctx()
1497 ctx = repo.workingctx()
1497 parents = ctx.parents()
1498 parents = ctx.parents()
1498 changed = False
1499 changed = False
1499 if default or id or num:
1500 if default or id or num:
1500 changed = ctx.files() + ctx.deleted()
1501 changed = ctx.files() + ctx.deleted()
1501 if default or id:
1502 if default or id:
1502 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1503 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1503 (changed) and "+" or "")]
1504 (changed) and "+" or "")]
1504 if num:
1505 if num:
1505 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1506 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1506 (changed) and "+" or ""))
1507 (changed) and "+" or ""))
1507 else:
1508 else:
1508 ctx = repo.changectx(rev)
1509 ctx = repo.changectx(rev)
1509 if default or id:
1510 if default or id:
1510 output = [hexfunc(ctx.node())]
1511 output = [hexfunc(ctx.node())]
1511 if num:
1512 if num:
1512 output.append(str(ctx.rev()))
1513 output.append(str(ctx.rev()))
1513
1514
1514 if not source and default and not ui.quiet:
1515 if not source and default and not ui.quiet:
1515 b = util.tolocal(ctx.branch())
1516 b = util.tolocal(ctx.branch())
1516 if b != 'default':
1517 if b != 'default':
1517 output.append("(%s)" % b)
1518 output.append("(%s)" % b)
1518
1519
1519 # multiple tags for a single parent separated by '/'
1520 # multiple tags for a single parent separated by '/'
1520 t = "/".join(ctx.tags())
1521 t = "/".join(ctx.tags())
1521 if t:
1522 if t:
1522 output.append(t)
1523 output.append(t)
1523
1524
1524 if branch:
1525 if branch:
1525 output.append(util.tolocal(ctx.branch()))
1526 output.append(util.tolocal(ctx.branch()))
1526
1527
1527 if tags:
1528 if tags:
1528 output.extend(ctx.tags())
1529 output.extend(ctx.tags())
1529
1530
1530 ui.write("%s\n" % ' '.join(output))
1531 ui.write("%s\n" % ' '.join(output))
1531
1532
1532 def import_(ui, repo, patch1, *patches, **opts):
1533 def import_(ui, repo, patch1, *patches, **opts):
1533 """import an ordered set of patches
1534 """import an ordered set of patches
1534
1535
1535 Import a list of patches and commit them individually.
1536 Import a list of patches and commit them individually.
1536
1537
1537 If there are outstanding changes in the working directory, import
1538 If there are outstanding changes in the working directory, import
1538 will abort unless given the -f flag.
1539 will abort unless given the -f flag.
1539
1540
1540 You can import a patch straight from a mail message. Even patches
1541 You can import a patch straight from a mail message. Even patches
1541 as attachments work (body part must be type text/plain or
1542 as attachments work (body part must be type text/plain or
1542 text/x-patch to be used). From and Subject headers of email
1543 text/x-patch to be used). From and Subject headers of email
1543 message are used as default committer and commit message. All
1544 message are used as default committer and commit message. All
1544 text/plain body parts before first diff are added to commit
1545 text/plain body parts before first diff are added to commit
1545 message.
1546 message.
1546
1547
1547 If the imported patch was generated by hg export, user and description
1548 If the imported patch was generated by hg export, user and description
1548 from patch override values from message headers and body. Values
1549 from patch override values from message headers and body. Values
1549 given on command line with -m and -u override these.
1550 given on command line with -m and -u override these.
1550
1551
1551 If --exact is specified, import will set the working directory
1552 If --exact is specified, import will set the working directory
1552 to the parent of each patch before applying it, and will abort
1553 to the parent of each patch before applying it, and will abort
1553 if the resulting changeset has a different ID than the one
1554 if the resulting changeset has a different ID than the one
1554 recorded in the patch. This may happen due to character set
1555 recorded in the patch. This may happen due to character set
1555 problems or other deficiencies in the text patch format.
1556 problems or other deficiencies in the text patch format.
1556
1557
1557 To read a patch from standard input, use patch name "-".
1558 To read a patch from standard input, use patch name "-".
1558 """
1559 """
1559 patches = (patch1,) + patches
1560 patches = (patch1,) + patches
1560
1561
1561 if opts.get('exact') or not opts['force']:
1562 if opts.get('exact') or not opts['force']:
1562 cmdutil.bail_if_changed(repo)
1563 cmdutil.bail_if_changed(repo)
1563
1564
1564 d = opts["base"]
1565 d = opts["base"]
1565 strip = opts["strip"]
1566 strip = opts["strip"]
1566 wlock = lock = None
1567 wlock = lock = None
1567 try:
1568 try:
1568 wlock = repo.wlock()
1569 wlock = repo.wlock()
1569 lock = repo.lock()
1570 lock = repo.lock()
1570 for p in patches:
1571 for p in patches:
1571 pf = os.path.join(d, p)
1572 pf = os.path.join(d, p)
1572
1573
1573 if pf == '-':
1574 if pf == '-':
1574 ui.status(_("applying patch from stdin\n"))
1575 ui.status(_("applying patch from stdin\n"))
1575 data = patch.extract(ui, sys.stdin)
1576 data = patch.extract(ui, sys.stdin)
1576 else:
1577 else:
1577 ui.status(_("applying %s\n") % p)
1578 ui.status(_("applying %s\n") % p)
1578 if os.path.exists(pf):
1579 if os.path.exists(pf):
1579 data = patch.extract(ui, file(pf, 'rb'))
1580 data = patch.extract(ui, file(pf, 'rb'))
1580 else:
1581 else:
1581 data = patch.extract(ui, urllib.urlopen(pf))
1582 data = patch.extract(ui, urllib.urlopen(pf))
1582 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1583 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1583
1584
1584 if tmpname is None:
1585 if tmpname is None:
1585 raise util.Abort(_('no diffs found'))
1586 raise util.Abort(_('no diffs found'))
1586
1587
1587 try:
1588 try:
1588 cmdline_message = cmdutil.logmessage(opts)
1589 cmdline_message = cmdutil.logmessage(opts)
1589 if cmdline_message:
1590 if cmdline_message:
1590 # pickup the cmdline msg
1591 # pickup the cmdline msg
1591 message = cmdline_message
1592 message = cmdline_message
1592 elif message:
1593 elif message:
1593 # pickup the patch msg
1594 # pickup the patch msg
1594 message = message.strip()
1595 message = message.strip()
1595 else:
1596 else:
1596 # launch the editor
1597 # launch the editor
1597 message = None
1598 message = None
1598 ui.debug(_('message:\n%s\n') % message)
1599 ui.debug(_('message:\n%s\n') % message)
1599
1600
1600 wp = repo.workingctx().parents()
1601 wp = repo.workingctx().parents()
1601 if opts.get('exact'):
1602 if opts.get('exact'):
1602 if not nodeid or not p1:
1603 if not nodeid or not p1:
1603 raise util.Abort(_('not a mercurial patch'))
1604 raise util.Abort(_('not a mercurial patch'))
1604 p1 = repo.lookup(p1)
1605 p1 = repo.lookup(p1)
1605 p2 = repo.lookup(p2 or hex(nullid))
1606 p2 = repo.lookup(p2 or hex(nullid))
1606
1607
1607 if p1 != wp[0].node():
1608 if p1 != wp[0].node():
1608 hg.clean(repo, p1)
1609 hg.clean(repo, p1)
1609 repo.dirstate.setparents(p1, p2)
1610 repo.dirstate.setparents(p1, p2)
1610 elif p2:
1611 elif p2:
1611 try:
1612 try:
1612 p1 = repo.lookup(p1)
1613 p1 = repo.lookup(p1)
1613 p2 = repo.lookup(p2)
1614 p2 = repo.lookup(p2)
1614 if p1 == wp[0].node():
1615 if p1 == wp[0].node():
1615 repo.dirstate.setparents(p1, p2)
1616 repo.dirstate.setparents(p1, p2)
1616 except hg.RepoError:
1617 except hg.RepoError:
1617 pass
1618 pass
1618 if opts.get('exact') or opts.get('import_branch'):
1619 if opts.get('exact') or opts.get('import_branch'):
1619 repo.dirstate.setbranch(branch or 'default')
1620 repo.dirstate.setbranch(branch or 'default')
1620
1621
1621 files = {}
1622 files = {}
1622 try:
1623 try:
1623 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1624 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1624 files=files)
1625 files=files)
1625 finally:
1626 finally:
1626 files = patch.updatedir(ui, repo, files)
1627 files = patch.updatedir(ui, repo, files)
1627 n = repo.commit(files, message, user, date)
1628 n = repo.commit(files, message, user, date)
1628 if opts.get('exact'):
1629 if opts.get('exact'):
1629 if hex(n) != nodeid:
1630 if hex(n) != nodeid:
1630 repo.rollback()
1631 repo.rollback()
1631 raise util.Abort(_('patch is damaged' +
1632 raise util.Abort(_('patch is damaged' +
1632 ' or loses information'))
1633 ' or loses information'))
1633 finally:
1634 finally:
1634 os.unlink(tmpname)
1635 os.unlink(tmpname)
1635 finally:
1636 finally:
1636 del lock, wlock
1637 del lock, wlock
1637
1638
1638 def incoming(ui, repo, source="default", **opts):
1639 def incoming(ui, repo, source="default", **opts):
1639 """show new changesets found in source
1640 """show new changesets found in source
1640
1641
1641 Show new changesets found in the specified path/URL or the default
1642 Show new changesets found in the specified path/URL or the default
1642 pull location. These are the changesets that would be pulled if a pull
1643 pull location. These are the changesets that would be pulled if a pull
1643 was requested.
1644 was requested.
1644
1645
1645 For remote repository, using --bundle avoids downloading the changesets
1646 For remote repository, using --bundle avoids downloading the changesets
1646 twice if the incoming is followed by a pull.
1647 twice if the incoming is followed by a pull.
1647
1648
1648 See pull for valid source format details.
1649 See pull for valid source format details.
1649 """
1650 """
1650 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
1651 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
1651 cmdutil.setremoteconfig(ui, opts)
1652 cmdutil.setremoteconfig(ui, opts)
1652
1653
1653 other = hg.repository(ui, source)
1654 other = hg.repository(ui, source)
1654 ui.status(_('comparing with %s\n') % source)
1655 ui.status(_('comparing with %s\n') % source)
1655 if revs:
1656 if revs:
1656 revs = [other.lookup(rev) for rev in revs]
1657 revs = [other.lookup(rev) for rev in revs]
1657 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1658 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1658 if not incoming:
1659 if not incoming:
1659 try:
1660 try:
1660 os.unlink(opts["bundle"])
1661 os.unlink(opts["bundle"])
1661 except:
1662 except:
1662 pass
1663 pass
1663 ui.status(_("no changes found\n"))
1664 ui.status(_("no changes found\n"))
1664 return 1
1665 return 1
1665
1666
1666 cleanup = None
1667 cleanup = None
1667 try:
1668 try:
1668 fname = opts["bundle"]
1669 fname = opts["bundle"]
1669 if fname or not other.local():
1670 if fname or not other.local():
1670 # create a bundle (uncompressed if other repo is not local)
1671 # create a bundle (uncompressed if other repo is not local)
1671 if revs is None:
1672 if revs is None:
1672 cg = other.changegroup(incoming, "incoming")
1673 cg = other.changegroup(incoming, "incoming")
1673 else:
1674 else:
1674 cg = other.changegroupsubset(incoming, revs, 'incoming')
1675 cg = other.changegroupsubset(incoming, revs, 'incoming')
1675 bundletype = other.local() and "HG10BZ" or "HG10UN"
1676 bundletype = other.local() and "HG10BZ" or "HG10UN"
1676 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1677 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1677 # keep written bundle?
1678 # keep written bundle?
1678 if opts["bundle"]:
1679 if opts["bundle"]:
1679 cleanup = None
1680 cleanup = None
1680 if not other.local():
1681 if not other.local():
1681 # use the created uncompressed bundlerepo
1682 # use the created uncompressed bundlerepo
1682 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1683 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1683
1684
1684 o = other.changelog.nodesbetween(incoming, revs)[0]
1685 o = other.changelog.nodesbetween(incoming, revs)[0]
1685 if opts['newest_first']:
1686 if opts['newest_first']:
1686 o.reverse()
1687 o.reverse()
1687 displayer = cmdutil.show_changeset(ui, other, opts)
1688 displayer = cmdutil.show_changeset(ui, other, opts)
1688 for n in o:
1689 for n in o:
1689 parents = [p for p in other.changelog.parents(n) if p != nullid]
1690 parents = [p for p in other.changelog.parents(n) if p != nullid]
1690 if opts['no_merges'] and len(parents) == 2:
1691 if opts['no_merges'] and len(parents) == 2:
1691 continue
1692 continue
1692 displayer.show(changenode=n)
1693 displayer.show(changenode=n)
1693 finally:
1694 finally:
1694 if hasattr(other, 'close'):
1695 if hasattr(other, 'close'):
1695 other.close()
1696 other.close()
1696 if cleanup:
1697 if cleanup:
1697 os.unlink(cleanup)
1698 os.unlink(cleanup)
1698
1699
1699 def init(ui, dest=".", **opts):
1700 def init(ui, dest=".", **opts):
1700 """create a new repository in the given directory
1701 """create a new repository in the given directory
1701
1702
1702 Initialize a new repository in the given directory. If the given
1703 Initialize a new repository in the given directory. If the given
1703 directory does not exist, it is created.
1704 directory does not exist, it is created.
1704
1705
1705 If no directory is given, the current directory is used.
1706 If no directory is given, the current directory is used.
1706
1707
1707 It is possible to specify an ssh:// URL as the destination.
1708 It is possible to specify an ssh:// URL as the destination.
1708 Look at the help text for the pull command for important details
1709 Look at the help text for the pull command for important details
1709 about ssh:// URLs.
1710 about ssh:// URLs.
1710 """
1711 """
1711 cmdutil.setremoteconfig(ui, opts)
1712 cmdutil.setremoteconfig(ui, opts)
1712 hg.repository(ui, dest, create=1)
1713 hg.repository(ui, dest, create=1)
1713
1714
1714 def locate(ui, repo, *pats, **opts):
1715 def locate(ui, repo, *pats, **opts):
1715 """locate files matching specific patterns
1716 """locate files matching specific patterns
1716
1717
1717 Print all files under Mercurial control whose names match the
1718 Print all files under Mercurial control whose names match the
1718 given patterns.
1719 given patterns.
1719
1720
1720 This command searches the entire repository by default. To search
1721 This command searches the entire repository by default. To search
1721 just the current directory and its subdirectories, use
1722 just the current directory and its subdirectories, use
1722 "--include .".
1723 "--include .".
1723
1724
1724 If no patterns are given to match, this command prints all file
1725 If no patterns are given to match, this command prints all file
1725 names.
1726 names.
1726
1727
1727 If you want to feed the output of this command into the "xargs"
1728 If you want to feed the output of this command into the "xargs"
1728 command, use the "-0" option to both this command and "xargs".
1729 command, use the "-0" option to both this command and "xargs".
1729 This will avoid the problem of "xargs" treating single filenames
1730 This will avoid the problem of "xargs" treating single filenames
1730 that contain white space as multiple filenames.
1731 that contain white space as multiple filenames.
1731 """
1732 """
1732 end = opts['print0'] and '\0' or '\n'
1733 end = opts['print0'] and '\0' or '\n'
1733 rev = opts['rev']
1734 rev = opts['rev']
1734 if rev:
1735 if rev:
1735 node = repo.lookup(rev)
1736 node = repo.lookup(rev)
1736 else:
1737 else:
1737 node = None
1738 node = None
1738
1739
1739 ret = 1
1740 ret = 1
1740 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1741 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1741 badmatch=util.always,
1742 badmatch=util.always,
1742 default='relglob'):
1743 default='relglob'):
1743 if src == 'b':
1744 if src == 'b':
1744 continue
1745 continue
1745 if not node and abs not in repo.dirstate:
1746 if not node and abs not in repo.dirstate:
1746 continue
1747 continue
1747 if opts['fullpath']:
1748 if opts['fullpath']:
1748 ui.write(os.path.join(repo.root, abs), end)
1749 ui.write(os.path.join(repo.root, abs), end)
1749 else:
1750 else:
1750 ui.write(((pats and rel) or abs), end)
1751 ui.write(((pats and rel) or abs), end)
1751 ret = 0
1752 ret = 0
1752
1753
1753 return ret
1754 return ret
1754
1755
1755 def log(ui, repo, *pats, **opts):
1756 def log(ui, repo, *pats, **opts):
1756 """show revision history of entire repository or files
1757 """show revision history of entire repository or files
1757
1758
1758 Print the revision history of the specified files or the entire
1759 Print the revision history of the specified files or the entire
1759 project.
1760 project.
1760
1761
1761 File history is shown without following rename or copy history of
1762 File history is shown without following rename or copy history of
1762 files. Use -f/--follow with a file name to follow history across
1763 files. Use -f/--follow with a file name to follow history across
1763 renames and copies. --follow without a file name will only show
1764 renames and copies. --follow without a file name will only show
1764 ancestors or descendants of the starting revision. --follow-first
1765 ancestors or descendants of the starting revision. --follow-first
1765 only follows the first parent of merge revisions.
1766 only follows the first parent of merge revisions.
1766
1767
1767 If no revision range is specified, the default is tip:0 unless
1768 If no revision range is specified, the default is tip:0 unless
1768 --follow is set, in which case the working directory parent is
1769 --follow is set, in which case the working directory parent is
1769 used as the starting revision.
1770 used as the starting revision.
1770
1771
1771 By default this command outputs: changeset id and hash, tags,
1772 By default this command outputs: changeset id and hash, tags,
1772 non-trivial parents, user, date and time, and a summary for each
1773 non-trivial parents, user, date and time, and a summary for each
1773 commit. When the -v/--verbose switch is used, the list of changed
1774 commit. When the -v/--verbose switch is used, the list of changed
1774 files and full commit message is shown.
1775 files and full commit message is shown.
1775
1776
1776 NOTE: log -p may generate unexpected diff output for merge
1777 NOTE: log -p may generate unexpected diff output for merge
1777 changesets, as it will compare the merge changeset against its
1778 changesets, as it will compare the merge changeset against its
1778 first parent only. Also, the files: list will only reflect files
1779 first parent only. Also, the files: list will only reflect files
1779 that are different from BOTH parents.
1780 that are different from BOTH parents.
1780
1781
1781 """
1782 """
1782
1783
1783 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1784 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1784 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1785 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1785
1786
1786 if opts['limit']:
1787 if opts['limit']:
1787 try:
1788 try:
1788 limit = int(opts['limit'])
1789 limit = int(opts['limit'])
1789 except ValueError:
1790 except ValueError:
1790 raise util.Abort(_('limit must be a positive integer'))
1791 raise util.Abort(_('limit must be a positive integer'))
1791 if limit <= 0: raise util.Abort(_('limit must be positive'))
1792 if limit <= 0: raise util.Abort(_('limit must be positive'))
1792 else:
1793 else:
1793 limit = sys.maxint
1794 limit = sys.maxint
1794 count = 0
1795 count = 0
1795
1796
1796 if opts['copies'] and opts['rev']:
1797 if opts['copies'] and opts['rev']:
1797 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1798 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1798 else:
1799 else:
1799 endrev = repo.changelog.count()
1800 endrev = repo.changelog.count()
1800 rcache = {}
1801 rcache = {}
1801 ncache = {}
1802 ncache = {}
1802 dcache = []
1803 dcache = []
1803 def getrenamed(fn, rev, man):
1804 def getrenamed(fn, rev, man):
1804 '''looks up all renames for a file (up to endrev) the first
1805 '''looks up all renames for a file (up to endrev) the first
1805 time the file is given. It indexes on the changerev and only
1806 time the file is given. It indexes on the changerev and only
1806 parses the manifest if linkrev != changerev.
1807 parses the manifest if linkrev != changerev.
1807 Returns rename info for fn at changerev rev.'''
1808 Returns rename info for fn at changerev rev.'''
1808 if fn not in rcache:
1809 if fn not in rcache:
1809 rcache[fn] = {}
1810 rcache[fn] = {}
1810 ncache[fn] = {}
1811 ncache[fn] = {}
1811 fl = repo.file(fn)
1812 fl = repo.file(fn)
1812 for i in xrange(fl.count()):
1813 for i in xrange(fl.count()):
1813 node = fl.node(i)
1814 node = fl.node(i)
1814 lr = fl.linkrev(node)
1815 lr = fl.linkrev(node)
1815 renamed = fl.renamed(node)
1816 renamed = fl.renamed(node)
1816 rcache[fn][lr] = renamed
1817 rcache[fn][lr] = renamed
1817 if renamed:
1818 if renamed:
1818 ncache[fn][node] = renamed
1819 ncache[fn][node] = renamed
1819 if lr >= endrev:
1820 if lr >= endrev:
1820 break
1821 break
1821 if rev in rcache[fn]:
1822 if rev in rcache[fn]:
1822 return rcache[fn][rev]
1823 return rcache[fn][rev]
1823 mr = repo.manifest.rev(man)
1824 mr = repo.manifest.rev(man)
1824 if repo.manifest.parentrevs(mr) != (mr - 1, nullrev):
1825 if repo.manifest.parentrevs(mr) != (mr - 1, nullrev):
1825 return ncache[fn].get(repo.manifest.find(man, fn)[0])
1826 return ncache[fn].get(repo.manifest.find(man, fn)[0])
1826 if not dcache or dcache[0] != man:
1827 if not dcache or dcache[0] != man:
1827 dcache[:] = [man, repo.manifest.readdelta(man)]
1828 dcache[:] = [man, repo.manifest.readdelta(man)]
1828 if fn in dcache[1]:
1829 if fn in dcache[1]:
1829 return ncache[fn].get(dcache[1][fn])
1830 return ncache[fn].get(dcache[1][fn])
1830 return None
1831 return None
1831
1832
1832 df = False
1833 df = False
1833 if opts["date"]:
1834 if opts["date"]:
1834 df = util.matchdate(opts["date"])
1835 df = util.matchdate(opts["date"])
1835
1836
1836 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1837 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1837 for st, rev, fns in changeiter:
1838 for st, rev, fns in changeiter:
1838 if st == 'add':
1839 if st == 'add':
1839 changenode = repo.changelog.node(rev)
1840 changenode = repo.changelog.node(rev)
1840 parents = [p for p in repo.changelog.parentrevs(rev)
1841 parents = [p for p in repo.changelog.parentrevs(rev)
1841 if p != nullrev]
1842 if p != nullrev]
1842 if opts['no_merges'] and len(parents) == 2:
1843 if opts['no_merges'] and len(parents) == 2:
1843 continue
1844 continue
1844 if opts['only_merges'] and len(parents) != 2:
1845 if opts['only_merges'] and len(parents) != 2:
1845 continue
1846 continue
1846
1847
1847 if df:
1848 if df:
1848 changes = get(rev)
1849 changes = get(rev)
1849 if not df(changes[2][0]):
1850 if not df(changes[2][0]):
1850 continue
1851 continue
1851
1852
1852 if opts['keyword']:
1853 if opts['keyword']:
1853 changes = get(rev)
1854 changes = get(rev)
1854 miss = 0
1855 miss = 0
1855 for k in [kw.lower() for kw in opts['keyword']]:
1856 for k in [kw.lower() for kw in opts['keyword']]:
1856 if not (k in changes[1].lower() or
1857 if not (k in changes[1].lower() or
1857 k in changes[4].lower() or
1858 k in changes[4].lower() or
1858 k in " ".join(changes[3]).lower()):
1859 k in " ".join(changes[3]).lower()):
1859 miss = 1
1860 miss = 1
1860 break
1861 break
1861 if miss:
1862 if miss:
1862 continue
1863 continue
1863
1864
1864 copies = []
1865 copies = []
1865 if opts.get('copies') and rev:
1866 if opts.get('copies') and rev:
1866 mf = get(rev)[0]
1867 mf = get(rev)[0]
1867 for fn in get(rev)[3]:
1868 for fn in get(rev)[3]:
1868 rename = getrenamed(fn, rev, mf)
1869 rename = getrenamed(fn, rev, mf)
1869 if rename:
1870 if rename:
1870 copies.append((fn, rename[0]))
1871 copies.append((fn, rename[0]))
1871 displayer.show(rev, changenode, copies=copies)
1872 displayer.show(rev, changenode, copies=copies)
1872 elif st == 'iter':
1873 elif st == 'iter':
1873 if count == limit: break
1874 if count == limit: break
1874 if displayer.flush(rev):
1875 if displayer.flush(rev):
1875 count += 1
1876 count += 1
1876
1877
1877 def manifest(ui, repo, node=None, rev=None):
1878 def manifest(ui, repo, node=None, rev=None):
1878 """output the current or given revision of the project manifest
1879 """output the current or given revision of the project manifest
1879
1880
1880 Print a list of version controlled files for the given revision.
1881 Print a list of version controlled files for the given revision.
1881 If no revision is given, the parent of the working directory is used,
1882 If no revision is given, the parent of the working directory is used,
1882 or tip if no revision is checked out.
1883 or tip if no revision is checked out.
1883
1884
1884 The manifest is the list of files being version controlled. If no revision
1885 The manifest is the list of files being version controlled. If no revision
1885 is given then the first parent of the working directory is used.
1886 is given then the first parent of the working directory is used.
1886
1887
1887 With -v flag, print file permissions, symlink and executable bits. With
1888 With -v flag, print file permissions, symlink and executable bits. With
1888 --debug flag, print file revision hashes.
1889 --debug flag, print file revision hashes.
1889 """
1890 """
1890
1891
1891 if rev and node:
1892 if rev and node:
1892 raise util.Abort(_("please specify just one revision"))
1893 raise util.Abort(_("please specify just one revision"))
1893
1894
1894 if not node:
1895 if not node:
1895 node = rev
1896 node = rev
1896
1897
1897 m = repo.changectx(node).manifest()
1898 m = repo.changectx(node).manifest()
1898 files = m.keys()
1899 files = m.keys()
1899 files.sort()
1900 files.sort()
1900
1901
1901 for f in files:
1902 for f in files:
1902 if ui.debugflag:
1903 if ui.debugflag:
1903 ui.write("%40s " % hex(m[f]))
1904 ui.write("%40s " % hex(m[f]))
1904 if ui.verbose:
1905 if ui.verbose:
1905 type = m.execf(f) and "*" or m.linkf(f) and "@" or " "
1906 type = m.execf(f) and "*" or m.linkf(f) and "@" or " "
1906 perm = m.execf(f) and "755" or "644"
1907 perm = m.execf(f) and "755" or "644"
1907 ui.write("%3s %1s " % (perm, type))
1908 ui.write("%3s %1s " % (perm, type))
1908 ui.write("%s\n" % f)
1909 ui.write("%s\n" % f)
1909
1910
1910 def merge(ui, repo, node=None, force=None, rev=None):
1911 def merge(ui, repo, node=None, force=None, rev=None):
1911 """merge working directory with another revision
1912 """merge working directory with another revision
1912
1913
1913 Merge the contents of the current working directory and the
1914 Merge the contents of the current working directory and the
1914 requested revision. Files that changed between either parent are
1915 requested revision. Files that changed between either parent are
1915 marked as changed for the next commit and a commit must be
1916 marked as changed for the next commit and a commit must be
1916 performed before any further updates are allowed.
1917 performed before any further updates are allowed.
1917
1918
1918 If no revision is specified, the working directory's parent is a
1919 If no revision is specified, the working directory's parent is a
1919 head revision, and the repository contains exactly one other head,
1920 head revision, and the repository contains exactly one other head,
1920 the other head is merged with by default. Otherwise, an explicit
1921 the other head is merged with by default. Otherwise, an explicit
1921 revision to merge with must be provided.
1922 revision to merge with must be provided.
1922 """
1923 """
1923
1924
1924 if rev and node:
1925 if rev and node:
1925 raise util.Abort(_("please specify just one revision"))
1926 raise util.Abort(_("please specify just one revision"))
1926 if not node:
1927 if not node:
1927 node = rev
1928 node = rev
1928
1929
1929 if not node:
1930 if not node:
1930 heads = repo.heads()
1931 heads = repo.heads()
1931 if len(heads) > 2:
1932 if len(heads) > 2:
1932 raise util.Abort(_('repo has %d heads - '
1933 raise util.Abort(_('repo has %d heads - '
1933 'please merge with an explicit rev') %
1934 'please merge with an explicit rev') %
1934 len(heads))
1935 len(heads))
1935 parent = repo.dirstate.parents()[0]
1936 parent = repo.dirstate.parents()[0]
1936 if len(heads) == 1:
1937 if len(heads) == 1:
1937 msg = _('there is nothing to merge')
1938 msg = _('there is nothing to merge')
1938 if parent != repo.lookup(repo.workingctx().branch()):
1939 if parent != repo.lookup(repo.workingctx().branch()):
1939 msg = _('%s - use "hg update" instead' % msg)
1940 msg = _('%s - use "hg update" instead' % msg)
1940 raise util.Abort(msg)
1941 raise util.Abort(msg)
1941
1942
1942 if parent not in heads:
1943 if parent not in heads:
1943 raise util.Abort(_('working dir not at a head rev - '
1944 raise util.Abort(_('working dir not at a head rev - '
1944 'use "hg update" or merge with an explicit rev'))
1945 'use "hg update" or merge with an explicit rev'))
1945 node = parent == heads[0] and heads[-1] or heads[0]
1946 node = parent == heads[0] and heads[-1] or heads[0]
1946 return hg.merge(repo, node, force=force)
1947 return hg.merge(repo, node, force=force)
1947
1948
1948 def outgoing(ui, repo, dest=None, **opts):
1949 def outgoing(ui, repo, dest=None, **opts):
1949 """show changesets not found in destination
1950 """show changesets not found in destination
1950
1951
1951 Show changesets not found in the specified destination repository or
1952 Show changesets not found in the specified destination repository or
1952 the default push location. These are the changesets that would be pushed
1953 the default push location. These are the changesets that would be pushed
1953 if a push was requested.
1954 if a push was requested.
1954
1955
1955 See pull for valid destination format details.
1956 See pull for valid destination format details.
1956 """
1957 """
1957 dest, revs, checkout = hg.parseurl(
1958 dest, revs, checkout = hg.parseurl(
1958 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1959 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1959 cmdutil.setremoteconfig(ui, opts)
1960 cmdutil.setremoteconfig(ui, opts)
1960 if revs:
1961 if revs:
1961 revs = [repo.lookup(rev) for rev in revs]
1962 revs = [repo.lookup(rev) for rev in revs]
1962
1963
1963 other = hg.repository(ui, dest)
1964 other = hg.repository(ui, dest)
1964 ui.status(_('comparing with %s\n') % dest)
1965 ui.status(_('comparing with %s\n') % dest)
1965 o = repo.findoutgoing(other, force=opts['force'])
1966 o = repo.findoutgoing(other, force=opts['force'])
1966 if not o:
1967 if not o:
1967 ui.status(_("no changes found\n"))
1968 ui.status(_("no changes found\n"))
1968 return 1
1969 return 1
1969 o = repo.changelog.nodesbetween(o, revs)[0]
1970 o = repo.changelog.nodesbetween(o, revs)[0]
1970 if opts['newest_first']:
1971 if opts['newest_first']:
1971 o.reverse()
1972 o.reverse()
1972 displayer = cmdutil.show_changeset(ui, repo, opts)
1973 displayer = cmdutil.show_changeset(ui, repo, opts)
1973 for n in o:
1974 for n in o:
1974 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1975 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1975 if opts['no_merges'] and len(parents) == 2:
1976 if opts['no_merges'] and len(parents) == 2:
1976 continue
1977 continue
1977 displayer.show(changenode=n)
1978 displayer.show(changenode=n)
1978
1979
1979 def parents(ui, repo, file_=None, **opts):
1980 def parents(ui, repo, file_=None, **opts):
1980 """show the parents of the working dir or revision
1981 """show the parents of the working dir or revision
1981
1982
1982 Print the working directory's parent revisions. If a
1983 Print the working directory's parent revisions. If a
1983 revision is given via --rev, the parent of that revision
1984 revision is given via --rev, the parent of that revision
1984 will be printed. If a file argument is given, revision in
1985 will be printed. If a file argument is given, revision in
1985 which the file was last changed (before the working directory
1986 which the file was last changed (before the working directory
1986 revision or the argument to --rev if given) is printed.
1987 revision or the argument to --rev if given) is printed.
1987 """
1988 """
1988 rev = opts.get('rev')
1989 rev = opts.get('rev')
1989 if rev:
1990 if rev:
1990 ctx = repo.changectx(rev)
1991 ctx = repo.changectx(rev)
1991 else:
1992 else:
1992 ctx = repo.workingctx()
1993 ctx = repo.workingctx()
1993
1994
1994 if file_:
1995 if file_:
1995 files, match, anypats = cmdutil.matchpats(repo, (file_,), opts)
1996 files, match, anypats = cmdutil.matchpats(repo, (file_,), opts)
1996 if anypats or len(files) != 1:
1997 if anypats or len(files) != 1:
1997 raise util.Abort(_('can only specify an explicit file name'))
1998 raise util.Abort(_('can only specify an explicit file name'))
1998 file_ = files[0]
1999 file_ = files[0]
1999 filenodes = []
2000 filenodes = []
2000 for cp in ctx.parents():
2001 for cp in ctx.parents():
2001 if not cp:
2002 if not cp:
2002 continue
2003 continue
2003 try:
2004 try:
2004 filenodes.append(cp.filenode(file_))
2005 filenodes.append(cp.filenode(file_))
2005 except revlog.LookupError:
2006 except revlog.LookupError:
2006 pass
2007 pass
2007 if not filenodes:
2008 if not filenodes:
2008 raise util.Abort(_("'%s' not found in manifest!") % file_)
2009 raise util.Abort(_("'%s' not found in manifest!") % file_)
2009 fl = repo.file(file_)
2010 fl = repo.file(file_)
2010 p = [repo.lookup(fl.linkrev(fn)) for fn in filenodes]
2011 p = [repo.lookup(fl.linkrev(fn)) for fn in filenodes]
2011 else:
2012 else:
2012 p = [cp.node() for cp in ctx.parents()]
2013 p = [cp.node() for cp in ctx.parents()]
2013
2014
2014 displayer = cmdutil.show_changeset(ui, repo, opts)
2015 displayer = cmdutil.show_changeset(ui, repo, opts)
2015 for n in p:
2016 for n in p:
2016 if n != nullid:
2017 if n != nullid:
2017 displayer.show(changenode=n)
2018 displayer.show(changenode=n)
2018
2019
2019 def paths(ui, repo, search=None):
2020 def paths(ui, repo, search=None):
2020 """show definition of symbolic path names
2021 """show definition of symbolic path names
2021
2022
2022 Show definition of symbolic path name NAME. If no name is given, show
2023 Show definition of symbolic path name NAME. If no name is given, show
2023 definition of available names.
2024 definition of available names.
2024
2025
2025 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2026 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2026 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2027 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2027 """
2028 """
2028 if search:
2029 if search:
2029 for name, path in ui.configitems("paths"):
2030 for name, path in ui.configitems("paths"):
2030 if name == search:
2031 if name == search:
2031 ui.write("%s\n" % path)
2032 ui.write("%s\n" % path)
2032 return
2033 return
2033 ui.warn(_("not found!\n"))
2034 ui.warn(_("not found!\n"))
2034 return 1
2035 return 1
2035 else:
2036 else:
2036 for name, path in ui.configitems("paths"):
2037 for name, path in ui.configitems("paths"):
2037 ui.write("%s = %s\n" % (name, path))
2038 ui.write("%s = %s\n" % (name, path))
2038
2039
2039 def postincoming(ui, repo, modheads, optupdate, checkout):
2040 def postincoming(ui, repo, modheads, optupdate, checkout):
2040 if modheads == 0:
2041 if modheads == 0:
2041 return
2042 return
2042 if optupdate:
2043 if optupdate:
2043 if modheads <= 1 or checkout:
2044 if modheads <= 1 or checkout:
2044 return hg.update(repo, checkout)
2045 return hg.update(repo, checkout)
2045 else:
2046 else:
2046 ui.status(_("not updating, since new heads added\n"))
2047 ui.status(_("not updating, since new heads added\n"))
2047 if modheads > 1:
2048 if modheads > 1:
2048 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2049 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2049 else:
2050 else:
2050 ui.status(_("(run 'hg update' to get a working copy)\n"))
2051 ui.status(_("(run 'hg update' to get a working copy)\n"))
2051
2052
2052 def pull(ui, repo, source="default", **opts):
2053 def pull(ui, repo, source="default", **opts):
2053 """pull changes from the specified source
2054 """pull changes from the specified source
2054
2055
2055 Pull changes from a remote repository to a local one.
2056 Pull changes from a remote repository to a local one.
2056
2057
2057 This finds all changes from the repository at the specified path
2058 This finds all changes from the repository at the specified path
2058 or URL and adds them to the local repository. By default, this
2059 or URL and adds them to the local repository. By default, this
2059 does not update the copy of the project in the working directory.
2060 does not update the copy of the project in the working directory.
2060
2061
2061 Valid URLs are of the form:
2062 Valid URLs are of the form:
2062
2063
2063 local/filesystem/path (or file://local/filesystem/path)
2064 local/filesystem/path (or file://local/filesystem/path)
2064 http://[user@]host[:port]/[path]
2065 http://[user@]host[:port]/[path]
2065 https://[user@]host[:port]/[path]
2066 https://[user@]host[:port]/[path]
2066 ssh://[user@]host[:port]/[path]
2067 ssh://[user@]host[:port]/[path]
2067 static-http://host[:port]/[path]
2068 static-http://host[:port]/[path]
2068
2069
2069 Paths in the local filesystem can either point to Mercurial
2070 Paths in the local filesystem can either point to Mercurial
2070 repositories or to bundle files (as created by 'hg bundle' or
2071 repositories or to bundle files (as created by 'hg bundle' or
2071 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
2072 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
2072 allows access to a Mercurial repository where you simply use a web
2073 allows access to a Mercurial repository where you simply use a web
2073 server to publish the .hg directory as static content.
2074 server to publish the .hg directory as static content.
2074
2075
2075 An optional identifier after # indicates a particular branch, tag,
2076 An optional identifier after # indicates a particular branch, tag,
2076 or changeset to pull.
2077 or changeset to pull.
2077
2078
2078 Some notes about using SSH with Mercurial:
2079 Some notes about using SSH with Mercurial:
2079 - SSH requires an accessible shell account on the destination machine
2080 - SSH requires an accessible shell account on the destination machine
2080 and a copy of hg in the remote path or specified with as remotecmd.
2081 and a copy of hg in the remote path or specified with as remotecmd.
2081 - path is relative to the remote user's home directory by default.
2082 - path is relative to the remote user's home directory by default.
2082 Use an extra slash at the start of a path to specify an absolute path:
2083 Use an extra slash at the start of a path to specify an absolute path:
2083 ssh://example.com//tmp/repository
2084 ssh://example.com//tmp/repository
2084 - Mercurial doesn't use its own compression via SSH; the right thing
2085 - Mercurial doesn't use its own compression via SSH; the right thing
2085 to do is to configure it in your ~/.ssh/config, e.g.:
2086 to do is to configure it in your ~/.ssh/config, e.g.:
2086 Host *.mylocalnetwork.example.com
2087 Host *.mylocalnetwork.example.com
2087 Compression no
2088 Compression no
2088 Host *
2089 Host *
2089 Compression yes
2090 Compression yes
2090 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2091 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2091 with the --ssh command line option.
2092 with the --ssh command line option.
2092 """
2093 """
2093 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
2094 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
2094 cmdutil.setremoteconfig(ui, opts)
2095 cmdutil.setremoteconfig(ui, opts)
2095
2096
2096 other = hg.repository(ui, source)
2097 other = hg.repository(ui, source)
2097 ui.status(_('pulling from %s\n') % (source))
2098 ui.status(_('pulling from %s\n') % (source))
2098 if revs:
2099 if revs:
2099 try:
2100 try:
2100 revs = [other.lookup(rev) for rev in revs]
2101 revs = [other.lookup(rev) for rev in revs]
2101 except repo.NoCapability:
2102 except repo.NoCapability:
2102 error = _("Other repository doesn't support revision lookup, "
2103 error = _("Other repository doesn't support revision lookup, "
2103 "so a rev cannot be specified.")
2104 "so a rev cannot be specified.")
2104 raise util.Abort(error)
2105 raise util.Abort(error)
2105
2106
2106 modheads = repo.pull(other, heads=revs, force=opts['force'])
2107 modheads = repo.pull(other, heads=revs, force=opts['force'])
2107 return postincoming(ui, repo, modheads, opts['update'], checkout)
2108 return postincoming(ui, repo, modheads, opts['update'], checkout)
2108
2109
2109 def push(ui, repo, dest=None, **opts):
2110 def push(ui, repo, dest=None, **opts):
2110 """push changes to the specified destination
2111 """push changes to the specified destination
2111
2112
2112 Push changes from the local repository to the given destination.
2113 Push changes from the local repository to the given destination.
2113
2114
2114 This is the symmetrical operation for pull. It helps to move
2115 This is the symmetrical operation for pull. It helps to move
2115 changes from the current repository to a different one. If the
2116 changes from the current repository to a different one. If the
2116 destination is local this is identical to a pull in that directory
2117 destination is local this is identical to a pull in that directory
2117 from the current one.
2118 from the current one.
2118
2119
2119 By default, push will refuse to run if it detects the result would
2120 By default, push will refuse to run if it detects the result would
2120 increase the number of remote heads. This generally indicates the
2121 increase the number of remote heads. This generally indicates the
2121 the client has forgotten to sync and merge before pushing.
2122 the client has forgotten to sync and merge before pushing.
2122
2123
2123 Valid URLs are of the form:
2124 Valid URLs are of the form:
2124
2125
2125 local/filesystem/path (or file://local/filesystem/path)
2126 local/filesystem/path (or file://local/filesystem/path)
2126 ssh://[user@]host[:port]/[path]
2127 ssh://[user@]host[:port]/[path]
2127 http://[user@]host[:port]/[path]
2128 http://[user@]host[:port]/[path]
2128 https://[user@]host[:port]/[path]
2129 https://[user@]host[:port]/[path]
2129
2130
2130 An optional identifier after # indicates a particular branch, tag,
2131 An optional identifier after # indicates a particular branch, tag,
2131 or changeset to push.
2132 or changeset to push.
2132
2133
2133 Look at the help text for the pull command for important details
2134 Look at the help text for the pull command for important details
2134 about ssh:// URLs.
2135 about ssh:// URLs.
2135
2136
2136 Pushing to http:// and https:// URLs is only possible, if this
2137 Pushing to http:// and https:// URLs is only possible, if this
2137 feature is explicitly enabled on the remote Mercurial server.
2138 feature is explicitly enabled on the remote Mercurial server.
2138 """
2139 """
2139 dest, revs, checkout = hg.parseurl(
2140 dest, revs, checkout = hg.parseurl(
2140 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2141 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2141 cmdutil.setremoteconfig(ui, opts)
2142 cmdutil.setremoteconfig(ui, opts)
2142
2143
2143 other = hg.repository(ui, dest)
2144 other = hg.repository(ui, dest)
2144 ui.status('pushing to %s\n' % (dest))
2145 ui.status('pushing to %s\n' % (dest))
2145 if revs:
2146 if revs:
2146 revs = [repo.lookup(rev) for rev in revs]
2147 revs = [repo.lookup(rev) for rev in revs]
2147 r = repo.push(other, opts['force'], revs=revs)
2148 r = repo.push(other, opts['force'], revs=revs)
2148 return r == 0
2149 return r == 0
2149
2150
2150 def rawcommit(ui, repo, *pats, **opts):
2151 def rawcommit(ui, repo, *pats, **opts):
2151 """raw commit interface (DEPRECATED)
2152 """raw commit interface (DEPRECATED)
2152
2153
2153 (DEPRECATED)
2154 (DEPRECATED)
2154 Lowlevel commit, for use in helper scripts.
2155 Lowlevel commit, for use in helper scripts.
2155
2156
2156 This command is not intended to be used by normal users, as it is
2157 This command is not intended to be used by normal users, as it is
2157 primarily useful for importing from other SCMs.
2158 primarily useful for importing from other SCMs.
2158
2159
2159 This command is now deprecated and will be removed in a future
2160 This command is now deprecated and will be removed in a future
2160 release, please use debugsetparents and commit instead.
2161 release, please use debugsetparents and commit instead.
2161 """
2162 """
2162
2163
2163 ui.warn(_("(the rawcommit command is deprecated)\n"))
2164 ui.warn(_("(the rawcommit command is deprecated)\n"))
2164
2165
2165 message = cmdutil.logmessage(opts)
2166 message = cmdutil.logmessage(opts)
2166
2167
2167 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
2168 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
2168 if opts['files']:
2169 if opts['files']:
2169 files += open(opts['files']).read().splitlines()
2170 files += open(opts['files']).read().splitlines()
2170
2171
2171 parents = [repo.lookup(p) for p in opts['parent']]
2172 parents = [repo.lookup(p) for p in opts['parent']]
2172
2173
2173 try:
2174 try:
2174 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2175 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2175 except ValueError, inst:
2176 except ValueError, inst:
2176 raise util.Abort(str(inst))
2177 raise util.Abort(str(inst))
2177
2178
2178 def recover(ui, repo):
2179 def recover(ui, repo):
2179 """roll back an interrupted transaction
2180 """roll back an interrupted transaction
2180
2181
2181 Recover from an interrupted commit or pull.
2182 Recover from an interrupted commit or pull.
2182
2183
2183 This command tries to fix the repository status after an interrupted
2184 This command tries to fix the repository status after an interrupted
2184 operation. It should only be necessary when Mercurial suggests it.
2185 operation. It should only be necessary when Mercurial suggests it.
2185 """
2186 """
2186 if repo.recover():
2187 if repo.recover():
2187 return hg.verify(repo)
2188 return hg.verify(repo)
2188 return 1
2189 return 1
2189
2190
2190 def remove(ui, repo, *pats, **opts):
2191 def remove(ui, repo, *pats, **opts):
2191 """remove the specified files on the next commit
2192 """remove the specified files on the next commit
2192
2193
2193 Schedule the indicated files for removal from the repository.
2194 Schedule the indicated files for removal from the repository.
2194
2195
2195 This only removes files from the current branch, not from the
2196 This only removes files from the current branch, not from the
2196 entire project history. If the files still exist in the working
2197 entire project history. If the files still exist in the working
2197 directory, they will be deleted from it. If invoked with --after,
2198 directory, they will be deleted from it. If invoked with --after,
2198 files are marked as removed, but not actually unlinked unless --force
2199 files are marked as removed, but not actually unlinked unless --force
2199 is also given. Without exact file names, --after will only mark
2200 is also given. Without exact file names, --after will only mark
2200 files as removed if they are no longer in the working directory.
2201 files as removed if they are no longer in the working directory.
2201
2202
2202 This command schedules the files to be removed at the next commit.
2203 This command schedules the files to be removed at the next commit.
2203 To undo a remove before that, see hg revert.
2204 To undo a remove before that, see hg revert.
2204
2205
2205 Modified files and added files are not removed by default. To
2206 Modified files and added files are not removed by default. To
2206 remove them, use the -f/--force option.
2207 remove them, use the -f/--force option.
2207 """
2208 """
2208 if not opts['after'] and not pats:
2209 if not opts['after'] and not pats:
2209 raise util.Abort(_('no files specified'))
2210 raise util.Abort(_('no files specified'))
2210 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2211 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2211 exact = dict.fromkeys(files)
2212 exact = dict.fromkeys(files)
2212 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2213 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2213 modified, added, removed, deleted, unknown = mardu
2214 modified, added, removed, deleted, unknown = mardu
2214 remove, forget = [], []
2215 remove, forget = [], []
2215 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2216 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2216 reason = None
2217 reason = None
2217 if abs in modified and not opts['force']:
2218 if abs in modified and not opts['force']:
2218 reason = _('is modified (use -f to force removal)')
2219 reason = _('is modified (use -f to force removal)')
2219 elif abs in added:
2220 elif abs in added:
2220 if opts['force']:
2221 if opts['force']:
2221 forget.append(abs)
2222 forget.append(abs)
2222 continue
2223 continue
2223 reason = _('has been marked for add (use -f to force removal)')
2224 reason = _('has been marked for add (use -f to force removal)')
2224 elif abs not in repo.dirstate:
2225 elif abs not in repo.dirstate:
2225 reason = _('is not managed')
2226 reason = _('is not managed')
2226 elif opts['after'] and not exact and abs not in deleted:
2227 elif opts['after'] and not exact and abs not in deleted:
2227 continue
2228 continue
2228 elif abs in removed:
2229 elif abs in removed:
2229 continue
2230 continue
2230 if reason:
2231 if reason:
2231 if exact:
2232 if exact:
2232 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2233 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2233 else:
2234 else:
2234 if ui.verbose or not exact:
2235 if ui.verbose or not exact:
2235 ui.status(_('removing %s\n') % rel)
2236 ui.status(_('removing %s\n') % rel)
2236 remove.append(abs)
2237 remove.append(abs)
2237 repo.forget(forget)
2238 repo.forget(forget)
2238 repo.remove(remove, unlink=opts['force'] or not opts['after'])
2239 repo.remove(remove, unlink=opts['force'] or not opts['after'])
2239
2240
2240 def rename(ui, repo, *pats, **opts):
2241 def rename(ui, repo, *pats, **opts):
2241 """rename files; equivalent of copy + remove
2242 """rename files; equivalent of copy + remove
2242
2243
2243 Mark dest as copies of sources; mark sources for deletion. If
2244 Mark dest as copies of sources; mark sources for deletion. If
2244 dest is a directory, copies are put in that directory. If dest is
2245 dest is a directory, copies are put in that directory. If dest is
2245 a file, there can only be one source.
2246 a file, there can only be one source.
2246
2247
2247 By default, this command copies the contents of files as they
2248 By default, this command copies the contents of files as they
2248 stand in the working directory. If invoked with --after, the
2249 stand in the working directory. If invoked with --after, the
2249 operation is recorded, but no copying is performed.
2250 operation is recorded, but no copying is performed.
2250
2251
2251 This command takes effect in the next commit. To undo a rename
2252 This command takes effect in the next commit. To undo a rename
2252 before that, see hg revert.
2253 before that, see hg revert.
2253 """
2254 """
2254 wlock = repo.wlock(False)
2255 wlock = repo.wlock(False)
2255 try:
2256 try:
2256 errs, copied = docopy(ui, repo, pats, opts)
2257 errs, copied = docopy(ui, repo, pats, opts)
2257 names = []
2258 names = []
2258 for abs, rel, exact in copied:
2259 for abs, rel, exact in copied:
2259 if ui.verbose or not exact:
2260 if ui.verbose or not exact:
2260 ui.status(_('removing %s\n') % rel)
2261 ui.status(_('removing %s\n') % rel)
2261 names.append(abs)
2262 names.append(abs)
2262 if not opts.get('dry_run'):
2263 if not opts.get('dry_run'):
2263 repo.remove(names, True)
2264 repo.remove(names, True)
2264 return errs
2265 return errs
2265 finally:
2266 finally:
2266 del wlock
2267 del wlock
2267
2268
2268 def revert(ui, repo, *pats, **opts):
2269 def revert(ui, repo, *pats, **opts):
2269 """revert files or dirs to their states as of some revision
2270 """revert files or dirs to their states as of some revision
2270
2271
2271 With no revision specified, revert the named files or directories
2272 With no revision specified, revert the named files or directories
2272 to the contents they had in the parent of the working directory.
2273 to the contents they had in the parent of the working directory.
2273 This restores the contents of the affected files to an unmodified
2274 This restores the contents of the affected files to an unmodified
2274 state and unschedules adds, removes, copies, and renames. If the
2275 state and unschedules adds, removes, copies, and renames. If the
2275 working directory has two parents, you must explicitly specify the
2276 working directory has two parents, you must explicitly specify the
2276 revision to revert to.
2277 revision to revert to.
2277
2278
2278 Modified files are saved with a .orig suffix before reverting.
2279 Modified files are saved with a .orig suffix before reverting.
2279 To disable these backups, use --no-backup.
2280 To disable these backups, use --no-backup.
2280
2281
2281 Using the -r option, revert the given files or directories to their
2282 Using the -r option, revert the given files or directories to their
2282 contents as of a specific revision. This can be helpful to "roll
2283 contents as of a specific revision. This can be helpful to "roll
2283 back" some or all of a change that should not have been committed.
2284 back" some or all of a change that should not have been committed.
2284
2285
2285 Revert modifies the working directory. It does not commit any
2286 Revert modifies the working directory. It does not commit any
2286 changes, or change the parent of the working directory. If you
2287 changes, or change the parent of the working directory. If you
2287 revert to a revision other than the parent of the working
2288 revert to a revision other than the parent of the working
2288 directory, the reverted files will thus appear modified
2289 directory, the reverted files will thus appear modified
2289 afterwards.
2290 afterwards.
2290
2291
2291 If a file has been deleted, it is restored. If the executable
2292 If a file has been deleted, it is restored. If the executable
2292 mode of a file was changed, it is reset.
2293 mode of a file was changed, it is reset.
2293
2294
2294 If names are given, all files matching the names are reverted.
2295 If names are given, all files matching the names are reverted.
2295
2296
2296 If no arguments are given, no files are reverted.
2297 If no arguments are given, no files are reverted.
2297 """
2298 """
2298
2299
2299 if opts["date"]:
2300 if opts["date"]:
2300 if opts["rev"]:
2301 if opts["rev"]:
2301 raise util.Abort(_("you can't specify a revision and a date"))
2302 raise util.Abort(_("you can't specify a revision and a date"))
2302 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2303 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2303
2304
2304 if not pats and not opts['all']:
2305 if not pats and not opts['all']:
2305 raise util.Abort(_('no files or directories specified; '
2306 raise util.Abort(_('no files or directories specified; '
2306 'use --all to revert the whole repo'))
2307 'use --all to revert the whole repo'))
2307
2308
2308 parent, p2 = repo.dirstate.parents()
2309 parent, p2 = repo.dirstate.parents()
2309 if not opts['rev'] and p2 != nullid:
2310 if not opts['rev'] and p2 != nullid:
2310 raise util.Abort(_('uncommitted merge - please provide a '
2311 raise util.Abort(_('uncommitted merge - please provide a '
2311 'specific revision'))
2312 'specific revision'))
2312 ctx = repo.changectx(opts['rev'])
2313 ctx = repo.changectx(opts['rev'])
2313 node = ctx.node()
2314 node = ctx.node()
2314 mf = ctx.manifest()
2315 mf = ctx.manifest()
2315 if node == parent:
2316 if node == parent:
2316 pmf = mf
2317 pmf = mf
2317 else:
2318 else:
2318 pmf = None
2319 pmf = None
2319
2320
2320 # need all matching names in dirstate and manifest of target rev,
2321 # need all matching names in dirstate and manifest of target rev,
2321 # so have to walk both. do not print errors if files exist in one
2322 # so have to walk both. do not print errors if files exist in one
2322 # but not other.
2323 # but not other.
2323
2324
2324 names = {}
2325 names = {}
2325 target_only = {}
2326 target_only = {}
2326
2327
2327 wlock = repo.wlock()
2328 wlock = repo.wlock()
2328 try:
2329 try:
2329 # walk dirstate.
2330 # walk dirstate.
2330 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2331 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2331 badmatch=mf.has_key):
2332 badmatch=mf.has_key):
2332 names[abs] = (rel, exact)
2333 names[abs] = (rel, exact)
2333 if src == 'b':
2334 if src == 'b':
2334 target_only[abs] = True
2335 target_only[abs] = True
2335
2336
2336 # walk target manifest.
2337 # walk target manifest.
2337
2338
2338 def badmatch(path):
2339 def badmatch(path):
2339 if path in names:
2340 if path in names:
2340 return True
2341 return True
2341 path_ = path + '/'
2342 path_ = path + '/'
2342 for f in names:
2343 for f in names:
2343 if f.startswith(path_):
2344 if f.startswith(path_):
2344 return True
2345 return True
2345 return False
2346 return False
2346
2347
2347 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2348 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2348 badmatch=badmatch):
2349 badmatch=badmatch):
2349 if abs in names or src == 'b':
2350 if abs in names or src == 'b':
2350 continue
2351 continue
2351 names[abs] = (rel, exact)
2352 names[abs] = (rel, exact)
2352 target_only[abs] = True
2353 target_only[abs] = True
2353
2354
2354 changes = repo.status(match=names.has_key)[:5]
2355 changes = repo.status(match=names.has_key)[:5]
2355 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2356 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2356
2357
2357 # if f is a rename, also revert the source
2358 # if f is a rename, also revert the source
2358 cwd = repo.getcwd()
2359 cwd = repo.getcwd()
2359 for f in added:
2360 for f in added:
2360 src = repo.dirstate.copied(f)
2361 src = repo.dirstate.copied(f)
2361 if src and src not in names and repo.dirstate[src] == 'r':
2362 if src and src not in names and repo.dirstate[src] == 'r':
2362 removed[src] = None
2363 removed[src] = None
2363 names[src] = (repo.pathto(src, cwd), True)
2364 names[src] = (repo.pathto(src, cwd), True)
2364
2365
2365 revert = ([], _('reverting %s\n'))
2366 revert = ([], _('reverting %s\n'))
2366 add = ([], _('adding %s\n'))
2367 add = ([], _('adding %s\n'))
2367 remove = ([], _('removing %s\n'))
2368 remove = ([], _('removing %s\n'))
2368 forget = ([], _('forgetting %s\n'))
2369 forget = ([], _('forgetting %s\n'))
2369 undelete = ([], _('undeleting %s\n'))
2370 undelete = ([], _('undeleting %s\n'))
2370 update = {}
2371 update = {}
2371
2372
2372 disptable = (
2373 disptable = (
2373 # dispatch table:
2374 # dispatch table:
2374 # file state
2375 # file state
2375 # action if in target manifest
2376 # action if in target manifest
2376 # action if not in target manifest
2377 # action if not in target manifest
2377 # make backup if in target manifest
2378 # make backup if in target manifest
2378 # make backup if not in target manifest
2379 # make backup if not in target manifest
2379 (modified, revert, remove, True, True),
2380 (modified, revert, remove, True, True),
2380 (added, revert, forget, True, False),
2381 (added, revert, forget, True, False),
2381 (removed, undelete, None, False, False),
2382 (removed, undelete, None, False, False),
2382 (deleted, revert, remove, False, False),
2383 (deleted, revert, remove, False, False),
2383 (unknown, add, None, True, False),
2384 (unknown, add, None, True, False),
2384 (target_only, add, None, False, False),
2385 (target_only, add, None, False, False),
2385 )
2386 )
2386
2387
2387 entries = names.items()
2388 entries = names.items()
2388 entries.sort()
2389 entries.sort()
2389
2390
2390 for abs, (rel, exact) in entries:
2391 for abs, (rel, exact) in entries:
2391 mfentry = mf.get(abs)
2392 mfentry = mf.get(abs)
2392 target = repo.wjoin(abs)
2393 target = repo.wjoin(abs)
2393 def handle(xlist, dobackup):
2394 def handle(xlist, dobackup):
2394 xlist[0].append(abs)
2395 xlist[0].append(abs)
2395 update[abs] = 1
2396 update[abs] = 1
2396 if dobackup and not opts['no_backup'] and util.lexists(target):
2397 if dobackup and not opts['no_backup'] and util.lexists(target):
2397 bakname = "%s.orig" % rel
2398 bakname = "%s.orig" % rel
2398 ui.note(_('saving current version of %s as %s\n') %
2399 ui.note(_('saving current version of %s as %s\n') %
2399 (rel, bakname))
2400 (rel, bakname))
2400 if not opts.get('dry_run'):
2401 if not opts.get('dry_run'):
2401 util.copyfile(target, bakname)
2402 util.copyfile(target, bakname)
2402 if ui.verbose or not exact:
2403 if ui.verbose or not exact:
2403 ui.status(xlist[1] % rel)
2404 ui.status(xlist[1] % rel)
2404 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2405 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2405 if abs not in table: continue
2406 if abs not in table: continue
2406 # file has changed in dirstate
2407 # file has changed in dirstate
2407 if mfentry:
2408 if mfentry:
2408 handle(hitlist, backuphit)
2409 handle(hitlist, backuphit)
2409 elif misslist is not None:
2410 elif misslist is not None:
2410 handle(misslist, backupmiss)
2411 handle(misslist, backupmiss)
2411 else:
2412 else:
2412 if exact: ui.warn(_('file not managed: %s\n') % rel)
2413 if exact: ui.warn(_('file not managed: %s\n') % rel)
2413 break
2414 break
2414 else:
2415 else:
2415 # file has not changed in dirstate
2416 # file has not changed in dirstate
2416 if node == parent:
2417 if node == parent:
2417 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2418 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2418 continue
2419 continue
2419 if pmf is None:
2420 if pmf is None:
2420 # only need parent manifest in this unlikely case,
2421 # only need parent manifest in this unlikely case,
2421 # so do not read by default
2422 # so do not read by default
2422 pmf = repo.changectx(parent).manifest()
2423 pmf = repo.changectx(parent).manifest()
2423 if abs in pmf:
2424 if abs in pmf:
2424 if mfentry:
2425 if mfentry:
2425 # if version of file is same in parent and target
2426 # if version of file is same in parent and target
2426 # manifests, do nothing
2427 # manifests, do nothing
2427 if pmf[abs] != mfentry:
2428 if pmf[abs] != mfentry:
2428 handle(revert, False)
2429 handle(revert, False)
2429 else:
2430 else:
2430 handle(remove, False)
2431 handle(remove, False)
2431
2432
2432 if not opts.get('dry_run'):
2433 if not opts.get('dry_run'):
2433 for f in forget[0]:
2434 for f in forget[0]:
2434 repo.dirstate.forget(f)
2435 repo.dirstate.forget(f)
2435 r = hg.revert(repo, node, update.has_key)
2436 r = hg.revert(repo, node, update.has_key)
2436 for f in add[0]:
2437 for f in add[0]:
2437 repo.dirstate.add(f)
2438 repo.dirstate.add(f)
2438 for f in undelete[0]:
2439 for f in undelete[0]:
2439 repo.dirstate.normal(f)
2440 repo.dirstate.normal(f)
2440 for f in remove[0]:
2441 for f in remove[0]:
2441 repo.dirstate.remove(f)
2442 repo.dirstate.remove(f)
2442 return r
2443 return r
2443 finally:
2444 finally:
2444 del wlock
2445 del wlock
2445
2446
2446 def rollback(ui, repo):
2447 def rollback(ui, repo):
2447 """roll back the last transaction in this repository
2448 """roll back the last transaction in this repository
2448
2449
2449 Roll back the last transaction in this repository, restoring the
2450 Roll back the last transaction in this repository, restoring the
2450 project to its state prior to the transaction.
2451 project to its state prior to the transaction.
2451
2452
2452 Transactions are used to encapsulate the effects of all commands
2453 Transactions are used to encapsulate the effects of all commands
2453 that create new changesets or propagate existing changesets into a
2454 that create new changesets or propagate existing changesets into a
2454 repository. For example, the following commands are transactional,
2455 repository. For example, the following commands are transactional,
2455 and their effects can be rolled back:
2456 and their effects can be rolled back:
2456
2457
2457 commit
2458 commit
2458 import
2459 import
2459 pull
2460 pull
2460 push (with this repository as destination)
2461 push (with this repository as destination)
2461 unbundle
2462 unbundle
2462
2463
2463 This command should be used with care. There is only one level of
2464 This command should be used with care. There is only one level of
2464 rollback, and there is no way to undo a rollback. It will also
2465 rollback, and there is no way to undo a rollback. It will also
2465 restore the dirstate at the time of the last transaction, which
2466 restore the dirstate at the time of the last transaction, which
2466 may lose subsequent dirstate changes.
2467 may lose subsequent dirstate changes.
2467
2468
2468 This command is not intended for use on public repositories. Once
2469 This command is not intended for use on public repositories. Once
2469 changes are visible for pull by other users, rolling a transaction
2470 changes are visible for pull by other users, rolling a transaction
2470 back locally is ineffective (someone else may already have pulled
2471 back locally is ineffective (someone else may already have pulled
2471 the changes). Furthermore, a race is possible with readers of the
2472 the changes). Furthermore, a race is possible with readers of the
2472 repository; for example an in-progress pull from the repository
2473 repository; for example an in-progress pull from the repository
2473 may fail if a rollback is performed.
2474 may fail if a rollback is performed.
2474 """
2475 """
2475 repo.rollback()
2476 repo.rollback()
2476
2477
2477 def root(ui, repo):
2478 def root(ui, repo):
2478 """print the root (top) of the current working dir
2479 """print the root (top) of the current working dir
2479
2480
2480 Print the root directory of the current repository.
2481 Print the root directory of the current repository.
2481 """
2482 """
2482 ui.write(repo.root + "\n")
2483 ui.write(repo.root + "\n")
2483
2484
2484 def serve(ui, repo, **opts):
2485 def serve(ui, repo, **opts):
2485 """export the repository via HTTP
2486 """export the repository via HTTP
2486
2487
2487 Start a local HTTP repository browser and pull server.
2488 Start a local HTTP repository browser and pull server.
2488
2489
2489 By default, the server logs accesses to stdout and errors to
2490 By default, the server logs accesses to stdout and errors to
2490 stderr. Use the "-A" and "-E" options to log to files.
2491 stderr. Use the "-A" and "-E" options to log to files.
2491 """
2492 """
2492
2493
2493 if opts["stdio"]:
2494 if opts["stdio"]:
2494 if repo is None:
2495 if repo is None:
2495 raise hg.RepoError(_("There is no Mercurial repository here"
2496 raise hg.RepoError(_("There is no Mercurial repository here"
2496 " (.hg not found)"))
2497 " (.hg not found)"))
2497 s = sshserver.sshserver(ui, repo)
2498 s = sshserver.sshserver(ui, repo)
2498 s.serve_forever()
2499 s.serve_forever()
2499
2500
2500 parentui = ui.parentui or ui
2501 parentui = ui.parentui or ui
2501 optlist = ("name templates style address port ipv6"
2502 optlist = ("name templates style address port ipv6"
2502 " accesslog errorlog webdir_conf certificate")
2503 " accesslog errorlog webdir_conf certificate")
2503 for o in optlist.split():
2504 for o in optlist.split():
2504 if opts[o]:
2505 if opts[o]:
2505 parentui.setconfig("web", o, str(opts[o]))
2506 parentui.setconfig("web", o, str(opts[o]))
2506 if (repo is not None) and (repo.ui != parentui):
2507 if (repo is not None) and (repo.ui != parentui):
2507 repo.ui.setconfig("web", o, str(opts[o]))
2508 repo.ui.setconfig("web", o, str(opts[o]))
2508
2509
2509 if repo is None and not ui.config("web", "webdir_conf"):
2510 if repo is None and not ui.config("web", "webdir_conf"):
2510 raise hg.RepoError(_("There is no Mercurial repository here"
2511 raise hg.RepoError(_("There is no Mercurial repository here"
2511 " (.hg not found)"))
2512 " (.hg not found)"))
2512
2513
2513 class service:
2514 class service:
2514 def init(self):
2515 def init(self):
2515 util.set_signal_handler()
2516 util.set_signal_handler()
2516 try:
2517 try:
2517 self.httpd = hgweb.server.create_server(parentui, repo)
2518 self.httpd = hgweb.server.create_server(parentui, repo)
2518 except socket.error, inst:
2519 except socket.error, inst:
2519 raise util.Abort(_('cannot start server: ') + inst.args[1])
2520 raise util.Abort(_('cannot start server: ') + inst.args[1])
2520
2521
2521 if not ui.verbose: return
2522 if not ui.verbose: return
2522
2523
2523 if self.httpd.port != 80:
2524 if self.httpd.port != 80:
2524 ui.status(_('listening at http://%s:%d/\n') %
2525 ui.status(_('listening at http://%s:%d/\n') %
2525 (self.httpd.addr, self.httpd.port))
2526 (self.httpd.addr, self.httpd.port))
2526 else:
2527 else:
2527 ui.status(_('listening at http://%s/\n') % self.httpd.addr)
2528 ui.status(_('listening at http://%s/\n') % self.httpd.addr)
2528
2529
2529 def run(self):
2530 def run(self):
2530 self.httpd.serve_forever()
2531 self.httpd.serve_forever()
2531
2532
2532 service = service()
2533 service = service()
2533
2534
2534 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2535 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2535
2536
2536 def status(ui, repo, *pats, **opts):
2537 def status(ui, repo, *pats, **opts):
2537 """show changed files in the working directory
2538 """show changed files in the working directory
2538
2539
2539 Show status of files in the repository. If names are given, only
2540 Show status of files in the repository. If names are given, only
2540 files that match are shown. Files that are clean or ignored, are
2541 files that match are shown. Files that are clean or ignored, are
2541 not listed unless -c (clean), -i (ignored) or -A is given.
2542 not listed unless -c (clean), -i (ignored) or -A is given.
2542
2543
2543 NOTE: status may appear to disagree with diff if permissions have
2544 NOTE: status may appear to disagree with diff if permissions have
2544 changed or a merge has occurred. The standard diff format does not
2545 changed or a merge has occurred. The standard diff format does not
2545 report permission changes and diff only reports changes relative
2546 report permission changes and diff only reports changes relative
2546 to one merge parent.
2547 to one merge parent.
2547
2548
2548 If one revision is given, it is used as the base revision.
2549 If one revision is given, it is used as the base revision.
2549 If two revisions are given, the difference between them is shown.
2550 If two revisions are given, the difference between them is shown.
2550
2551
2551 The codes used to show the status of files are:
2552 The codes used to show the status of files are:
2552 M = modified
2553 M = modified
2553 A = added
2554 A = added
2554 R = removed
2555 R = removed
2555 C = clean
2556 C = clean
2556 ! = deleted, but still tracked
2557 ! = deleted, but still tracked
2557 ? = not tracked
2558 ? = not tracked
2558 I = ignored (not shown by default)
2559 I = ignored (not shown by default)
2559 = the previous added file was copied from here
2560 = the previous added file was copied from here
2560 """
2561 """
2561
2562
2562 all = opts['all']
2563 all = opts['all']
2563 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2564 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2564
2565
2565 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2566 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2566 cwd = (pats and repo.getcwd()) or ''
2567 cwd = (pats and repo.getcwd()) or ''
2567 modified, added, removed, deleted, unknown, ignored, clean = [
2568 modified, added, removed, deleted, unknown, ignored, clean = [
2568 n for n in repo.status(node1=node1, node2=node2, files=files,
2569 n for n in repo.status(node1=node1, node2=node2, files=files,
2569 match=matchfn,
2570 match=matchfn,
2570 list_ignored=all or opts['ignored'],
2571 list_ignored=all or opts['ignored'],
2571 list_clean=all or opts['clean'])]
2572 list_clean=all or opts['clean'])]
2572
2573
2573 changetypes = (('modified', 'M', modified),
2574 changetypes = (('modified', 'M', modified),
2574 ('added', 'A', added),
2575 ('added', 'A', added),
2575 ('removed', 'R', removed),
2576 ('removed', 'R', removed),
2576 ('deleted', '!', deleted),
2577 ('deleted', '!', deleted),
2577 ('unknown', '?', unknown),
2578 ('unknown', '?', unknown),
2578 ('ignored', 'I', ignored))
2579 ('ignored', 'I', ignored))
2579
2580
2580 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2581 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2581
2582
2582 end = opts['print0'] and '\0' or '\n'
2583 end = opts['print0'] and '\0' or '\n'
2583
2584
2584 for opt, char, changes in ([ct for ct in explicit_changetypes
2585 for opt, char, changes in ([ct for ct in explicit_changetypes
2585 if all or opts[ct[0]]]
2586 if all or opts[ct[0]]]
2586 or changetypes):
2587 or changetypes):
2587 if opts['no_status']:
2588 if opts['no_status']:
2588 format = "%%s%s" % end
2589 format = "%%s%s" % end
2589 else:
2590 else:
2590 format = "%s %%s%s" % (char, end)
2591 format = "%s %%s%s" % (char, end)
2591
2592
2592 for f in changes:
2593 for f in changes:
2593 ui.write(format % repo.pathto(f, cwd))
2594 ui.write(format % repo.pathto(f, cwd))
2594 if ((all or opts.get('copies')) and not opts.get('no_status')):
2595 if ((all or opts.get('copies')) and not opts.get('no_status')):
2595 copied = repo.dirstate.copied(f)
2596 copied = repo.dirstate.copied(f)
2596 if copied:
2597 if copied:
2597 ui.write(' %s%s' % (repo.pathto(copied, cwd), end))
2598 ui.write(' %s%s' % (repo.pathto(copied, cwd), end))
2598
2599
2599 def tag(ui, repo, name, rev_=None, **opts):
2600 def tag(ui, repo, name, rev_=None, **opts):
2600 """add a tag for the current or given revision
2601 """add a tag for the current or given revision
2601
2602
2602 Name a particular revision using <name>.
2603 Name a particular revision using <name>.
2603
2604
2604 Tags are used to name particular revisions of the repository and are
2605 Tags are used to name particular revisions of the repository and are
2605 very useful to compare different revision, to go back to significant
2606 very useful to compare different revision, to go back to significant
2606 earlier versions or to mark branch points as releases, etc.
2607 earlier versions or to mark branch points as releases, etc.
2607
2608
2608 If no revision is given, the parent of the working directory is used,
2609 If no revision is given, the parent of the working directory is used,
2609 or tip if no revision is checked out.
2610 or tip if no revision is checked out.
2610
2611
2611 To facilitate version control, distribution, and merging of tags,
2612 To facilitate version control, distribution, and merging of tags,
2612 they are stored as a file named ".hgtags" which is managed
2613 they are stored as a file named ".hgtags" which is managed
2613 similarly to other project files and can be hand-edited if
2614 similarly to other project files and can be hand-edited if
2614 necessary. The file '.hg/localtags' is used for local tags (not
2615 necessary. The file '.hg/localtags' is used for local tags (not
2615 shared among repositories).
2616 shared among repositories).
2616 """
2617 """
2617 if name in ['tip', '.', 'null']:
2618 if name in ['tip', '.', 'null']:
2618 raise util.Abort(_("the name '%s' is reserved") % name)
2619 raise util.Abort(_("the name '%s' is reserved") % name)
2619 if rev_ is not None:
2620 if rev_ is not None:
2620 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2621 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2621 "please use 'hg tag [-r REV] NAME' instead\n"))
2622 "please use 'hg tag [-r REV] NAME' instead\n"))
2622 if opts['rev']:
2623 if opts['rev']:
2623 raise util.Abort(_("use only one form to specify the revision"))
2624 raise util.Abort(_("use only one form to specify the revision"))
2624 if opts['rev'] and opts['remove']:
2625 if opts['rev'] and opts['remove']:
2625 raise util.Abort(_("--rev and --remove are incompatible"))
2626 raise util.Abort(_("--rev and --remove are incompatible"))
2626 if opts['rev']:
2627 if opts['rev']:
2627 rev_ = opts['rev']
2628 rev_ = opts['rev']
2628 message = opts['message']
2629 message = opts['message']
2629 if opts['remove']:
2630 if opts['remove']:
2630 if not name in repo.tags():
2631 if not name in repo.tags():
2631 raise util.Abort(_('tag %s does not exist') % name)
2632 raise util.Abort(_('tag %s does not exist') % name)
2632 rev_ = nullid
2633 rev_ = nullid
2633 if not message:
2634 if not message:
2634 message = _('Removed tag %s') % name
2635 message = _('Removed tag %s') % name
2635 elif name in repo.tags() and not opts['force']:
2636 elif name in repo.tags() and not opts['force']:
2636 raise util.Abort(_('a tag named %s already exists (use -f to force)')
2637 raise util.Abort(_('a tag named %s already exists (use -f to force)')
2637 % name)
2638 % name)
2638 if not rev_ and repo.dirstate.parents()[1] != nullid:
2639 if not rev_ and repo.dirstate.parents()[1] != nullid:
2639 raise util.Abort(_('uncommitted merge - please provide a '
2640 raise util.Abort(_('uncommitted merge - please provide a '
2640 'specific revision'))
2641 'specific revision'))
2641 r = repo.changectx(rev_).node()
2642 r = repo.changectx(rev_).node()
2642
2643
2643 if not message:
2644 if not message:
2644 message = _('Added tag %s for changeset %s') % (name, short(r))
2645 message = _('Added tag %s for changeset %s') % (name, short(r))
2645
2646
2646 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2647 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2647
2648
2648 def tags(ui, repo):
2649 def tags(ui, repo):
2649 """list repository tags
2650 """list repository tags
2650
2651
2651 List the repository tags.
2652 List the repository tags.
2652
2653
2653 This lists both regular and local tags.
2654 This lists both regular and local tags.
2654 """
2655 """
2655
2656
2656 l = repo.tagslist()
2657 l = repo.tagslist()
2657 l.reverse()
2658 l.reverse()
2658 hexfunc = ui.debugflag and hex or short
2659 hexfunc = ui.debugflag and hex or short
2659 for t, n in l:
2660 for t, n in l:
2660 try:
2661 try:
2661 hn = hexfunc(n)
2662 hn = hexfunc(n)
2662 r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
2663 r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
2663 except revlog.LookupError:
2664 except revlog.LookupError:
2664 r = " ?:%s" % hn
2665 r = " ?:%s" % hn
2665 if ui.quiet:
2666 if ui.quiet:
2666 ui.write("%s\n" % t)
2667 ui.write("%s\n" % t)
2667 else:
2668 else:
2668 spaces = " " * (30 - util.locallen(t))
2669 spaces = " " * (30 - util.locallen(t))
2669 ui.write("%s%s %s\n" % (t, spaces, r))
2670 ui.write("%s%s %s\n" % (t, spaces, r))
2670
2671
2671 def tip(ui, repo, **opts):
2672 def tip(ui, repo, **opts):
2672 """show the tip revision
2673 """show the tip revision
2673
2674
2674 Show the tip revision.
2675 Show the tip revision.
2675 """
2676 """
2676 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2677 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2677
2678
2678 def unbundle(ui, repo, fname1, *fnames, **opts):
2679 def unbundle(ui, repo, fname1, *fnames, **opts):
2679 """apply one or more changegroup files
2680 """apply one or more changegroup files
2680
2681
2681 Apply one or more compressed changegroup files generated by the
2682 Apply one or more compressed changegroup files generated by the
2682 bundle command.
2683 bundle command.
2683 """
2684 """
2684 fnames = (fname1,) + fnames
2685 fnames = (fname1,) + fnames
2685 for fname in fnames:
2686 for fname in fnames:
2686 if os.path.exists(fname):
2687 if os.path.exists(fname):
2687 f = open(fname, "rb")
2688 f = open(fname, "rb")
2688 else:
2689 else:
2689 f = urllib.urlopen(fname)
2690 f = urllib.urlopen(fname)
2690 gen = changegroup.readbundle(f, fname)
2691 gen = changegroup.readbundle(f, fname)
2691 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2692 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2692
2693
2693 return postincoming(ui, repo, modheads, opts['update'], None)
2694 return postincoming(ui, repo, modheads, opts['update'], None)
2694
2695
2695 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2696 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2696 """update working directory
2697 """update working directory
2697
2698
2698 Update the working directory to the specified revision, or the
2699 Update the working directory to the specified revision, or the
2699 tip of the current branch if none is specified.
2700 tip of the current branch if none is specified.
2700
2701
2701 If there are no outstanding changes in the working directory and
2702 If there are no outstanding changes in the working directory and
2702 there is a linear relationship between the current version and the
2703 there is a linear relationship between the current version and the
2703 requested version, the result is the requested version.
2704 requested version, the result is the requested version.
2704
2705
2705 To merge the working directory with another revision, use the
2706 To merge the working directory with another revision, use the
2706 merge command.
2707 merge command.
2707
2708
2708 By default, update will refuse to run if doing so would require
2709 By default, update will refuse to run if doing so would require
2709 discarding local changes.
2710 discarding local changes.
2710 """
2711 """
2711 if rev and node:
2712 if rev and node:
2712 raise util.Abort(_("please specify just one revision"))
2713 raise util.Abort(_("please specify just one revision"))
2713
2714
2714 if not rev:
2715 if not rev:
2715 rev = node
2716 rev = node
2716
2717
2717 if date:
2718 if date:
2718 if rev:
2719 if rev:
2719 raise util.Abort(_("you can't specify a revision and a date"))
2720 raise util.Abort(_("you can't specify a revision and a date"))
2720 rev = cmdutil.finddate(ui, repo, date)
2721 rev = cmdutil.finddate(ui, repo, date)
2721
2722
2722 if clean:
2723 if clean:
2723 return hg.clean(repo, rev)
2724 return hg.clean(repo, rev)
2724 else:
2725 else:
2725 return hg.update(repo, rev)
2726 return hg.update(repo, rev)
2726
2727
2727 def verify(ui, repo):
2728 def verify(ui, repo):
2728 """verify the integrity of the repository
2729 """verify the integrity of the repository
2729
2730
2730 Verify the integrity of the current repository.
2731 Verify the integrity of the current repository.
2731
2732
2732 This will perform an extensive check of the repository's
2733 This will perform an extensive check of the repository's
2733 integrity, validating the hashes and checksums of each entry in
2734 integrity, validating the hashes and checksums of each entry in
2734 the changelog, manifest, and tracked files, as well as the
2735 the changelog, manifest, and tracked files, as well as the
2735 integrity of their crosslinks and indices.
2736 integrity of their crosslinks and indices.
2736 """
2737 """
2737 return hg.verify(repo)
2738 return hg.verify(repo)
2738
2739
2739 def version_(ui):
2740 def version_(ui):
2740 """output version and copyright information"""
2741 """output version and copyright information"""
2741 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2742 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2742 % version.get_version())
2743 % version.get_version())
2743 ui.status(_(
2744 ui.status(_(
2744 "\nCopyright (C) 2005-2007 Matt Mackall <mpm@selenic.com> and others\n"
2745 "\nCopyright (C) 2005-2007 Matt Mackall <mpm@selenic.com> and others\n"
2745 "This is free software; see the source for copying conditions. "
2746 "This is free software; see the source for copying conditions. "
2746 "There is NO\nwarranty; "
2747 "There is NO\nwarranty; "
2747 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2748 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2748 ))
2749 ))
2749
2750
2750 # Command options and aliases are listed here, alphabetically
2751 # Command options and aliases are listed here, alphabetically
2751
2752
2752 globalopts = [
2753 globalopts = [
2753 ('R', 'repository', '',
2754 ('R', 'repository', '',
2754 _('repository root directory or symbolic path name')),
2755 _('repository root directory or symbolic path name')),
2755 ('', 'cwd', '', _('change working directory')),
2756 ('', 'cwd', '', _('change working directory')),
2756 ('y', 'noninteractive', None,
2757 ('y', 'noninteractive', None,
2757 _('do not prompt, assume \'yes\' for any required answers')),
2758 _('do not prompt, assume \'yes\' for any required answers')),
2758 ('q', 'quiet', None, _('suppress output')),
2759 ('q', 'quiet', None, _('suppress output')),
2759 ('v', 'verbose', None, _('enable additional output')),
2760 ('v', 'verbose', None, _('enable additional output')),
2760 ('', 'config', [], _('set/override config option')),
2761 ('', 'config', [], _('set/override config option')),
2761 ('', 'debug', None, _('enable debugging output')),
2762 ('', 'debug', None, _('enable debugging output')),
2762 ('', 'debugger', None, _('start debugger')),
2763 ('', 'debugger', None, _('start debugger')),
2763 ('', 'encoding', util._encoding, _('set the charset encoding')),
2764 ('', 'encoding', util._encoding, _('set the charset encoding')),
2764 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2765 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2765 ('', 'lsprof', None, _('print improved command execution profile')),
2766 ('', 'lsprof', None, _('print improved command execution profile')),
2766 ('', 'traceback', None, _('print traceback on exception')),
2767 ('', 'traceback', None, _('print traceback on exception')),
2767 ('', 'time', None, _('time how long the command takes')),
2768 ('', 'time', None, _('time how long the command takes')),
2768 ('', 'profile', None, _('print command execution profile')),
2769 ('', 'profile', None, _('print command execution profile')),
2769 ('', 'version', None, _('output version information and exit')),
2770 ('', 'version', None, _('output version information and exit')),
2770 ('h', 'help', None, _('display help and exit')),
2771 ('h', 'help', None, _('display help and exit')),
2771 ]
2772 ]
2772
2773
2773 dryrunopts = [('n', 'dry-run', None,
2774 dryrunopts = [('n', 'dry-run', None,
2774 _('do not perform actions, just print output'))]
2775 _('do not perform actions, just print output'))]
2775
2776
2776 remoteopts = [
2777 remoteopts = [
2777 ('e', 'ssh', '', _('specify ssh command to use')),
2778 ('e', 'ssh', '', _('specify ssh command to use')),
2778 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2779 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2779 ]
2780 ]
2780
2781
2781 walkopts = [
2782 walkopts = [
2782 ('I', 'include', [], _('include names matching the given patterns')),
2783 ('I', 'include', [], _('include names matching the given patterns')),
2783 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2784 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2784 ]
2785 ]
2785
2786
2786 commitopts = [
2787 commitopts = [
2787 ('m', 'message', '', _('use <text> as commit message')),
2788 ('m', 'message', '', _('use <text> as commit message')),
2788 ('l', 'logfile', '', _('read commit message from <file>')),
2789 ('l', 'logfile', '', _('read commit message from <file>')),
2789 ]
2790 ]
2790
2791
2791 commitopts2 = [
2792 commitopts2 = [
2792 ('d', 'date', '', _('record datecode as commit date')),
2793 ('d', 'date', '', _('record datecode as commit date')),
2793 ('u', 'user', '', _('record user as committer')),
2794 ('u', 'user', '', _('record user as committer')),
2794 ]
2795 ]
2795
2796
2796 table = {
2797 table = {
2797 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2798 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2798 "addremove":
2799 "addremove":
2799 (addremove,
2800 (addremove,
2800 [('s', 'similarity', '',
2801 [('s', 'similarity', '',
2801 _('guess renamed files by similarity (0<=s<=100)')),
2802 _('guess renamed files by similarity (0<=s<=100)')),
2802 ] + walkopts + dryrunopts,
2803 ] + walkopts + dryrunopts,
2803 _('hg addremove [OPTION]... [FILE]...')),
2804 _('hg addremove [OPTION]... [FILE]...')),
2804 "^annotate":
2805 "^annotate":
2805 (annotate,
2806 (annotate,
2806 [('r', 'rev', '', _('annotate the specified revision')),
2807 [('r', 'rev', '', _('annotate the specified revision')),
2807 ('f', 'follow', None, _('follow file copies and renames')),
2808 ('f', 'follow', None, _('follow file copies and renames')),
2808 ('a', 'text', None, _('treat all files as text')),
2809 ('a', 'text', None, _('treat all files as text')),
2809 ('u', 'user', None, _('list the author')),
2810 ('u', 'user', None, _('list the author')),
2810 ('d', 'date', None, _('list the date')),
2811 ('d', 'date', None, _('list the date')),
2811 ('n', 'number', None, _('list the revision number (default)')),
2812 ('n', 'number', None, _('list the revision number (default)')),
2812 ('c', 'changeset', None, _('list the changeset')),
2813 ('c', 'changeset', None, _('list the changeset')),
2813 ('l', 'line-number', None,
2814 ('l', 'line-number', None,
2814 _('show line number at the first appearance'))
2815 _('show line number at the first appearance'))
2815 ] + walkopts,
2816 ] + walkopts,
2816 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
2817 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
2817 "archive":
2818 "archive":
2818 (archive,
2819 (archive,
2819 [('', 'no-decode', None, _('do not pass files through decoders')),
2820 [('', 'no-decode', None, _('do not pass files through decoders')),
2820 ('p', 'prefix', '', _('directory prefix for files in archive')),
2821 ('p', 'prefix', '', _('directory prefix for files in archive')),
2821 ('r', 'rev', '', _('revision to distribute')),
2822 ('r', 'rev', '', _('revision to distribute')),
2822 ('t', 'type', '', _('type of distribution to create')),
2823 ('t', 'type', '', _('type of distribution to create')),
2823 ] + walkopts,
2824 ] + walkopts,
2824 _('hg archive [OPTION]... DEST')),
2825 _('hg archive [OPTION]... DEST')),
2825 "backout":
2826 "backout":
2826 (backout,
2827 (backout,
2827 [('', 'merge', None,
2828 [('', 'merge', None,
2828 _('merge with old dirstate parent after backout')),
2829 _('merge with old dirstate parent after backout')),
2829 ('', 'parent', '', _('parent to choose when backing out merge')),
2830 ('', 'parent', '', _('parent to choose when backing out merge')),
2830 ('r', 'rev', '', _('revision to backout')),
2831 ('r', 'rev', '', _('revision to backout')),
2831 ] + walkopts + commitopts + commitopts2,
2832 ] + walkopts + commitopts + commitopts2,
2832 _('hg backout [OPTION]... [-r] REV')),
2833 _('hg backout [OPTION]... [-r] REV')),
2833 "branch":
2834 "branch":
2834 (branch,
2835 (branch,
2835 [('f', 'force', None,
2836 [('f', 'force', None,
2836 _('set branch name even if it shadows an existing branch'))],
2837 _('set branch name even if it shadows an existing branch'))],
2837 _('hg branch [NAME]')),
2838 _('hg branch [NAME]')),
2838 "branches":
2839 "branches":
2839 (branches,
2840 (branches,
2840 [('a', 'active', False,
2841 [('a', 'active', False,
2841 _('show only branches that have unmerged heads'))],
2842 _('show only branches that have unmerged heads'))],
2842 _('hg branches [-a]')),
2843 _('hg branches [-a]')),
2843 "bundle":
2844 "bundle":
2844 (bundle,
2845 (bundle,
2845 [('f', 'force', None,
2846 [('f', 'force', None,
2846 _('run even when remote repository is unrelated')),
2847 _('run even when remote repository is unrelated')),
2847 ('r', 'rev', [],
2848 ('r', 'rev', [],
2848 _('a changeset you would like to bundle')),
2849 _('a changeset you would like to bundle')),
2849 ('', 'base', [],
2850 ('', 'base', [],
2850 _('a base changeset to specify instead of a destination')),
2851 _('a base changeset to specify instead of a destination')),
2851 ] + remoteopts,
2852 ] + remoteopts,
2852 _('hg bundle [-f] [-r REV]... [--base REV]... FILE [DEST]')),
2853 _('hg bundle [-f] [-r REV]... [--base REV]... FILE [DEST]')),
2853 "cat":
2854 "cat":
2854 (cat,
2855 (cat,
2855 [('o', 'output', '', _('print output to file with formatted name')),
2856 [('o', 'output', '', _('print output to file with formatted name')),
2856 ('r', 'rev', '', _('print the given revision')),
2857 ('r', 'rev', '', _('print the given revision')),
2857 ] + walkopts,
2858 ] + walkopts,
2858 _('hg cat [OPTION]... FILE...')),
2859 _('hg cat [OPTION]... FILE...')),
2859 "^clone":
2860 "^clone":
2860 (clone,
2861 (clone,
2861 [('U', 'noupdate', None, _('do not update the new working directory')),
2862 [('U', 'noupdate', None, _('do not update the new working directory')),
2862 ('r', 'rev', [],
2863 ('r', 'rev', [],
2863 _('a changeset you would like to have after cloning')),
2864 _('a changeset you would like to have after cloning')),
2864 ('', 'pull', None, _('use pull protocol to copy metadata')),
2865 ('', 'pull', None, _('use pull protocol to copy metadata')),
2865 ('', 'uncompressed', None,
2866 ('', 'uncompressed', None,
2866 _('use uncompressed transfer (fast over LAN)')),
2867 _('use uncompressed transfer (fast over LAN)')),
2867 ] + remoteopts,
2868 ] + remoteopts,
2868 _('hg clone [OPTION]... SOURCE [DEST]')),
2869 _('hg clone [OPTION]... SOURCE [DEST]')),
2869 "^commit|ci":
2870 "^commit|ci":
2870 (commit,
2871 (commit,
2871 [('A', 'addremove', None,
2872 [('A', 'addremove', None,
2872 _('mark new/missing files as added/removed before committing')),
2873 _('mark new/missing files as added/removed before committing')),
2873 ] + walkopts + commitopts + commitopts2,
2874 ] + walkopts + commitopts + commitopts2,
2874 _('hg commit [OPTION]... [FILE]...')),
2875 _('hg commit [OPTION]... [FILE]...')),
2875 "copy|cp":
2876 "copy|cp":
2876 (copy,
2877 (copy,
2877 [('A', 'after', None, _('record a copy that has already occurred')),
2878 [('A', 'after', None, _('record a copy that has already occurred')),
2878 ('f', 'force', None,
2879 ('f', 'force', None,
2879 _('forcibly copy over an existing managed file')),
2880 _('forcibly copy over an existing managed file')),
2880 ] + walkopts + dryrunopts,
2881 ] + walkopts + dryrunopts,
2881 _('hg copy [OPTION]... [SOURCE]... DEST')),
2882 _('hg copy [OPTION]... [SOURCE]... DEST')),
2882 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2883 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2883 "debugcomplete":
2884 "debugcomplete":
2884 (debugcomplete,
2885 (debugcomplete,
2885 [('o', 'options', None, _('show the command options'))],
2886 [('o', 'options', None, _('show the command options'))],
2886 _('debugcomplete [-o] CMD')),
2887 _('debugcomplete [-o] CMD')),
2887 "debuginstall": (debuginstall, [], _('debuginstall')),
2888 "debuginstall": (debuginstall, [], _('debuginstall')),
2888 "debugrebuildstate":
2889 "debugrebuildstate":
2889 (debugrebuildstate,
2890 (debugrebuildstate,
2890 [('r', 'rev', '', _('revision to rebuild to'))],
2891 [('r', 'rev', '', _('revision to rebuild to'))],
2891 _('debugrebuildstate [-r REV] [REV]')),
2892 _('debugrebuildstate [-r REV] [REV]')),
2892 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2893 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2893 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2894 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2894 "debugstate": (debugstate, [], _('debugstate')),
2895 "debugstate": (debugstate, [], _('debugstate')),
2895 "debugdate":
2896 "debugdate":
2896 (debugdate,
2897 (debugdate,
2897 [('e', 'extended', None, _('try extended date formats'))],
2898 [('e', 'extended', None, _('try extended date formats'))],
2898 _('debugdate [-e] DATE [RANGE]')),
2899 _('debugdate [-e] DATE [RANGE]')),
2899 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2900 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2900 "debugindex": (debugindex, [], _('debugindex FILE')),
2901 "debugindex": (debugindex, [], _('debugindex FILE')),
2901 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2902 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2902 "debugrename":
2903 "debugrename":
2903 (debugrename,
2904 (debugrename,
2904 [('r', 'rev', '', _('revision to debug'))],
2905 [('r', 'rev', '', _('revision to debug'))],
2905 _('debugrename [-r REV] FILE')),
2906 _('debugrename [-r REV] FILE')),
2906 "debugwalk": (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
2907 "debugwalk": (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
2907 "^diff":
2908 "^diff":
2908 (diff,
2909 (diff,
2909 [('r', 'rev', [], _('revision')),
2910 [('r', 'rev', [], _('revision')),
2910 ('a', 'text', None, _('treat all files as text')),
2911 ('a', 'text', None, _('treat all files as text')),
2911 ('p', 'show-function', None,
2912 ('p', 'show-function', None,
2912 _('show which function each change is in')),
2913 _('show which function each change is in')),
2913 ('g', 'git', None, _('use git extended diff format')),
2914 ('g', 'git', None, _('use git extended diff format')),
2914 ('', 'nodates', None, _("don't include dates in diff headers")),
2915 ('', 'nodates', None, _("don't include dates in diff headers")),
2915 ('w', 'ignore-all-space', None,
2916 ('w', 'ignore-all-space', None,
2916 _('ignore white space when comparing lines')),
2917 _('ignore white space when comparing lines')),
2917 ('b', 'ignore-space-change', None,
2918 ('b', 'ignore-space-change', None,
2918 _('ignore changes in the amount of white space')),
2919 _('ignore changes in the amount of white space')),
2919 ('B', 'ignore-blank-lines', None,
2920 ('B', 'ignore-blank-lines', None,
2920 _('ignore changes whose lines are all blank')),
2921 _('ignore changes whose lines are all blank')),
2921 ] + walkopts,
2922 ] + walkopts,
2922 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
2923 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
2923 "^export":
2924 "^export":
2924 (export,
2925 (export,
2925 [('o', 'output', '', _('print output to file with formatted name')),
2926 [('o', 'output', '', _('print output to file with formatted name')),
2926 ('a', 'text', None, _('treat all files as text')),
2927 ('a', 'text', None, _('treat all files as text')),
2927 ('g', 'git', None, _('use git extended diff format')),
2928 ('g', 'git', None, _('use git extended diff format')),
2928 ('', 'nodates', None, _("don't include dates in diff headers")),
2929 ('', 'nodates', None, _("don't include dates in diff headers")),
2929 ('', 'switch-parent', None, _('diff against the second parent'))],
2930 ('', 'switch-parent', None, _('diff against the second parent'))],
2930 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
2931 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
2931 "grep":
2932 "grep":
2932 (grep,
2933 (grep,
2933 [('0', 'print0', None, _('end fields with NUL')),
2934 [('0', 'print0', None, _('end fields with NUL')),
2934 ('', 'all', None, _('print all revisions that match')),
2935 ('', 'all', None, _('print all revisions that match')),
2935 ('f', 'follow', None,
2936 ('f', 'follow', None,
2936 _('follow changeset history, or file history across copies and renames')),
2937 _('follow changeset history, or file history across copies and renames')),
2937 ('i', 'ignore-case', None, _('ignore case when matching')),
2938 ('i', 'ignore-case', None, _('ignore case when matching')),
2938 ('l', 'files-with-matches', None,
2939 ('l', 'files-with-matches', None,
2939 _('print only filenames and revs that match')),
2940 _('print only filenames and revs that match')),
2940 ('n', 'line-number', None, _('print matching line numbers')),
2941 ('n', 'line-number', None, _('print matching line numbers')),
2941 ('r', 'rev', [], _('search in given revision range')),
2942 ('r', 'rev', [], _('search in given revision range')),
2942 ('u', 'user', None, _('print user who committed change')),
2943 ('u', 'user', None, _('print user who committed change')),
2943 ] + walkopts,
2944 ] + walkopts,
2944 _('hg grep [OPTION]... PATTERN [FILE]...')),
2945 _('hg grep [OPTION]... PATTERN [FILE]...')),
2945 "heads":
2946 "heads":
2946 (heads,
2947 (heads,
2947 [('', 'style', '', _('display using template map file')),
2948 [('', 'style', '', _('display using template map file')),
2948 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2949 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2949 ('', 'template', '', _('display with template'))],
2950 ('', 'template', '', _('display with template'))],
2950 _('hg heads [-r REV] [REV]...')),
2951 _('hg heads [-r REV] [REV]...')),
2951 "help": (help_, [], _('hg help [COMMAND]')),
2952 "help": (help_, [], _('hg help [COMMAND]')),
2952 "identify|id":
2953 "identify|id":
2953 (identify,
2954 (identify,
2954 [('r', 'rev', '', _('identify the specified rev')),
2955 [('r', 'rev', '', _('identify the specified rev')),
2955 ('n', 'num', None, _('show local revision number')),
2956 ('n', 'num', None, _('show local revision number')),
2956 ('i', 'id', None, _('show global revision id')),
2957 ('i', 'id', None, _('show global revision id')),
2957 ('b', 'branch', None, _('show branch')),
2958 ('b', 'branch', None, _('show branch')),
2958 ('t', 'tags', None, _('show tags'))],
2959 ('t', 'tags', None, _('show tags'))],
2959 _('hg identify [-nibt] [-r REV] [SOURCE]')),
2960 _('hg identify [-nibt] [-r REV] [SOURCE]')),
2960 "import|patch":
2961 "import|patch":
2961 (import_,
2962 (import_,
2962 [('p', 'strip', 1,
2963 [('p', 'strip', 1,
2963 _('directory strip option for patch. This has the same\n'
2964 _('directory strip option for patch. This has the same\n'
2964 'meaning as the corresponding patch option')),
2965 'meaning as the corresponding patch option')),
2965 ('b', 'base', '', _('base path')),
2966 ('b', 'base', '', _('base path')),
2966 ('f', 'force', None,
2967 ('f', 'force', None,
2967 _('skip check for outstanding uncommitted changes')),
2968 _('skip check for outstanding uncommitted changes')),
2968 ('', 'exact', None,
2969 ('', 'exact', None,
2969 _('apply patch to the nodes from which it was generated')),
2970 _('apply patch to the nodes from which it was generated')),
2970 ('', 'import-branch', None,
2971 ('', 'import-branch', None,
2971 _('Use any branch information in patch (implied by --exact)'))] + commitopts,
2972 _('Use any branch information in patch (implied by --exact)'))] + commitopts,
2972 _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
2973 _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
2973 "incoming|in": (incoming,
2974 "incoming|in": (incoming,
2974 [('M', 'no-merges', None, _('do not show merges')),
2975 [('M', 'no-merges', None, _('do not show merges')),
2975 ('f', 'force', None,
2976 ('f', 'force', None,
2976 _('run even when remote repository is unrelated')),
2977 _('run even when remote repository is unrelated')),
2977 ('', 'style', '', _('display using template map file')),
2978 ('', 'style', '', _('display using template map file')),
2978 ('n', 'newest-first', None, _('show newest record first')),
2979 ('n', 'newest-first', None, _('show newest record first')),
2979 ('', 'bundle', '', _('file to store the bundles into')),
2980 ('', 'bundle', '', _('file to store the bundles into')),
2980 ('p', 'patch', None, _('show patch')),
2981 ('p', 'patch', None, _('show patch')),
2981 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2982 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2982 ('', 'template', '', _('display with template')),
2983 ('', 'template', '', _('display with template')),
2983 ] + remoteopts,
2984 ] + remoteopts,
2984 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
2985 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
2985 ' [--bundle FILENAME] [SOURCE]')),
2986 ' [--bundle FILENAME] [SOURCE]')),
2986 "^init":
2987 "^init":
2987 (init,
2988 (init,
2988 remoteopts,
2989 remoteopts,
2989 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
2990 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
2990 "locate":
2991 "locate":
2991 (locate,
2992 (locate,
2992 [('r', 'rev', '', _('search the repository as it stood at rev')),
2993 [('r', 'rev', '', _('search the repository as it stood at rev')),
2993 ('0', 'print0', None,
2994 ('0', 'print0', None,
2994 _('end filenames with NUL, for use with xargs')),
2995 _('end filenames with NUL, for use with xargs')),
2995 ('f', 'fullpath', None,
2996 ('f', 'fullpath', None,
2996 _('print complete paths from the filesystem root')),
2997 _('print complete paths from the filesystem root')),
2997 ] + walkopts,
2998 ] + walkopts,
2998 _('hg locate [OPTION]... [PATTERN]...')),
2999 _('hg locate [OPTION]... [PATTERN]...')),
2999 "^log|history":
3000 "^log|history":
3000 (log,
3001 (log,
3001 [('f', 'follow', None,
3002 [('f', 'follow', None,
3002 _('follow changeset history, or file history across copies and renames')),
3003 _('follow changeset history, or file history across copies and renames')),
3003 ('', 'follow-first', None,
3004 ('', 'follow-first', None,
3004 _('only follow the first parent of merge changesets')),
3005 _('only follow the first parent of merge changesets')),
3005 ('d', 'date', '', _('show revs matching date spec')),
3006 ('d', 'date', '', _('show revs matching date spec')),
3006 ('C', 'copies', None, _('show copied files')),
3007 ('C', 'copies', None, _('show copied files')),
3007 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3008 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3008 ('l', 'limit', '', _('limit number of changes displayed')),
3009 ('l', 'limit', '', _('limit number of changes displayed')),
3009 ('r', 'rev', [], _('show the specified revision or range')),
3010 ('r', 'rev', [], _('show the specified revision or range')),
3010 ('', 'removed', None, _('include revs where files were removed')),
3011 ('', 'removed', None, _('include revs where files were removed')),
3011 ('M', 'no-merges', None, _('do not show merges')),
3012 ('M', 'no-merges', None, _('do not show merges')),
3012 ('', 'style', '', _('display using template map file')),
3013 ('', 'style', '', _('display using template map file')),
3013 ('m', 'only-merges', None, _('show only merges')),
3014 ('m', 'only-merges', None, _('show only merges')),
3014 ('p', 'patch', None, _('show patch')),
3015 ('p', 'patch', None, _('show patch')),
3015 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3016 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3016 ('', 'template', '', _('display with template')),
3017 ('', 'template', '', _('display with template')),
3017 ] + walkopts,
3018 ] + walkopts,
3018 _('hg log [OPTION]... [FILE]')),
3019 _('hg log [OPTION]... [FILE]')),
3019 "manifest": (manifest, [('r', 'rev', '', _('revision to display'))],
3020 "manifest": (manifest, [('r', 'rev', '', _('revision to display'))],
3020 _('hg manifest [-r REV]')),
3021 _('hg manifest [-r REV]')),
3021 "^merge":
3022 "^merge":
3022 (merge,
3023 (merge,
3023 [('f', 'force', None, _('force a merge with outstanding changes')),
3024 [('f', 'force', None, _('force a merge with outstanding changes')),
3024 ('r', 'rev', '', _('revision to merge')),
3025 ('r', 'rev', '', _('revision to merge')),
3025 ],
3026 ],
3026 _('hg merge [-f] [[-r] REV]')),
3027 _('hg merge [-f] [[-r] REV]')),
3027 "outgoing|out": (outgoing,
3028 "outgoing|out": (outgoing,
3028 [('M', 'no-merges', None, _('do not show merges')),
3029 [('M', 'no-merges', None, _('do not show merges')),
3029 ('f', 'force', None,
3030 ('f', 'force', None,
3030 _('run even when remote repository is unrelated')),
3031 _('run even when remote repository is unrelated')),
3031 ('p', 'patch', None, _('show patch')),
3032 ('p', 'patch', None, _('show patch')),
3032 ('', 'style', '', _('display using template map file')),
3033 ('', 'style', '', _('display using template map file')),
3033 ('r', 'rev', [], _('a specific revision you would like to push')),
3034 ('r', 'rev', [], _('a specific revision you would like to push')),
3034 ('n', 'newest-first', None, _('show newest record first')),
3035 ('n', 'newest-first', None, _('show newest record first')),
3035 ('', 'template', '', _('display with template')),
3036 ('', 'template', '', _('display with template')),
3036 ] + remoteopts,
3037 ] + remoteopts,
3037 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3038 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3038 "^parents":
3039 "^parents":
3039 (parents,
3040 (parents,
3040 [('r', 'rev', '', _('show parents from the specified rev')),
3041 [('r', 'rev', '', _('show parents from the specified rev')),
3041 ('', 'style', '', _('display using template map file')),
3042 ('', 'style', '', _('display using template map file')),
3042 ('', 'template', '', _('display with template'))],
3043 ('', 'template', '', _('display with template'))],
3043 _('hg parents [-r REV] [FILE]')),
3044 _('hg parents [-r REV] [FILE]')),
3044 "paths": (paths, [], _('hg paths [NAME]')),
3045 "paths": (paths, [], _('hg paths [NAME]')),
3045 "^pull":
3046 "^pull":
3046 (pull,
3047 (pull,
3047 [('u', 'update', None,
3048 [('u', 'update', None,
3048 _('update to new tip if changesets were pulled')),
3049 _('update to new tip if changesets were pulled')),
3049 ('f', 'force', None,
3050 ('f', 'force', None,
3050 _('run even when remote repository is unrelated')),
3051 _('run even when remote repository is unrelated')),
3051 ('r', 'rev', [],
3052 ('r', 'rev', [],
3052 _('a specific revision up to which you would like to pull')),
3053 _('a specific revision up to which you would like to pull')),
3053 ] + remoteopts,
3054 ] + remoteopts,
3054 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3055 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3055 "^push":
3056 "^push":
3056 (push,
3057 (push,
3057 [('f', 'force', None, _('force push')),
3058 [('f', 'force', None, _('force push')),
3058 ('r', 'rev', [], _('a specific revision you would like to push')),
3059 ('r', 'rev', [], _('a specific revision you would like to push')),
3059 ] + remoteopts,
3060 ] + remoteopts,
3060 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3061 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3061 "debugrawcommit|rawcommit":
3062 "debugrawcommit|rawcommit":
3062 (rawcommit,
3063 (rawcommit,
3063 [('p', 'parent', [], _('parent')),
3064 [('p', 'parent', [], _('parent')),
3064 ('F', 'files', '', _('file list'))
3065 ('F', 'files', '', _('file list'))
3065 ] + commitopts + commitopts2,
3066 ] + commitopts + commitopts2,
3066 _('hg debugrawcommit [OPTION]... [FILE]...')),
3067 _('hg debugrawcommit [OPTION]... [FILE]...')),
3067 "recover": (recover, [], _('hg recover')),
3068 "recover": (recover, [], _('hg recover')),
3068 "^remove|rm":
3069 "^remove|rm":
3069 (remove,
3070 (remove,
3070 [('A', 'after', None, _('record remove that has already occurred')),
3071 [('A', 'after', None, _('record remove that has already occurred')),
3071 ('f', 'force', None, _('remove file even if modified')),
3072 ('f', 'force', None, _('remove file even if modified')),
3072 ] + walkopts,
3073 ] + walkopts,
3073 _('hg remove [OPTION]... FILE...')),
3074 _('hg remove [OPTION]... FILE...')),
3074 "rename|mv":
3075 "rename|mv":
3075 (rename,
3076 (rename,
3076 [('A', 'after', None, _('record a rename that has already occurred')),
3077 [('A', 'after', None, _('record a rename that has already occurred')),
3077 ('f', 'force', None,
3078 ('f', 'force', None,
3078 _('forcibly copy over an existing managed file')),
3079 _('forcibly copy over an existing managed file')),
3079 ] + walkopts + dryrunopts,
3080 ] + walkopts + dryrunopts,
3080 _('hg rename [OPTION]... SOURCE... DEST')),
3081 _('hg rename [OPTION]... SOURCE... DEST')),
3081 "^revert":
3082 "^revert":
3082 (revert,
3083 (revert,
3083 [('a', 'all', None, _('revert all changes when no arguments given')),
3084 [('a', 'all', None, _('revert all changes when no arguments given')),
3084 ('d', 'date', '', _('tipmost revision matching date')),
3085 ('d', 'date', '', _('tipmost revision matching date')),
3085 ('r', 'rev', '', _('revision to revert to')),
3086 ('r', 'rev', '', _('revision to revert to')),
3086 ('', 'no-backup', None, _('do not save backup copies of files')),
3087 ('', 'no-backup', None, _('do not save backup copies of files')),
3087 ] + walkopts + dryrunopts,
3088 ] + walkopts + dryrunopts,
3088 _('hg revert [OPTION]... [-r REV] [NAME]...')),
3089 _('hg revert [OPTION]... [-r REV] [NAME]...')),
3089 "rollback": (rollback, [], _('hg rollback')),
3090 "rollback": (rollback, [], _('hg rollback')),
3090 "root": (root, [], _('hg root')),
3091 "root": (root, [], _('hg root')),
3091 "showconfig|debugconfig":
3092 "showconfig|debugconfig":
3092 (showconfig,
3093 (showconfig,
3093 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3094 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3094 _('showconfig [-u] [NAME]...')),
3095 _('showconfig [-u] [NAME]...')),
3095 "^serve":
3096 "^serve":
3096 (serve,
3097 (serve,
3097 [('A', 'accesslog', '', _('name of access log file to write to')),
3098 [('A', 'accesslog', '', _('name of access log file to write to')),
3098 ('d', 'daemon', None, _('run server in background')),
3099 ('d', 'daemon', None, _('run server in background')),
3099 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3100 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3100 ('E', 'errorlog', '', _('name of error log file to write to')),
3101 ('E', 'errorlog', '', _('name of error log file to write to')),
3101 ('p', 'port', 0, _('port to use (default: 8000)')),
3102 ('p', 'port', 0, _('port to use (default: 8000)')),
3102 ('a', 'address', '', _('address to use')),
3103 ('a', 'address', '', _('address to use')),
3103 ('n', 'name', '',
3104 ('n', 'name', '',
3104 _('name to show in web pages (default: working dir)')),
3105 _('name to show in web pages (default: working dir)')),
3105 ('', 'webdir-conf', '', _('name of the webdir config file'
3106 ('', 'webdir-conf', '', _('name of the webdir config file'
3106 ' (serve more than one repo)')),
3107 ' (serve more than one repo)')),
3107 ('', 'pid-file', '', _('name of file to write process ID to')),
3108 ('', 'pid-file', '', _('name of file to write process ID to')),
3108 ('', 'stdio', None, _('for remote clients')),
3109 ('', 'stdio', None, _('for remote clients')),
3109 ('t', 'templates', '', _('web templates to use')),
3110 ('t', 'templates', '', _('web templates to use')),
3110 ('', 'style', '', _('template style to use')),
3111 ('', 'style', '', _('template style to use')),
3111 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3112 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3112 ('', 'certificate', '', _('SSL certificate file'))],
3113 ('', 'certificate', '', _('SSL certificate file'))],
3113 _('hg serve [OPTION]...')),
3114 _('hg serve [OPTION]...')),
3114 "^status|st":
3115 "^status|st":
3115 (status,
3116 (status,
3116 [('A', 'all', None, _('show status of all files')),
3117 [('A', 'all', None, _('show status of all files')),
3117 ('m', 'modified', None, _('show only modified files')),
3118 ('m', 'modified', None, _('show only modified files')),
3118 ('a', 'added', None, _('show only added files')),
3119 ('a', 'added', None, _('show only added files')),
3119 ('r', 'removed', None, _('show only removed files')),
3120 ('r', 'removed', None, _('show only removed files')),
3120 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3121 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3121 ('c', 'clean', None, _('show only files without changes')),
3122 ('c', 'clean', None, _('show only files without changes')),
3122 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3123 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3123 ('i', 'ignored', None, _('show only ignored files')),
3124 ('i', 'ignored', None, _('show only ignored files')),
3124 ('n', 'no-status', None, _('hide status prefix')),
3125 ('n', 'no-status', None, _('hide status prefix')),
3125 ('C', 'copies', None, _('show source of copied files')),
3126 ('C', 'copies', None, _('show source of copied files')),
3126 ('0', 'print0', None,
3127 ('0', 'print0', None,
3127 _('end filenames with NUL, for use with xargs')),
3128 _('end filenames with NUL, for use with xargs')),
3128 ('', 'rev', [], _('show difference from revision')),
3129 ('', 'rev', [], _('show difference from revision')),
3129 ] + walkopts,
3130 ] + walkopts,
3130 _('hg status [OPTION]... [FILE]...')),
3131 _('hg status [OPTION]... [FILE]...')),
3131 "tag":
3132 "tag":
3132 (tag,
3133 (tag,
3133 [('f', 'force', None, _('replace existing tag')),
3134 [('f', 'force', None, _('replace existing tag')),
3134 ('l', 'local', None, _('make the tag local')),
3135 ('l', 'local', None, _('make the tag local')),
3135 ('r', 'rev', '', _('revision to tag')),
3136 ('r', 'rev', '', _('revision to tag')),
3136 ('', 'remove', None, _('remove a tag')),
3137 ('', 'remove', None, _('remove a tag')),
3137 # -l/--local is already there, commitopts cannot be used
3138 # -l/--local is already there, commitopts cannot be used
3138 ('m', 'message', '', _('use <text> as commit message')),
3139 ('m', 'message', '', _('use <text> as commit message')),
3139 ] + commitopts2,
3140 ] + commitopts2,
3140 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3141 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3141 "tags": (tags, [], _('hg tags')),
3142 "tags": (tags, [], _('hg tags')),
3142 "tip":
3143 "tip":
3143 (tip,
3144 (tip,
3144 [('', 'style', '', _('display using template map file')),
3145 [('', 'style', '', _('display using template map file')),
3145 ('p', 'patch', None, _('show patch')),
3146 ('p', 'patch', None, _('show patch')),
3146 ('', 'template', '', _('display with template'))],
3147 ('', 'template', '', _('display with template'))],
3147 _('hg tip [-p]')),
3148 _('hg tip [-p]')),
3148 "unbundle":
3149 "unbundle":
3149 (unbundle,
3150 (unbundle,
3150 [('u', 'update', None,
3151 [('u', 'update', None,
3151 _('update to new tip if changesets were unbundled'))],
3152 _('update to new tip if changesets were unbundled'))],
3152 _('hg unbundle [-u] FILE...')),
3153 _('hg unbundle [-u] FILE...')),
3153 "^update|up|checkout|co":
3154 "^update|up|checkout|co":
3154 (update,
3155 (update,
3155 [('C', 'clean', None, _('overwrite locally modified files')),
3156 [('C', 'clean', None, _('overwrite locally modified files')),
3156 ('d', 'date', '', _('tipmost revision matching date')),
3157 ('d', 'date', '', _('tipmost revision matching date')),
3157 ('r', 'rev', '', _('revision'))],
3158 ('r', 'rev', '', _('revision'))],
3158 _('hg update [-C] [-d DATE] [[-r] REV]')),
3159 _('hg update [-C] [-d DATE] [[-r] REV]')),
3159 "verify": (verify, [], _('hg verify')),
3160 "verify": (verify, [], _('hg verify')),
3160 "version": (version_, [], _('hg version')),
3161 "version": (version_, [], _('hg version')),
3161 }
3162 }
3162
3163
3163 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3164 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3164 " debugindex debugindexdot debugdate debuginstall")
3165 " debugindex debugindexdot debugdate debuginstall")
3165 optionalrepo = ("identify paths serve showconfig")
3166 optionalrepo = ("identify paths serve showconfig")
@@ -1,253 +1,253 b''
1 # mdiff.py - diff and patch routines for mercurial
1 # mdiff.py - diff and patch routines for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import bdiff, mpatch, re, struct, util, md5
8 import bdiff, mpatch, re, struct, util, md5
9
9
10 def splitnewlines(text):
10 def splitnewlines(text):
11 '''like str.splitlines, but only split on newlines.'''
11 '''like str.splitlines, but only split on newlines.'''
12 lines = [l + '\n' for l in text.split('\n')]
12 lines = [l + '\n' for l in text.split('\n')]
13 if lines:
13 if lines:
14 if lines[-1] == '\n':
14 if lines[-1] == '\n':
15 lines.pop()
15 lines.pop()
16 else:
16 else:
17 lines[-1] = lines[-1][:-1]
17 lines[-1] = lines[-1][:-1]
18 return lines
18 return lines
19
19
20 class diffopts(object):
20 class diffopts(object):
21 '''context is the number of context lines
21 '''context is the number of context lines
22 text treats all files as text
22 text treats all files as text
23 showfunc enables diff -p output
23 showfunc enables diff -p output
24 git enables the git extended patch format
24 git enables the git extended patch format
25 nodates removes dates from diff headers
25 nodates removes dates from diff headers
26 ignorews ignores all whitespace changes in the diff
26 ignorews ignores all whitespace changes in the diff
27 ignorewsamount ignores changes in the amount of whitespace
27 ignorewsamount ignores changes in the amount of whitespace
28 ignoreblanklines ignores changes whose lines are all blank'''
28 ignoreblanklines ignores changes whose lines are all blank'''
29
29
30 defaults = {
30 defaults = {
31 'context': 3,
31 'context': 3,
32 'text': False,
32 'text': False,
33 'showfunc': True,
33 'showfunc': True,
34 'git': False,
34 'git': False,
35 'nodates': False,
35 'nodates': False,
36 'ignorews': False,
36 'ignorews': False,
37 'ignorewsamount': False,
37 'ignorewsamount': False,
38 'ignoreblanklines': False,
38 'ignoreblanklines': False,
39 }
39 }
40
40
41 __slots__ = defaults.keys()
41 __slots__ = defaults.keys()
42
42
43 def __init__(self, **opts):
43 def __init__(self, **opts):
44 for k in self.__slots__:
44 for k in self.__slots__:
45 v = opts.get(k)
45 v = opts.get(k)
46 if v is None:
46 if v is None:
47 v = self.defaults[k]
47 v = self.defaults[k]
48 setattr(self, k, v)
48 setattr(self, k, v)
49
49
50 defaultopts = diffopts()
50 defaultopts = diffopts()
51
51
52 def wsclean(opts, text):
52 def wsclean(opts, text):
53 if opts.ignorews:
53 if opts.ignorews:
54 text = re.sub('[ \t]+', '', text)
54 text = re.sub('[ \t]+', '', text)
55 elif opts.ignorewsamount:
55 elif opts.ignorewsamount:
56 text = re.sub('[ \t]+', ' ', text)
56 text = re.sub('[ \t]+', ' ', text)
57 text = re.sub('[ \t]+\n', '\n', text)
57 text = re.sub('[ \t]+\n', '\n', text)
58 if opts.ignoreblanklines:
58 if opts.ignoreblanklines:
59 text = re.sub('\n+', '', text)
59 text = re.sub('\n+', '', text)
60 return text
60 return text
61
61
62 def unidiff(a, ad, b, bd, fn, r=None, opts=defaultopts):
62 def unidiff(a, ad, b, bd, fn1, fn2, r=None, opts=defaultopts):
63 def datetag(date, addtab=True):
63 def datetag(date, addtab=True):
64 if not opts.git and not opts.nodates:
64 if not opts.git and not opts.nodates:
65 return '\t%s\n' % date
65 return '\t%s\n' % date
66 if addtab and ' ' in fn:
66 if addtab and ' ' in fn1:
67 return '\t\n'
67 return '\t\n'
68 return '\n'
68 return '\n'
69
69
70 if not a and not b: return ""
70 if not a and not b: return ""
71 epoch = util.datestr((0, 0))
71 epoch = util.datestr((0, 0))
72
72
73 if not opts.text and (util.binary(a) or util.binary(b)):
73 if not opts.text and (util.binary(a) or util.binary(b)):
74 def h(v):
74 def h(v):
75 # md5 is used instead of sha1 because md5 is supposedly faster
75 # md5 is used instead of sha1 because md5 is supposedly faster
76 return md5.new(v).digest()
76 return md5.new(v).digest()
77 if a and b and len(a) == len(b) and h(a) == h(b):
77 if a and b and len(a) == len(b) and h(a) == h(b):
78 return ""
78 return ""
79 l = ['Binary file %s has changed\n' % fn]
79 l = ['Binary file %s has changed\n' % fn1]
80 elif not a:
80 elif not a:
81 b = splitnewlines(b)
81 b = splitnewlines(b)
82 if a is None:
82 if a is None:
83 l1 = '--- /dev/null%s' % datetag(epoch, False)
83 l1 = '--- /dev/null%s' % datetag(epoch, False)
84 else:
84 else:
85 l1 = "--- %s%s" % ("a/" + fn, datetag(ad))
85 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
86 l2 = "+++ %s%s" % ("b/" + fn, datetag(bd))
86 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
87 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
87 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
88 l = [l1, l2, l3] + ["+" + e for e in b]
88 l = [l1, l2, l3] + ["+" + e for e in b]
89 elif not b:
89 elif not b:
90 a = splitnewlines(a)
90 a = splitnewlines(a)
91 l1 = "--- %s%s" % ("a/" + fn, datetag(ad))
91 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
92 if b is None:
92 if b is None:
93 l2 = '+++ /dev/null%s' % datetag(epoch, False)
93 l2 = '+++ /dev/null%s' % datetag(epoch, False)
94 else:
94 else:
95 l2 = "+++ %s%s" % ("b/" + fn, datetag(bd))
95 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
96 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
96 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
97 l = [l1, l2, l3] + ["-" + e for e in a]
97 l = [l1, l2, l3] + ["-" + e for e in a]
98 else:
98 else:
99 al = splitnewlines(a)
99 al = splitnewlines(a)
100 bl = splitnewlines(b)
100 bl = splitnewlines(b)
101 l = list(bunidiff(a, b, al, bl, "a/" + fn, "b/" + fn, opts=opts))
101 l = list(bunidiff(a, b, al, bl, "a/" + fn1, "b/" + fn2, opts=opts))
102 if not l: return ""
102 if not l: return ""
103 # difflib uses a space, rather than a tab
103 # difflib uses a space, rather than a tab
104 l[0] = "%s%s" % (l[0][:-2], datetag(ad))
104 l[0] = "%s%s" % (l[0][:-2], datetag(ad))
105 l[1] = "%s%s" % (l[1][:-2], datetag(bd))
105 l[1] = "%s%s" % (l[1][:-2], datetag(bd))
106
106
107 for ln in xrange(len(l)):
107 for ln in xrange(len(l)):
108 if l[ln][-1] != '\n':
108 if l[ln][-1] != '\n':
109 l[ln] += "\n\ No newline at end of file\n"
109 l[ln] += "\n\ No newline at end of file\n"
110
110
111 if r:
111 if r:
112 l.insert(0, "diff %s %s\n" %
112 l.insert(0, "diff %s %s\n" %
113 (' '.join(["-r %s" % rev for rev in r]), fn))
113 (' '.join(["-r %s" % rev for rev in r]), fn1))
114
114
115 return "".join(l)
115 return "".join(l)
116
116
117 # somewhat self contained replacement for difflib.unified_diff
117 # somewhat self contained replacement for difflib.unified_diff
118 # t1 and t2 are the text to be diffed
118 # t1 and t2 are the text to be diffed
119 # l1 and l2 are the text broken up into lines
119 # l1 and l2 are the text broken up into lines
120 # header1 and header2 are the filenames for the diff output
120 # header1 and header2 are the filenames for the diff output
121 def bunidiff(t1, t2, l1, l2, header1, header2, opts=defaultopts):
121 def bunidiff(t1, t2, l1, l2, header1, header2, opts=defaultopts):
122 def contextend(l, len):
122 def contextend(l, len):
123 ret = l + opts.context
123 ret = l + opts.context
124 if ret > len:
124 if ret > len:
125 ret = len
125 ret = len
126 return ret
126 return ret
127
127
128 def contextstart(l):
128 def contextstart(l):
129 ret = l - opts.context
129 ret = l - opts.context
130 if ret < 0:
130 if ret < 0:
131 return 0
131 return 0
132 return ret
132 return ret
133
133
134 def yieldhunk(hunk, header):
134 def yieldhunk(hunk, header):
135 if header:
135 if header:
136 for x in header:
136 for x in header:
137 yield x
137 yield x
138 (astart, a2, bstart, b2, delta) = hunk
138 (astart, a2, bstart, b2, delta) = hunk
139 aend = contextend(a2, len(l1))
139 aend = contextend(a2, len(l1))
140 alen = aend - astart
140 alen = aend - astart
141 blen = b2 - bstart + aend - a2
141 blen = b2 - bstart + aend - a2
142
142
143 func = ""
143 func = ""
144 if opts.showfunc:
144 if opts.showfunc:
145 # walk backwards from the start of the context
145 # walk backwards from the start of the context
146 # to find a line starting with an alphanumeric char.
146 # to find a line starting with an alphanumeric char.
147 for x in xrange(astart, -1, -1):
147 for x in xrange(astart, -1, -1):
148 t = l1[x].rstrip()
148 t = l1[x].rstrip()
149 if funcre.match(t):
149 if funcre.match(t):
150 func = ' ' + t[:40]
150 func = ' ' + t[:40]
151 break
151 break
152
152
153 yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen,
153 yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen,
154 bstart + 1, blen, func)
154 bstart + 1, blen, func)
155 for x in delta:
155 for x in delta:
156 yield x
156 yield x
157 for x in xrange(a2, aend):
157 for x in xrange(a2, aend):
158 yield ' ' + l1[x]
158 yield ' ' + l1[x]
159
159
160 header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ]
160 header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ]
161
161
162 if opts.showfunc:
162 if opts.showfunc:
163 funcre = re.compile('\w')
163 funcre = re.compile('\w')
164
164
165 # bdiff.blocks gives us the matching sequences in the files. The loop
165 # bdiff.blocks gives us the matching sequences in the files. The loop
166 # below finds the spaces between those matching sequences and translates
166 # below finds the spaces between those matching sequences and translates
167 # them into diff output.
167 # them into diff output.
168 #
168 #
169 diff = bdiff.blocks(t1, t2)
169 diff = bdiff.blocks(t1, t2)
170 hunk = None
170 hunk = None
171 for i in xrange(len(diff)):
171 for i in xrange(len(diff)):
172 # The first match is special.
172 # The first match is special.
173 # we've either found a match starting at line 0 or a match later
173 # we've either found a match starting at line 0 or a match later
174 # in the file. If it starts later, old and new below will both be
174 # in the file. If it starts later, old and new below will both be
175 # empty and we'll continue to the next match.
175 # empty and we'll continue to the next match.
176 if i > 0:
176 if i > 0:
177 s = diff[i-1]
177 s = diff[i-1]
178 else:
178 else:
179 s = [0, 0, 0, 0]
179 s = [0, 0, 0, 0]
180 delta = []
180 delta = []
181 s1 = diff[i]
181 s1 = diff[i]
182 a1 = s[1]
182 a1 = s[1]
183 a2 = s1[0]
183 a2 = s1[0]
184 b1 = s[3]
184 b1 = s[3]
185 b2 = s1[2]
185 b2 = s1[2]
186
186
187 old = l1[a1:a2]
187 old = l1[a1:a2]
188 new = l2[b1:b2]
188 new = l2[b1:b2]
189
189
190 # bdiff sometimes gives huge matches past eof, this check eats them,
190 # bdiff sometimes gives huge matches past eof, this check eats them,
191 # and deals with the special first match case described above
191 # and deals with the special first match case described above
192 if not old and not new:
192 if not old and not new:
193 continue
193 continue
194
194
195 if opts.ignorews or opts.ignorewsamount or opts.ignoreblanklines:
195 if opts.ignorews or opts.ignorewsamount or opts.ignoreblanklines:
196 if wsclean(opts, "".join(old)) == wsclean(opts, "".join(new)):
196 if wsclean(opts, "".join(old)) == wsclean(opts, "".join(new)):
197 continue
197 continue
198
198
199 astart = contextstart(a1)
199 astart = contextstart(a1)
200 bstart = contextstart(b1)
200 bstart = contextstart(b1)
201 prev = None
201 prev = None
202 if hunk:
202 if hunk:
203 # join with the previous hunk if it falls inside the context
203 # join with the previous hunk if it falls inside the context
204 if astart < hunk[1] + opts.context + 1:
204 if astart < hunk[1] + opts.context + 1:
205 prev = hunk
205 prev = hunk
206 astart = hunk[1]
206 astart = hunk[1]
207 bstart = hunk[3]
207 bstart = hunk[3]
208 else:
208 else:
209 for x in yieldhunk(hunk, header):
209 for x in yieldhunk(hunk, header):
210 yield x
210 yield x
211 # we only want to yield the header if the files differ, and
211 # we only want to yield the header if the files differ, and
212 # we only want to yield it once.
212 # we only want to yield it once.
213 header = None
213 header = None
214 if prev:
214 if prev:
215 # we've joined the previous hunk, record the new ending points.
215 # we've joined the previous hunk, record the new ending points.
216 hunk[1] = a2
216 hunk[1] = a2
217 hunk[3] = b2
217 hunk[3] = b2
218 delta = hunk[4]
218 delta = hunk[4]
219 else:
219 else:
220 # create a new hunk
220 # create a new hunk
221 hunk = [ astart, a2, bstart, b2, delta ]
221 hunk = [ astart, a2, bstart, b2, delta ]
222
222
223 delta[len(delta):] = [ ' ' + x for x in l1[astart:a1] ]
223 delta[len(delta):] = [ ' ' + x for x in l1[astart:a1] ]
224 delta[len(delta):] = [ '-' + x for x in old ]
224 delta[len(delta):] = [ '-' + x for x in old ]
225 delta[len(delta):] = [ '+' + x for x in new ]
225 delta[len(delta):] = [ '+' + x for x in new ]
226
226
227 if hunk:
227 if hunk:
228 for x in yieldhunk(hunk, header):
228 for x in yieldhunk(hunk, header):
229 yield x
229 yield x
230
230
231 def patchtext(bin):
231 def patchtext(bin):
232 pos = 0
232 pos = 0
233 t = []
233 t = []
234 while pos < len(bin):
234 while pos < len(bin):
235 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
235 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
236 pos += 12
236 pos += 12
237 t.append(bin[pos:pos + l])
237 t.append(bin[pos:pos + l])
238 pos += l
238 pos += l
239 return "".join(t)
239 return "".join(t)
240
240
241 def patch(a, bin):
241 def patch(a, bin):
242 return mpatch.patches(a, [bin])
242 return mpatch.patches(a, [bin])
243
243
244 # similar to difflib.SequenceMatcher.get_matching_blocks
244 # similar to difflib.SequenceMatcher.get_matching_blocks
245 def get_matching_blocks(a, b):
245 def get_matching_blocks(a, b):
246 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
246 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
247
247
248 def trivialdiffheader(length):
248 def trivialdiffheader(length):
249 return struct.pack(">lll", 0, 0, length)
249 return struct.pack(">lll", 0, 0, length)
250
250
251 patches = mpatch.patches
251 patches = mpatch.patches
252 patchedsize = mpatch.patchedsize
252 patchedsize = mpatch.patchedsize
253 textdiff = bdiff.bdiff
253 textdiff = bdiff.bdiff
@@ -1,1348 +1,1348 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 from i18n import _
9 from i18n import _
10 from node import *
10 from node import *
11 import base85, cmdutil, mdiff, util, context, revlog, diffhelpers
11 import base85, cmdutil, mdiff, util, context, revlog, diffhelpers
12 import cStringIO, email.Parser, os, popen2, re, sha, errno
12 import cStringIO, email.Parser, os, popen2, re, sha, errno
13 import sys, tempfile, zlib
13 import sys, tempfile, zlib
14
14
15 class PatchError(Exception):
15 class PatchError(Exception):
16 pass
16 pass
17
17
18 class NoHunks(PatchError):
18 class NoHunks(PatchError):
19 pass
19 pass
20
20
21 # helper functions
21 # helper functions
22
22
23 def copyfile(src, dst, basedir=None):
23 def copyfile(src, dst, basedir=None):
24 if not basedir:
24 if not basedir:
25 basedir = os.getcwd()
25 basedir = os.getcwd()
26
26
27 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
27 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
28 if os.path.exists(absdst):
28 if os.path.exists(absdst):
29 raise util.Abort(_("cannot create %s: destination already exists") %
29 raise util.Abort(_("cannot create %s: destination already exists") %
30 dst)
30 dst)
31
31
32 targetdir = os.path.dirname(absdst)
32 targetdir = os.path.dirname(absdst)
33 if not os.path.isdir(targetdir):
33 if not os.path.isdir(targetdir):
34 os.makedirs(targetdir)
34 os.makedirs(targetdir)
35
35
36 util.copyfile(abssrc, absdst)
36 util.copyfile(abssrc, absdst)
37
37
38 # public functions
38 # public functions
39
39
40 def extract(ui, fileobj):
40 def extract(ui, fileobj):
41 '''extract patch from data read from fileobj.
41 '''extract patch from data read from fileobj.
42
42
43 patch can be a normal patch or contained in an email message.
43 patch can be a normal patch or contained in an email message.
44
44
45 return tuple (filename, message, user, date, node, p1, p2).
45 return tuple (filename, message, user, date, node, p1, p2).
46 Any item in the returned tuple can be None. If filename is None,
46 Any item in the returned tuple can be None. If filename is None,
47 fileobj did not contain a patch. Caller must unlink filename when done.'''
47 fileobj did not contain a patch. Caller must unlink filename when done.'''
48
48
49 # attempt to detect the start of a patch
49 # attempt to detect the start of a patch
50 # (this heuristic is borrowed from quilt)
50 # (this heuristic is borrowed from quilt)
51 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
51 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
52 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
52 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
53 '(---|\*\*\*)[ \t])', re.MULTILINE)
53 '(---|\*\*\*)[ \t])', re.MULTILINE)
54
54
55 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
55 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
56 tmpfp = os.fdopen(fd, 'w')
56 tmpfp = os.fdopen(fd, 'w')
57 try:
57 try:
58 msg = email.Parser.Parser().parse(fileobj)
58 msg = email.Parser.Parser().parse(fileobj)
59
59
60 subject = msg['Subject']
60 subject = msg['Subject']
61 user = msg['From']
61 user = msg['From']
62 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
62 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
63 # should try to parse msg['Date']
63 # should try to parse msg['Date']
64 date = None
64 date = None
65 nodeid = None
65 nodeid = None
66 branch = None
66 branch = None
67 parents = []
67 parents = []
68
68
69 if subject:
69 if subject:
70 if subject.startswith('[PATCH'):
70 if subject.startswith('[PATCH'):
71 pend = subject.find(']')
71 pend = subject.find(']')
72 if pend >= 0:
72 if pend >= 0:
73 subject = subject[pend+1:].lstrip()
73 subject = subject[pend+1:].lstrip()
74 subject = subject.replace('\n\t', ' ')
74 subject = subject.replace('\n\t', ' ')
75 ui.debug('Subject: %s\n' % subject)
75 ui.debug('Subject: %s\n' % subject)
76 if user:
76 if user:
77 ui.debug('From: %s\n' % user)
77 ui.debug('From: %s\n' % user)
78 diffs_seen = 0
78 diffs_seen = 0
79 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
79 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
80 message = ''
80 message = ''
81 for part in msg.walk():
81 for part in msg.walk():
82 content_type = part.get_content_type()
82 content_type = part.get_content_type()
83 ui.debug('Content-Type: %s\n' % content_type)
83 ui.debug('Content-Type: %s\n' % content_type)
84 if content_type not in ok_types:
84 if content_type not in ok_types:
85 continue
85 continue
86 payload = part.get_payload(decode=True)
86 payload = part.get_payload(decode=True)
87 m = diffre.search(payload)
87 m = diffre.search(payload)
88 if m:
88 if m:
89 hgpatch = False
89 hgpatch = False
90 ignoretext = False
90 ignoretext = False
91
91
92 ui.debug(_('found patch at byte %d\n') % m.start(0))
92 ui.debug(_('found patch at byte %d\n') % m.start(0))
93 diffs_seen += 1
93 diffs_seen += 1
94 cfp = cStringIO.StringIO()
94 cfp = cStringIO.StringIO()
95 for line in payload[:m.start(0)].splitlines():
95 for line in payload[:m.start(0)].splitlines():
96 if line.startswith('# HG changeset patch'):
96 if line.startswith('# HG changeset patch'):
97 ui.debug(_('patch generated by hg export\n'))
97 ui.debug(_('patch generated by hg export\n'))
98 hgpatch = True
98 hgpatch = True
99 # drop earlier commit message content
99 # drop earlier commit message content
100 cfp.seek(0)
100 cfp.seek(0)
101 cfp.truncate()
101 cfp.truncate()
102 subject = None
102 subject = None
103 elif hgpatch:
103 elif hgpatch:
104 if line.startswith('# User '):
104 if line.startswith('# User '):
105 user = line[7:]
105 user = line[7:]
106 ui.debug('From: %s\n' % user)
106 ui.debug('From: %s\n' % user)
107 elif line.startswith("# Date "):
107 elif line.startswith("# Date "):
108 date = line[7:]
108 date = line[7:]
109 elif line.startswith("# Branch "):
109 elif line.startswith("# Branch "):
110 branch = line[9:]
110 branch = line[9:]
111 elif line.startswith("# Node ID "):
111 elif line.startswith("# Node ID "):
112 nodeid = line[10:]
112 nodeid = line[10:]
113 elif line.startswith("# Parent "):
113 elif line.startswith("# Parent "):
114 parents.append(line[10:])
114 parents.append(line[10:])
115 elif line == '---' and gitsendmail:
115 elif line == '---' and gitsendmail:
116 ignoretext = True
116 ignoretext = True
117 if not line.startswith('# ') and not ignoretext:
117 if not line.startswith('# ') and not ignoretext:
118 cfp.write(line)
118 cfp.write(line)
119 cfp.write('\n')
119 cfp.write('\n')
120 message = cfp.getvalue()
120 message = cfp.getvalue()
121 if tmpfp:
121 if tmpfp:
122 tmpfp.write(payload)
122 tmpfp.write(payload)
123 if not payload.endswith('\n'):
123 if not payload.endswith('\n'):
124 tmpfp.write('\n')
124 tmpfp.write('\n')
125 elif not diffs_seen and message and content_type == 'text/plain':
125 elif not diffs_seen and message and content_type == 'text/plain':
126 message += '\n' + payload
126 message += '\n' + payload
127 except:
127 except:
128 tmpfp.close()
128 tmpfp.close()
129 os.unlink(tmpname)
129 os.unlink(tmpname)
130 raise
130 raise
131
131
132 if subject and not message.startswith(subject):
132 if subject and not message.startswith(subject):
133 message = '%s\n%s' % (subject, message)
133 message = '%s\n%s' % (subject, message)
134 tmpfp.close()
134 tmpfp.close()
135 if not diffs_seen:
135 if not diffs_seen:
136 os.unlink(tmpname)
136 os.unlink(tmpname)
137 return None, message, user, date, branch, None, None, None
137 return None, message, user, date, branch, None, None, None
138 p1 = parents and parents.pop(0) or None
138 p1 = parents and parents.pop(0) or None
139 p2 = parents and parents.pop(0) or None
139 p2 = parents and parents.pop(0) or None
140 return tmpname, message, user, date, branch, nodeid, p1, p2
140 return tmpname, message, user, date, branch, nodeid, p1, p2
141
141
142 GP_PATCH = 1 << 0 # we have to run patch
142 GP_PATCH = 1 << 0 # we have to run patch
143 GP_FILTER = 1 << 1 # there's some copy/rename operation
143 GP_FILTER = 1 << 1 # there's some copy/rename operation
144 GP_BINARY = 1 << 2 # there's a binary patch
144 GP_BINARY = 1 << 2 # there's a binary patch
145
145
146 def readgitpatch(fp, firstline=None):
146 def readgitpatch(fp, firstline=None):
147 """extract git-style metadata about patches from <patchname>"""
147 """extract git-style metadata about patches from <patchname>"""
148 class gitpatch:
148 class gitpatch:
149 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
149 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
150 def __init__(self, path):
150 def __init__(self, path):
151 self.path = path
151 self.path = path
152 self.oldpath = None
152 self.oldpath = None
153 self.mode = None
153 self.mode = None
154 self.op = 'MODIFY'
154 self.op = 'MODIFY'
155 self.lineno = 0
155 self.lineno = 0
156 self.binary = False
156 self.binary = False
157
157
158 def reader(fp, firstline):
158 def reader(fp, firstline):
159 if firstline is not None:
159 if firstline is not None:
160 yield firstline
160 yield firstline
161 for line in fp:
161 for line in fp:
162 yield line
162 yield line
163
163
164 # Filter patch for git information
164 # Filter patch for git information
165 gitre = re.compile('diff --git a/(.*) b/(.*)')
165 gitre = re.compile('diff --git a/(.*) b/(.*)')
166 gp = None
166 gp = None
167 gitpatches = []
167 gitpatches = []
168 # Can have a git patch with only metadata, causing patch to complain
168 # Can have a git patch with only metadata, causing patch to complain
169 dopatch = 0
169 dopatch = 0
170
170
171 lineno = 0
171 lineno = 0
172 for line in reader(fp, firstline):
172 for line in reader(fp, firstline):
173 lineno += 1
173 lineno += 1
174 if line.startswith('diff --git'):
174 if line.startswith('diff --git'):
175 m = gitre.match(line)
175 m = gitre.match(line)
176 if m:
176 if m:
177 if gp:
177 if gp:
178 gitpatches.append(gp)
178 gitpatches.append(gp)
179 src, dst = m.group(1, 2)
179 src, dst = m.group(1, 2)
180 gp = gitpatch(dst)
180 gp = gitpatch(dst)
181 gp.lineno = lineno
181 gp.lineno = lineno
182 elif gp:
182 elif gp:
183 if line.startswith('--- '):
183 if line.startswith('--- '):
184 if gp.op in ('COPY', 'RENAME'):
184 if gp.op in ('COPY', 'RENAME'):
185 dopatch |= GP_FILTER
185 dopatch |= GP_FILTER
186 gitpatches.append(gp)
186 gitpatches.append(gp)
187 gp = None
187 gp = None
188 dopatch |= GP_PATCH
188 dopatch |= GP_PATCH
189 continue
189 continue
190 if line.startswith('rename from '):
190 if line.startswith('rename from '):
191 gp.op = 'RENAME'
191 gp.op = 'RENAME'
192 gp.oldpath = line[12:].rstrip()
192 gp.oldpath = line[12:].rstrip()
193 elif line.startswith('rename to '):
193 elif line.startswith('rename to '):
194 gp.path = line[10:].rstrip()
194 gp.path = line[10:].rstrip()
195 elif line.startswith('copy from '):
195 elif line.startswith('copy from '):
196 gp.op = 'COPY'
196 gp.op = 'COPY'
197 gp.oldpath = line[10:].rstrip()
197 gp.oldpath = line[10:].rstrip()
198 elif line.startswith('copy to '):
198 elif line.startswith('copy to '):
199 gp.path = line[8:].rstrip()
199 gp.path = line[8:].rstrip()
200 elif line.startswith('deleted file'):
200 elif line.startswith('deleted file'):
201 gp.op = 'DELETE'
201 gp.op = 'DELETE'
202 elif line.startswith('new file mode '):
202 elif line.startswith('new file mode '):
203 gp.op = 'ADD'
203 gp.op = 'ADD'
204 gp.mode = int(line.rstrip()[-6:], 8)
204 gp.mode = int(line.rstrip()[-6:], 8)
205 elif line.startswith('new mode '):
205 elif line.startswith('new mode '):
206 gp.mode = int(line.rstrip()[-6:], 8)
206 gp.mode = int(line.rstrip()[-6:], 8)
207 elif line.startswith('GIT binary patch'):
207 elif line.startswith('GIT binary patch'):
208 dopatch |= GP_BINARY
208 dopatch |= GP_BINARY
209 gp.binary = True
209 gp.binary = True
210 if gp:
210 if gp:
211 gitpatches.append(gp)
211 gitpatches.append(gp)
212
212
213 if not gitpatches:
213 if not gitpatches:
214 dopatch = GP_PATCH
214 dopatch = GP_PATCH
215
215
216 return (dopatch, gitpatches)
216 return (dopatch, gitpatches)
217
217
218 def patch(patchname, ui, strip=1, cwd=None, files={}):
218 def patch(patchname, ui, strip=1, cwd=None, files={}):
219 """apply <patchname> to the working directory.
219 """apply <patchname> to the working directory.
220 returns whether patch was applied with fuzz factor."""
220 returns whether patch was applied with fuzz factor."""
221 patcher = ui.config('ui', 'patch')
221 patcher = ui.config('ui', 'patch')
222 args = []
222 args = []
223 try:
223 try:
224 if patcher:
224 if patcher:
225 return externalpatch(patcher, args, patchname, ui, strip, cwd,
225 return externalpatch(patcher, args, patchname, ui, strip, cwd,
226 files)
226 files)
227 else:
227 else:
228 try:
228 try:
229 return internalpatch(patchname, ui, strip, cwd, files)
229 return internalpatch(patchname, ui, strip, cwd, files)
230 except NoHunks:
230 except NoHunks:
231 patcher = util.find_exe('gpatch') or util.find_exe('patch')
231 patcher = util.find_exe('gpatch') or util.find_exe('patch')
232 ui.debug('no valid hunks found; trying with %r instead\n' %
232 ui.debug('no valid hunks found; trying with %r instead\n' %
233 patcher)
233 patcher)
234 if util.needbinarypatch():
234 if util.needbinarypatch():
235 args.append('--binary')
235 args.append('--binary')
236 return externalpatch(patcher, args, patchname, ui, strip, cwd,
236 return externalpatch(patcher, args, patchname, ui, strip, cwd,
237 files)
237 files)
238 except PatchError, err:
238 except PatchError, err:
239 s = str(err)
239 s = str(err)
240 if s:
240 if s:
241 raise util.Abort(s)
241 raise util.Abort(s)
242 else:
242 else:
243 raise util.Abort(_('patch failed to apply'))
243 raise util.Abort(_('patch failed to apply'))
244
244
245 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
245 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
246 """use <patcher> to apply <patchname> to the working directory.
246 """use <patcher> to apply <patchname> to the working directory.
247 returns whether patch was applied with fuzz factor."""
247 returns whether patch was applied with fuzz factor."""
248
248
249 fuzz = False
249 fuzz = False
250 if cwd:
250 if cwd:
251 args.append('-d %s' % util.shellquote(cwd))
251 args.append('-d %s' % util.shellquote(cwd))
252 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
252 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
253 util.shellquote(patchname)))
253 util.shellquote(patchname)))
254
254
255 for line in fp:
255 for line in fp:
256 line = line.rstrip()
256 line = line.rstrip()
257 ui.note(line + '\n')
257 ui.note(line + '\n')
258 if line.startswith('patching file '):
258 if line.startswith('patching file '):
259 pf = util.parse_patch_output(line)
259 pf = util.parse_patch_output(line)
260 printed_file = False
260 printed_file = False
261 files.setdefault(pf, (None, None))
261 files.setdefault(pf, (None, None))
262 elif line.find('with fuzz') >= 0:
262 elif line.find('with fuzz') >= 0:
263 fuzz = True
263 fuzz = True
264 if not printed_file:
264 if not printed_file:
265 ui.warn(pf + '\n')
265 ui.warn(pf + '\n')
266 printed_file = True
266 printed_file = True
267 ui.warn(line + '\n')
267 ui.warn(line + '\n')
268 elif line.find('saving rejects to file') >= 0:
268 elif line.find('saving rejects to file') >= 0:
269 ui.warn(line + '\n')
269 ui.warn(line + '\n')
270 elif line.find('FAILED') >= 0:
270 elif line.find('FAILED') >= 0:
271 if not printed_file:
271 if not printed_file:
272 ui.warn(pf + '\n')
272 ui.warn(pf + '\n')
273 printed_file = True
273 printed_file = True
274 ui.warn(line + '\n')
274 ui.warn(line + '\n')
275 code = fp.close()
275 code = fp.close()
276 if code:
276 if code:
277 raise PatchError(_("patch command failed: %s") %
277 raise PatchError(_("patch command failed: %s") %
278 util.explain_exit(code)[0])
278 util.explain_exit(code)[0])
279 return fuzz
279 return fuzz
280
280
281 def internalpatch(patchobj, ui, strip, cwd, files={}):
281 def internalpatch(patchobj, ui, strip, cwd, files={}):
282 """use builtin patch to apply <patchobj> to the working directory.
282 """use builtin patch to apply <patchobj> to the working directory.
283 returns whether patch was applied with fuzz factor."""
283 returns whether patch was applied with fuzz factor."""
284 try:
284 try:
285 fp = file(patchobj, 'rb')
285 fp = file(patchobj, 'rb')
286 except TypeError:
286 except TypeError:
287 fp = patchobj
287 fp = patchobj
288 if cwd:
288 if cwd:
289 curdir = os.getcwd()
289 curdir = os.getcwd()
290 os.chdir(cwd)
290 os.chdir(cwd)
291 try:
291 try:
292 ret = applydiff(ui, fp, files, strip=strip)
292 ret = applydiff(ui, fp, files, strip=strip)
293 finally:
293 finally:
294 if cwd:
294 if cwd:
295 os.chdir(curdir)
295 os.chdir(curdir)
296 if ret < 0:
296 if ret < 0:
297 raise PatchError
297 raise PatchError
298 return ret > 0
298 return ret > 0
299
299
300 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
300 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
301 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
301 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
302 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
302 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
303
303
304 class patchfile:
304 class patchfile:
305 def __init__(self, ui, fname):
305 def __init__(self, ui, fname):
306 self.fname = fname
306 self.fname = fname
307 self.ui = ui
307 self.ui = ui
308 try:
308 try:
309 fp = file(fname, 'rb')
309 fp = file(fname, 'rb')
310 self.lines = fp.readlines()
310 self.lines = fp.readlines()
311 self.exists = True
311 self.exists = True
312 except IOError:
312 except IOError:
313 dirname = os.path.dirname(fname)
313 dirname = os.path.dirname(fname)
314 if dirname and not os.path.isdir(dirname):
314 if dirname and not os.path.isdir(dirname):
315 dirs = dirname.split(os.path.sep)
315 dirs = dirname.split(os.path.sep)
316 d = ""
316 d = ""
317 for x in dirs:
317 for x in dirs:
318 d = os.path.join(d, x)
318 d = os.path.join(d, x)
319 if not os.path.isdir(d):
319 if not os.path.isdir(d):
320 os.mkdir(d)
320 os.mkdir(d)
321 self.lines = []
321 self.lines = []
322 self.exists = False
322 self.exists = False
323
323
324 self.hash = {}
324 self.hash = {}
325 self.dirty = 0
325 self.dirty = 0
326 self.offset = 0
326 self.offset = 0
327 self.rej = []
327 self.rej = []
328 self.fileprinted = False
328 self.fileprinted = False
329 self.printfile(False)
329 self.printfile(False)
330 self.hunks = 0
330 self.hunks = 0
331
331
332 def printfile(self, warn):
332 def printfile(self, warn):
333 if self.fileprinted:
333 if self.fileprinted:
334 return
334 return
335 if warn or self.ui.verbose:
335 if warn or self.ui.verbose:
336 self.fileprinted = True
336 self.fileprinted = True
337 s = _("patching file %s\n") % self.fname
337 s = _("patching file %s\n") % self.fname
338 if warn:
338 if warn:
339 self.ui.warn(s)
339 self.ui.warn(s)
340 else:
340 else:
341 self.ui.note(s)
341 self.ui.note(s)
342
342
343
343
344 def findlines(self, l, linenum):
344 def findlines(self, l, linenum):
345 # looks through the hash and finds candidate lines. The
345 # looks through the hash and finds candidate lines. The
346 # result is a list of line numbers sorted based on distance
346 # result is a list of line numbers sorted based on distance
347 # from linenum
347 # from linenum
348 def sorter(a, b):
348 def sorter(a, b):
349 vala = abs(a - linenum)
349 vala = abs(a - linenum)
350 valb = abs(b - linenum)
350 valb = abs(b - linenum)
351 return cmp(vala, valb)
351 return cmp(vala, valb)
352
352
353 try:
353 try:
354 cand = self.hash[l]
354 cand = self.hash[l]
355 except:
355 except:
356 return []
356 return []
357
357
358 if len(cand) > 1:
358 if len(cand) > 1:
359 # resort our list of potentials forward then back.
359 # resort our list of potentials forward then back.
360 cand.sort(cmp=sorter)
360 cand.sort(cmp=sorter)
361 return cand
361 return cand
362
362
363 def hashlines(self):
363 def hashlines(self):
364 self.hash = {}
364 self.hash = {}
365 for x in xrange(len(self.lines)):
365 for x in xrange(len(self.lines)):
366 s = self.lines[x]
366 s = self.lines[x]
367 self.hash.setdefault(s, []).append(x)
367 self.hash.setdefault(s, []).append(x)
368
368
369 def write_rej(self):
369 def write_rej(self):
370 # our rejects are a little different from patch(1). This always
370 # our rejects are a little different from patch(1). This always
371 # creates rejects in the same form as the original patch. A file
371 # creates rejects in the same form as the original patch. A file
372 # header is inserted so that you can run the reject through patch again
372 # header is inserted so that you can run the reject through patch again
373 # without having to type the filename.
373 # without having to type the filename.
374
374
375 if not self.rej:
375 if not self.rej:
376 return
376 return
377 if self.hunks != 1:
377 if self.hunks != 1:
378 hunkstr = "s"
378 hunkstr = "s"
379 else:
379 else:
380 hunkstr = ""
380 hunkstr = ""
381
381
382 fname = self.fname + ".rej"
382 fname = self.fname + ".rej"
383 self.ui.warn(
383 self.ui.warn(
384 _("%d out of %d hunk%s FAILED -- saving rejects to file %s\n") %
384 _("%d out of %d hunk%s FAILED -- saving rejects to file %s\n") %
385 (len(self.rej), self.hunks, hunkstr, fname))
385 (len(self.rej), self.hunks, hunkstr, fname))
386 try: os.unlink(fname)
386 try: os.unlink(fname)
387 except:
387 except:
388 pass
388 pass
389 fp = file(fname, 'wb')
389 fp = file(fname, 'wb')
390 base = os.path.basename(self.fname)
390 base = os.path.basename(self.fname)
391 fp.write("--- %s\n+++ %s\n" % (base, base))
391 fp.write("--- %s\n+++ %s\n" % (base, base))
392 for x in self.rej:
392 for x in self.rej:
393 for l in x.hunk:
393 for l in x.hunk:
394 fp.write(l)
394 fp.write(l)
395 if l[-1] != '\n':
395 if l[-1] != '\n':
396 fp.write("\n\ No newline at end of file\n")
396 fp.write("\n\ No newline at end of file\n")
397
397
398 def write(self, dest=None):
398 def write(self, dest=None):
399 if self.dirty:
399 if self.dirty:
400 if not dest:
400 if not dest:
401 dest = self.fname
401 dest = self.fname
402 st = None
402 st = None
403 try:
403 try:
404 st = os.lstat(dest)
404 st = os.lstat(dest)
405 except OSError, inst:
405 except OSError, inst:
406 if inst.errno != errno.ENOENT:
406 if inst.errno != errno.ENOENT:
407 raise
407 raise
408 if st and st.st_nlink > 1:
408 if st and st.st_nlink > 1:
409 os.unlink(dest)
409 os.unlink(dest)
410 fp = file(dest, 'wb')
410 fp = file(dest, 'wb')
411 if st and st.st_nlink > 1:
411 if st and st.st_nlink > 1:
412 os.chmod(dest, st.st_mode)
412 os.chmod(dest, st.st_mode)
413 fp.writelines(self.lines)
413 fp.writelines(self.lines)
414 fp.close()
414 fp.close()
415
415
416 def close(self):
416 def close(self):
417 self.write()
417 self.write()
418 self.write_rej()
418 self.write_rej()
419
419
420 def apply(self, h, reverse):
420 def apply(self, h, reverse):
421 if not h.complete():
421 if not h.complete():
422 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
422 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
423 (h.number, h.desc, len(h.a), h.lena, len(h.b),
423 (h.number, h.desc, len(h.a), h.lena, len(h.b),
424 h.lenb))
424 h.lenb))
425
425
426 self.hunks += 1
426 self.hunks += 1
427 if reverse:
427 if reverse:
428 h.reverse()
428 h.reverse()
429
429
430 if self.exists and h.createfile():
430 if self.exists and h.createfile():
431 self.ui.warn(_("file %s already exists\n") % self.fname)
431 self.ui.warn(_("file %s already exists\n") % self.fname)
432 self.rej.append(h)
432 self.rej.append(h)
433 return -1
433 return -1
434
434
435 if isinstance(h, binhunk):
435 if isinstance(h, binhunk):
436 if h.rmfile():
436 if h.rmfile():
437 os.unlink(self.fname)
437 os.unlink(self.fname)
438 else:
438 else:
439 self.lines[:] = h.new()
439 self.lines[:] = h.new()
440 self.offset += len(h.new())
440 self.offset += len(h.new())
441 self.dirty = 1
441 self.dirty = 1
442 return 0
442 return 0
443
443
444 # fast case first, no offsets, no fuzz
444 # fast case first, no offsets, no fuzz
445 old = h.old()
445 old = h.old()
446 # patch starts counting at 1 unless we are adding the file
446 # patch starts counting at 1 unless we are adding the file
447 if h.starta == 0:
447 if h.starta == 0:
448 start = 0
448 start = 0
449 else:
449 else:
450 start = h.starta + self.offset - 1
450 start = h.starta + self.offset - 1
451 orig_start = start
451 orig_start = start
452 if diffhelpers.testhunk(old, self.lines, start) == 0:
452 if diffhelpers.testhunk(old, self.lines, start) == 0:
453 if h.rmfile():
453 if h.rmfile():
454 os.unlink(self.fname)
454 os.unlink(self.fname)
455 else:
455 else:
456 self.lines[start : start + h.lena] = h.new()
456 self.lines[start : start + h.lena] = h.new()
457 self.offset += h.lenb - h.lena
457 self.offset += h.lenb - h.lena
458 self.dirty = 1
458 self.dirty = 1
459 return 0
459 return 0
460
460
461 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
461 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
462 self.hashlines()
462 self.hashlines()
463 if h.hunk[-1][0] != ' ':
463 if h.hunk[-1][0] != ' ':
464 # if the hunk tried to put something at the bottom of the file
464 # if the hunk tried to put something at the bottom of the file
465 # override the start line and use eof here
465 # override the start line and use eof here
466 search_start = len(self.lines)
466 search_start = len(self.lines)
467 else:
467 else:
468 search_start = orig_start
468 search_start = orig_start
469
469
470 for fuzzlen in xrange(3):
470 for fuzzlen in xrange(3):
471 for toponly in [ True, False ]:
471 for toponly in [ True, False ]:
472 old = h.old(fuzzlen, toponly)
472 old = h.old(fuzzlen, toponly)
473
473
474 cand = self.findlines(old[0][1:], search_start)
474 cand = self.findlines(old[0][1:], search_start)
475 for l in cand:
475 for l in cand:
476 if diffhelpers.testhunk(old, self.lines, l) == 0:
476 if diffhelpers.testhunk(old, self.lines, l) == 0:
477 newlines = h.new(fuzzlen, toponly)
477 newlines = h.new(fuzzlen, toponly)
478 self.lines[l : l + len(old)] = newlines
478 self.lines[l : l + len(old)] = newlines
479 self.offset += len(newlines) - len(old)
479 self.offset += len(newlines) - len(old)
480 self.dirty = 1
480 self.dirty = 1
481 if fuzzlen:
481 if fuzzlen:
482 fuzzstr = "with fuzz %d " % fuzzlen
482 fuzzstr = "with fuzz %d " % fuzzlen
483 f = self.ui.warn
483 f = self.ui.warn
484 self.printfile(True)
484 self.printfile(True)
485 else:
485 else:
486 fuzzstr = ""
486 fuzzstr = ""
487 f = self.ui.note
487 f = self.ui.note
488 offset = l - orig_start - fuzzlen
488 offset = l - orig_start - fuzzlen
489 if offset == 1:
489 if offset == 1:
490 linestr = "line"
490 linestr = "line"
491 else:
491 else:
492 linestr = "lines"
492 linestr = "lines"
493 f(_("Hunk #%d succeeded at %d %s(offset %d %s).\n") %
493 f(_("Hunk #%d succeeded at %d %s(offset %d %s).\n") %
494 (h.number, l+1, fuzzstr, offset, linestr))
494 (h.number, l+1, fuzzstr, offset, linestr))
495 return fuzzlen
495 return fuzzlen
496 self.printfile(True)
496 self.printfile(True)
497 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
497 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
498 self.rej.append(h)
498 self.rej.append(h)
499 return -1
499 return -1
500
500
501 class hunk:
501 class hunk:
502 def __init__(self, desc, num, lr, context):
502 def __init__(self, desc, num, lr, context):
503 self.number = num
503 self.number = num
504 self.desc = desc
504 self.desc = desc
505 self.hunk = [ desc ]
505 self.hunk = [ desc ]
506 self.a = []
506 self.a = []
507 self.b = []
507 self.b = []
508 if context:
508 if context:
509 self.read_context_hunk(lr)
509 self.read_context_hunk(lr)
510 else:
510 else:
511 self.read_unified_hunk(lr)
511 self.read_unified_hunk(lr)
512
512
513 def read_unified_hunk(self, lr):
513 def read_unified_hunk(self, lr):
514 m = unidesc.match(self.desc)
514 m = unidesc.match(self.desc)
515 if not m:
515 if not m:
516 raise PatchError(_("bad hunk #%d") % self.number)
516 raise PatchError(_("bad hunk #%d") % self.number)
517 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
517 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
518 if self.lena == None:
518 if self.lena == None:
519 self.lena = 1
519 self.lena = 1
520 else:
520 else:
521 self.lena = int(self.lena)
521 self.lena = int(self.lena)
522 if self.lenb == None:
522 if self.lenb == None:
523 self.lenb = 1
523 self.lenb = 1
524 else:
524 else:
525 self.lenb = int(self.lenb)
525 self.lenb = int(self.lenb)
526 self.starta = int(self.starta)
526 self.starta = int(self.starta)
527 self.startb = int(self.startb)
527 self.startb = int(self.startb)
528 diffhelpers.addlines(lr.fp, self.hunk, self.lena, self.lenb, self.a, self.b)
528 diffhelpers.addlines(lr.fp, self.hunk, self.lena, self.lenb, self.a, self.b)
529 # if we hit eof before finishing out the hunk, the last line will
529 # if we hit eof before finishing out the hunk, the last line will
530 # be zero length. Lets try to fix it up.
530 # be zero length. Lets try to fix it up.
531 while len(self.hunk[-1]) == 0:
531 while len(self.hunk[-1]) == 0:
532 del self.hunk[-1]
532 del self.hunk[-1]
533 del self.a[-1]
533 del self.a[-1]
534 del self.b[-1]
534 del self.b[-1]
535 self.lena -= 1
535 self.lena -= 1
536 self.lenb -= 1
536 self.lenb -= 1
537
537
538 def read_context_hunk(self, lr):
538 def read_context_hunk(self, lr):
539 self.desc = lr.readline()
539 self.desc = lr.readline()
540 m = contextdesc.match(self.desc)
540 m = contextdesc.match(self.desc)
541 if not m:
541 if not m:
542 raise PatchError(_("bad hunk #%d") % self.number)
542 raise PatchError(_("bad hunk #%d") % self.number)
543 foo, self.starta, foo2, aend, foo3 = m.groups()
543 foo, self.starta, foo2, aend, foo3 = m.groups()
544 self.starta = int(self.starta)
544 self.starta = int(self.starta)
545 if aend == None:
545 if aend == None:
546 aend = self.starta
546 aend = self.starta
547 self.lena = int(aend) - self.starta
547 self.lena = int(aend) - self.starta
548 if self.starta:
548 if self.starta:
549 self.lena += 1
549 self.lena += 1
550 for x in xrange(self.lena):
550 for x in xrange(self.lena):
551 l = lr.readline()
551 l = lr.readline()
552 if l.startswith('---'):
552 if l.startswith('---'):
553 lr.push(l)
553 lr.push(l)
554 break
554 break
555 s = l[2:]
555 s = l[2:]
556 if l.startswith('- ') or l.startswith('! '):
556 if l.startswith('- ') or l.startswith('! '):
557 u = '-' + s
557 u = '-' + s
558 elif l.startswith(' '):
558 elif l.startswith(' '):
559 u = ' ' + s
559 u = ' ' + s
560 else:
560 else:
561 raise PatchError(_("bad hunk #%d old text line %d") %
561 raise PatchError(_("bad hunk #%d old text line %d") %
562 (self.number, x))
562 (self.number, x))
563 self.a.append(u)
563 self.a.append(u)
564 self.hunk.append(u)
564 self.hunk.append(u)
565
565
566 l = lr.readline()
566 l = lr.readline()
567 if l.startswith('\ '):
567 if l.startswith('\ '):
568 s = self.a[-1][:-1]
568 s = self.a[-1][:-1]
569 self.a[-1] = s
569 self.a[-1] = s
570 self.hunk[-1] = s
570 self.hunk[-1] = s
571 l = lr.readline()
571 l = lr.readline()
572 m = contextdesc.match(l)
572 m = contextdesc.match(l)
573 if not m:
573 if not m:
574 raise PatchError(_("bad hunk #%d") % self.number)
574 raise PatchError(_("bad hunk #%d") % self.number)
575 foo, self.startb, foo2, bend, foo3 = m.groups()
575 foo, self.startb, foo2, bend, foo3 = m.groups()
576 self.startb = int(self.startb)
576 self.startb = int(self.startb)
577 if bend == None:
577 if bend == None:
578 bend = self.startb
578 bend = self.startb
579 self.lenb = int(bend) - self.startb
579 self.lenb = int(bend) - self.startb
580 if self.startb:
580 if self.startb:
581 self.lenb += 1
581 self.lenb += 1
582 hunki = 1
582 hunki = 1
583 for x in xrange(self.lenb):
583 for x in xrange(self.lenb):
584 l = lr.readline()
584 l = lr.readline()
585 if l.startswith('\ '):
585 if l.startswith('\ '):
586 s = self.b[-1][:-1]
586 s = self.b[-1][:-1]
587 self.b[-1] = s
587 self.b[-1] = s
588 self.hunk[hunki-1] = s
588 self.hunk[hunki-1] = s
589 continue
589 continue
590 if not l:
590 if not l:
591 lr.push(l)
591 lr.push(l)
592 break
592 break
593 s = l[2:]
593 s = l[2:]
594 if l.startswith('+ ') or l.startswith('! '):
594 if l.startswith('+ ') or l.startswith('! '):
595 u = '+' + s
595 u = '+' + s
596 elif l.startswith(' '):
596 elif l.startswith(' '):
597 u = ' ' + s
597 u = ' ' + s
598 elif len(self.b) == 0:
598 elif len(self.b) == 0:
599 # this can happen when the hunk does not add any lines
599 # this can happen when the hunk does not add any lines
600 lr.push(l)
600 lr.push(l)
601 break
601 break
602 else:
602 else:
603 raise PatchError(_("bad hunk #%d old text line %d") %
603 raise PatchError(_("bad hunk #%d old text line %d") %
604 (self.number, x))
604 (self.number, x))
605 self.b.append(s)
605 self.b.append(s)
606 while True:
606 while True:
607 if hunki >= len(self.hunk):
607 if hunki >= len(self.hunk):
608 h = ""
608 h = ""
609 else:
609 else:
610 h = self.hunk[hunki]
610 h = self.hunk[hunki]
611 hunki += 1
611 hunki += 1
612 if h == u:
612 if h == u:
613 break
613 break
614 elif h.startswith('-'):
614 elif h.startswith('-'):
615 continue
615 continue
616 else:
616 else:
617 self.hunk.insert(hunki-1, u)
617 self.hunk.insert(hunki-1, u)
618 break
618 break
619
619
620 if not self.a:
620 if not self.a:
621 # this happens when lines were only added to the hunk
621 # this happens when lines were only added to the hunk
622 for x in self.hunk:
622 for x in self.hunk:
623 if x.startswith('-') or x.startswith(' '):
623 if x.startswith('-') or x.startswith(' '):
624 self.a.append(x)
624 self.a.append(x)
625 if not self.b:
625 if not self.b:
626 # this happens when lines were only deleted from the hunk
626 # this happens when lines were only deleted from the hunk
627 for x in self.hunk:
627 for x in self.hunk:
628 if x.startswith('+') or x.startswith(' '):
628 if x.startswith('+') or x.startswith(' '):
629 self.b.append(x[1:])
629 self.b.append(x[1:])
630 # @@ -start,len +start,len @@
630 # @@ -start,len +start,len @@
631 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
631 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
632 self.startb, self.lenb)
632 self.startb, self.lenb)
633 self.hunk[0] = self.desc
633 self.hunk[0] = self.desc
634
634
635 def reverse(self):
635 def reverse(self):
636 origlena = self.lena
636 origlena = self.lena
637 origstarta = self.starta
637 origstarta = self.starta
638 self.lena = self.lenb
638 self.lena = self.lenb
639 self.starta = self.startb
639 self.starta = self.startb
640 self.lenb = origlena
640 self.lenb = origlena
641 self.startb = origstarta
641 self.startb = origstarta
642 self.a = []
642 self.a = []
643 self.b = []
643 self.b = []
644 # self.hunk[0] is the @@ description
644 # self.hunk[0] is the @@ description
645 for x in xrange(1, len(self.hunk)):
645 for x in xrange(1, len(self.hunk)):
646 o = self.hunk[x]
646 o = self.hunk[x]
647 if o.startswith('-'):
647 if o.startswith('-'):
648 n = '+' + o[1:]
648 n = '+' + o[1:]
649 self.b.append(o[1:])
649 self.b.append(o[1:])
650 elif o.startswith('+'):
650 elif o.startswith('+'):
651 n = '-' + o[1:]
651 n = '-' + o[1:]
652 self.a.append(n)
652 self.a.append(n)
653 else:
653 else:
654 n = o
654 n = o
655 self.b.append(o[1:])
655 self.b.append(o[1:])
656 self.a.append(o)
656 self.a.append(o)
657 self.hunk[x] = o
657 self.hunk[x] = o
658
658
659 def fix_newline(self):
659 def fix_newline(self):
660 diffhelpers.fix_newline(self.hunk, self.a, self.b)
660 diffhelpers.fix_newline(self.hunk, self.a, self.b)
661
661
662 def complete(self):
662 def complete(self):
663 return len(self.a) == self.lena and len(self.b) == self.lenb
663 return len(self.a) == self.lena and len(self.b) == self.lenb
664
664
665 def createfile(self):
665 def createfile(self):
666 return self.starta == 0 and self.lena == 0
666 return self.starta == 0 and self.lena == 0
667
667
668 def rmfile(self):
668 def rmfile(self):
669 return self.startb == 0 and self.lenb == 0
669 return self.startb == 0 and self.lenb == 0
670
670
671 def fuzzit(self, l, fuzz, toponly):
671 def fuzzit(self, l, fuzz, toponly):
672 # this removes context lines from the top and bottom of list 'l'. It
672 # this removes context lines from the top and bottom of list 'l'. It
673 # checks the hunk to make sure only context lines are removed, and then
673 # checks the hunk to make sure only context lines are removed, and then
674 # returns a new shortened list of lines.
674 # returns a new shortened list of lines.
675 fuzz = min(fuzz, len(l)-1)
675 fuzz = min(fuzz, len(l)-1)
676 if fuzz:
676 if fuzz:
677 top = 0
677 top = 0
678 bot = 0
678 bot = 0
679 hlen = len(self.hunk)
679 hlen = len(self.hunk)
680 for x in xrange(hlen-1):
680 for x in xrange(hlen-1):
681 # the hunk starts with the @@ line, so use x+1
681 # the hunk starts with the @@ line, so use x+1
682 if self.hunk[x+1][0] == ' ':
682 if self.hunk[x+1][0] == ' ':
683 top += 1
683 top += 1
684 else:
684 else:
685 break
685 break
686 if not toponly:
686 if not toponly:
687 for x in xrange(hlen-1):
687 for x in xrange(hlen-1):
688 if self.hunk[hlen-bot-1][0] == ' ':
688 if self.hunk[hlen-bot-1][0] == ' ':
689 bot += 1
689 bot += 1
690 else:
690 else:
691 break
691 break
692
692
693 # top and bot now count context in the hunk
693 # top and bot now count context in the hunk
694 # adjust them if either one is short
694 # adjust them if either one is short
695 context = max(top, bot, 3)
695 context = max(top, bot, 3)
696 if bot < context:
696 if bot < context:
697 bot = max(0, fuzz - (context - bot))
697 bot = max(0, fuzz - (context - bot))
698 else:
698 else:
699 bot = min(fuzz, bot)
699 bot = min(fuzz, bot)
700 if top < context:
700 if top < context:
701 top = max(0, fuzz - (context - top))
701 top = max(0, fuzz - (context - top))
702 else:
702 else:
703 top = min(fuzz, top)
703 top = min(fuzz, top)
704
704
705 return l[top:len(l)-bot]
705 return l[top:len(l)-bot]
706 return l
706 return l
707
707
708 def old(self, fuzz=0, toponly=False):
708 def old(self, fuzz=0, toponly=False):
709 return self.fuzzit(self.a, fuzz, toponly)
709 return self.fuzzit(self.a, fuzz, toponly)
710
710
711 def newctrl(self):
711 def newctrl(self):
712 res = []
712 res = []
713 for x in self.hunk:
713 for x in self.hunk:
714 c = x[0]
714 c = x[0]
715 if c == ' ' or c == '+':
715 if c == ' ' or c == '+':
716 res.append(x)
716 res.append(x)
717 return res
717 return res
718
718
719 def new(self, fuzz=0, toponly=False):
719 def new(self, fuzz=0, toponly=False):
720 return self.fuzzit(self.b, fuzz, toponly)
720 return self.fuzzit(self.b, fuzz, toponly)
721
721
722 class binhunk:
722 class binhunk:
723 'A binary patch file. Only understands literals so far.'
723 'A binary patch file. Only understands literals so far.'
724 def __init__(self, gitpatch):
724 def __init__(self, gitpatch):
725 self.gitpatch = gitpatch
725 self.gitpatch = gitpatch
726 self.text = None
726 self.text = None
727 self.hunk = ['GIT binary patch\n']
727 self.hunk = ['GIT binary patch\n']
728
728
729 def createfile(self):
729 def createfile(self):
730 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
730 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
731
731
732 def rmfile(self):
732 def rmfile(self):
733 return self.gitpatch.op == 'DELETE'
733 return self.gitpatch.op == 'DELETE'
734
734
735 def complete(self):
735 def complete(self):
736 return self.text is not None
736 return self.text is not None
737
737
738 def new(self):
738 def new(self):
739 return [self.text]
739 return [self.text]
740
740
741 def extract(self, fp):
741 def extract(self, fp):
742 line = fp.readline()
742 line = fp.readline()
743 self.hunk.append(line)
743 self.hunk.append(line)
744 while line and not line.startswith('literal '):
744 while line and not line.startswith('literal '):
745 line = fp.readline()
745 line = fp.readline()
746 self.hunk.append(line)
746 self.hunk.append(line)
747 if not line:
747 if not line:
748 raise PatchError(_('could not extract binary patch'))
748 raise PatchError(_('could not extract binary patch'))
749 size = int(line[8:].rstrip())
749 size = int(line[8:].rstrip())
750 dec = []
750 dec = []
751 line = fp.readline()
751 line = fp.readline()
752 self.hunk.append(line)
752 self.hunk.append(line)
753 while len(line) > 1:
753 while len(line) > 1:
754 l = line[0]
754 l = line[0]
755 if l <= 'Z' and l >= 'A':
755 if l <= 'Z' and l >= 'A':
756 l = ord(l) - ord('A') + 1
756 l = ord(l) - ord('A') + 1
757 else:
757 else:
758 l = ord(l) - ord('a') + 27
758 l = ord(l) - ord('a') + 27
759 dec.append(base85.b85decode(line[1:-1])[:l])
759 dec.append(base85.b85decode(line[1:-1])[:l])
760 line = fp.readline()
760 line = fp.readline()
761 self.hunk.append(line)
761 self.hunk.append(line)
762 text = zlib.decompress(''.join(dec))
762 text = zlib.decompress(''.join(dec))
763 if len(text) != size:
763 if len(text) != size:
764 raise PatchError(_('binary patch is %d bytes, not %d') %
764 raise PatchError(_('binary patch is %d bytes, not %d') %
765 len(text), size)
765 len(text), size)
766 self.text = text
766 self.text = text
767
767
768 def parsefilename(str):
768 def parsefilename(str):
769 # --- filename \t|space stuff
769 # --- filename \t|space stuff
770 s = str[4:]
770 s = str[4:]
771 i = s.find('\t')
771 i = s.find('\t')
772 if i < 0:
772 if i < 0:
773 i = s.find(' ')
773 i = s.find(' ')
774 if i < 0:
774 if i < 0:
775 return s
775 return s
776 return s[:i]
776 return s[:i]
777
777
778 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
778 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
779 def pathstrip(path, count=1):
779 def pathstrip(path, count=1):
780 pathlen = len(path)
780 pathlen = len(path)
781 i = 0
781 i = 0
782 if count == 0:
782 if count == 0:
783 return path.rstrip()
783 return path.rstrip()
784 while count > 0:
784 while count > 0:
785 i = path.find('/', i)
785 i = path.find('/', i)
786 if i == -1:
786 if i == -1:
787 raise PatchError(_("unable to strip away %d dirs from %s") %
787 raise PatchError(_("unable to strip away %d dirs from %s") %
788 (count, path))
788 (count, path))
789 i += 1
789 i += 1
790 # consume '//' in the path
790 # consume '//' in the path
791 while i < pathlen - 1 and path[i] == '/':
791 while i < pathlen - 1 and path[i] == '/':
792 i += 1
792 i += 1
793 count -= 1
793 count -= 1
794 return path[i:].rstrip()
794 return path[i:].rstrip()
795
795
796 nulla = afile_orig == "/dev/null"
796 nulla = afile_orig == "/dev/null"
797 nullb = bfile_orig == "/dev/null"
797 nullb = bfile_orig == "/dev/null"
798 afile = pathstrip(afile_orig, strip)
798 afile = pathstrip(afile_orig, strip)
799 gooda = os.path.exists(afile) and not nulla
799 gooda = os.path.exists(afile) and not nulla
800 bfile = pathstrip(bfile_orig, strip)
800 bfile = pathstrip(bfile_orig, strip)
801 if afile == bfile:
801 if afile == bfile:
802 goodb = gooda
802 goodb = gooda
803 else:
803 else:
804 goodb = os.path.exists(bfile) and not nullb
804 goodb = os.path.exists(bfile) and not nullb
805 createfunc = hunk.createfile
805 createfunc = hunk.createfile
806 if reverse:
806 if reverse:
807 createfunc = hunk.rmfile
807 createfunc = hunk.rmfile
808 if not goodb and not gooda and not createfunc():
808 if not goodb and not gooda and not createfunc():
809 raise PatchError(_("unable to find %s or %s for patching") %
809 raise PatchError(_("unable to find %s or %s for patching") %
810 (afile, bfile))
810 (afile, bfile))
811 if gooda and goodb:
811 if gooda and goodb:
812 fname = bfile
812 fname = bfile
813 if afile in bfile:
813 if afile in bfile:
814 fname = afile
814 fname = afile
815 elif gooda:
815 elif gooda:
816 fname = afile
816 fname = afile
817 elif not nullb:
817 elif not nullb:
818 fname = bfile
818 fname = bfile
819 if afile in bfile:
819 if afile in bfile:
820 fname = afile
820 fname = afile
821 elif not nulla:
821 elif not nulla:
822 fname = afile
822 fname = afile
823 return fname
823 return fname
824
824
825 class linereader:
825 class linereader:
826 # simple class to allow pushing lines back into the input stream
826 # simple class to allow pushing lines back into the input stream
827 def __init__(self, fp):
827 def __init__(self, fp):
828 self.fp = fp
828 self.fp = fp
829 self.buf = []
829 self.buf = []
830
830
831 def push(self, line):
831 def push(self, line):
832 self.buf.append(line)
832 self.buf.append(line)
833
833
834 def readline(self):
834 def readline(self):
835 if self.buf:
835 if self.buf:
836 l = self.buf[0]
836 l = self.buf[0]
837 del self.buf[0]
837 del self.buf[0]
838 return l
838 return l
839 return self.fp.readline()
839 return self.fp.readline()
840
840
841 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
841 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
842 rejmerge=None, updatedir=None):
842 rejmerge=None, updatedir=None):
843 """reads a patch from fp and tries to apply it. The dict 'changed' is
843 """reads a patch from fp and tries to apply it. The dict 'changed' is
844 filled in with all of the filenames changed by the patch. Returns 0
844 filled in with all of the filenames changed by the patch. Returns 0
845 for a clean patch, -1 if any rejects were found and 1 if there was
845 for a clean patch, -1 if any rejects were found and 1 if there was
846 any fuzz."""
846 any fuzz."""
847
847
848 def scangitpatch(fp, firstline, cwd=None):
848 def scangitpatch(fp, firstline, cwd=None):
849 '''git patches can modify a file, then copy that file to
849 '''git patches can modify a file, then copy that file to
850 a new file, but expect the source to be the unmodified form.
850 a new file, but expect the source to be the unmodified form.
851 So we scan the patch looking for that case so we can do
851 So we scan the patch looking for that case so we can do
852 the copies ahead of time.'''
852 the copies ahead of time.'''
853
853
854 pos = 0
854 pos = 0
855 try:
855 try:
856 pos = fp.tell()
856 pos = fp.tell()
857 except IOError:
857 except IOError:
858 fp = cStringIO.StringIO(fp.read())
858 fp = cStringIO.StringIO(fp.read())
859
859
860 (dopatch, gitpatches) = readgitpatch(fp, firstline)
860 (dopatch, gitpatches) = readgitpatch(fp, firstline)
861 for gp in gitpatches:
861 for gp in gitpatches:
862 if gp.op in ('COPY', 'RENAME'):
862 if gp.op in ('COPY', 'RENAME'):
863 copyfile(gp.oldpath, gp.path, basedir=cwd)
863 copyfile(gp.oldpath, gp.path, basedir=cwd)
864
864
865 fp.seek(pos)
865 fp.seek(pos)
866
866
867 return fp, dopatch, gitpatches
867 return fp, dopatch, gitpatches
868
868
869 current_hunk = None
869 current_hunk = None
870 current_file = None
870 current_file = None
871 afile = ""
871 afile = ""
872 bfile = ""
872 bfile = ""
873 state = None
873 state = None
874 hunknum = 0
874 hunknum = 0
875 rejects = 0
875 rejects = 0
876
876
877 git = False
877 git = False
878 gitre = re.compile('diff --git (a/.*) (b/.*)')
878 gitre = re.compile('diff --git (a/.*) (b/.*)')
879
879
880 # our states
880 # our states
881 BFILE = 1
881 BFILE = 1
882 err = 0
882 err = 0
883 context = None
883 context = None
884 lr = linereader(fp)
884 lr = linereader(fp)
885 dopatch = True
885 dopatch = True
886 gitworkdone = False
886 gitworkdone = False
887
887
888 while True:
888 while True:
889 newfile = False
889 newfile = False
890 x = lr.readline()
890 x = lr.readline()
891 if not x:
891 if not x:
892 break
892 break
893 if current_hunk:
893 if current_hunk:
894 if x.startswith('\ '):
894 if x.startswith('\ '):
895 current_hunk.fix_newline()
895 current_hunk.fix_newline()
896 ret = current_file.apply(current_hunk, reverse)
896 ret = current_file.apply(current_hunk, reverse)
897 if ret >= 0:
897 if ret >= 0:
898 changed.setdefault(current_file.fname, (None, None))
898 changed.setdefault(current_file.fname, (None, None))
899 if ret > 0:
899 if ret > 0:
900 err = 1
900 err = 1
901 current_hunk = None
901 current_hunk = None
902 gitworkdone = False
902 gitworkdone = False
903 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
903 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
904 ((context or context == None) and x.startswith('***************')))):
904 ((context or context == None) and x.startswith('***************')))):
905 try:
905 try:
906 if context == None and x.startswith('***************'):
906 if context == None and x.startswith('***************'):
907 context = True
907 context = True
908 current_hunk = hunk(x, hunknum + 1, lr, context)
908 current_hunk = hunk(x, hunknum + 1, lr, context)
909 except PatchError, err:
909 except PatchError, err:
910 ui.debug(err)
910 ui.debug(err)
911 current_hunk = None
911 current_hunk = None
912 continue
912 continue
913 hunknum += 1
913 hunknum += 1
914 if not current_file:
914 if not current_file:
915 if sourcefile:
915 if sourcefile:
916 current_file = patchfile(ui, sourcefile)
916 current_file = patchfile(ui, sourcefile)
917 else:
917 else:
918 current_file = selectfile(afile, bfile, current_hunk,
918 current_file = selectfile(afile, bfile, current_hunk,
919 strip, reverse)
919 strip, reverse)
920 current_file = patchfile(ui, current_file)
920 current_file = patchfile(ui, current_file)
921 elif state == BFILE and x.startswith('GIT binary patch'):
921 elif state == BFILE and x.startswith('GIT binary patch'):
922 current_hunk = binhunk(changed[bfile[2:]][1])
922 current_hunk = binhunk(changed[bfile[2:]][1])
923 if not current_file:
923 if not current_file:
924 if sourcefile:
924 if sourcefile:
925 current_file = patchfile(ui, sourcefile)
925 current_file = patchfile(ui, sourcefile)
926 else:
926 else:
927 current_file = selectfile(afile, bfile, current_hunk,
927 current_file = selectfile(afile, bfile, current_hunk,
928 strip, reverse)
928 strip, reverse)
929 current_file = patchfile(ui, current_file)
929 current_file = patchfile(ui, current_file)
930 hunknum += 1
930 hunknum += 1
931 current_hunk.extract(fp)
931 current_hunk.extract(fp)
932 elif x.startswith('diff --git'):
932 elif x.startswith('diff --git'):
933 # check for git diff, scanning the whole patch file if needed
933 # check for git diff, scanning the whole patch file if needed
934 m = gitre.match(x)
934 m = gitre.match(x)
935 if m:
935 if m:
936 afile, bfile = m.group(1, 2)
936 afile, bfile = m.group(1, 2)
937 if not git:
937 if not git:
938 git = True
938 git = True
939 fp, dopatch, gitpatches = scangitpatch(fp, x)
939 fp, dopatch, gitpatches = scangitpatch(fp, x)
940 for gp in gitpatches:
940 for gp in gitpatches:
941 changed[gp.path] = (gp.op, gp)
941 changed[gp.path] = (gp.op, gp)
942 # else error?
942 # else error?
943 # copy/rename + modify should modify target, not source
943 # copy/rename + modify should modify target, not source
944 if changed.get(bfile[2:], (None, None))[0] in ('COPY',
944 if changed.get(bfile[2:], (None, None))[0] in ('COPY',
945 'RENAME'):
945 'RENAME'):
946 afile = bfile
946 afile = bfile
947 gitworkdone = True
947 gitworkdone = True
948 newfile = True
948 newfile = True
949 elif x.startswith('---'):
949 elif x.startswith('---'):
950 # check for a unified diff
950 # check for a unified diff
951 l2 = lr.readline()
951 l2 = lr.readline()
952 if not l2.startswith('+++'):
952 if not l2.startswith('+++'):
953 lr.push(l2)
953 lr.push(l2)
954 continue
954 continue
955 newfile = True
955 newfile = True
956 context = False
956 context = False
957 afile = parsefilename(x)
957 afile = parsefilename(x)
958 bfile = parsefilename(l2)
958 bfile = parsefilename(l2)
959 elif x.startswith('***'):
959 elif x.startswith('***'):
960 # check for a context diff
960 # check for a context diff
961 l2 = lr.readline()
961 l2 = lr.readline()
962 if not l2.startswith('---'):
962 if not l2.startswith('---'):
963 lr.push(l2)
963 lr.push(l2)
964 continue
964 continue
965 l3 = lr.readline()
965 l3 = lr.readline()
966 lr.push(l3)
966 lr.push(l3)
967 if not l3.startswith("***************"):
967 if not l3.startswith("***************"):
968 lr.push(l2)
968 lr.push(l2)
969 continue
969 continue
970 newfile = True
970 newfile = True
971 context = True
971 context = True
972 afile = parsefilename(x)
972 afile = parsefilename(x)
973 bfile = parsefilename(l2)
973 bfile = parsefilename(l2)
974
974
975 if newfile:
975 if newfile:
976 if current_file:
976 if current_file:
977 current_file.close()
977 current_file.close()
978 if rejmerge:
978 if rejmerge:
979 rejmerge(current_file)
979 rejmerge(current_file)
980 rejects += len(current_file.rej)
980 rejects += len(current_file.rej)
981 state = BFILE
981 state = BFILE
982 current_file = None
982 current_file = None
983 hunknum = 0
983 hunknum = 0
984 if current_hunk:
984 if current_hunk:
985 if current_hunk.complete():
985 if current_hunk.complete():
986 ret = current_file.apply(current_hunk, reverse)
986 ret = current_file.apply(current_hunk, reverse)
987 if ret >= 0:
987 if ret >= 0:
988 changed.setdefault(current_file.fname, (None, None))
988 changed.setdefault(current_file.fname, (None, None))
989 if ret > 0:
989 if ret > 0:
990 err = 1
990 err = 1
991 else:
991 else:
992 fname = current_file and current_file.fname or None
992 fname = current_file and current_file.fname or None
993 raise PatchError(_("malformed patch %s %s") % (fname,
993 raise PatchError(_("malformed patch %s %s") % (fname,
994 current_hunk.desc))
994 current_hunk.desc))
995 if current_file:
995 if current_file:
996 current_file.close()
996 current_file.close()
997 if rejmerge:
997 if rejmerge:
998 rejmerge(current_file)
998 rejmerge(current_file)
999 rejects += len(current_file.rej)
999 rejects += len(current_file.rej)
1000 if updatedir and git:
1000 if updatedir and git:
1001 updatedir(gitpatches)
1001 updatedir(gitpatches)
1002 if rejects:
1002 if rejects:
1003 return -1
1003 return -1
1004 if hunknum == 0 and dopatch and not gitworkdone:
1004 if hunknum == 0 and dopatch and not gitworkdone:
1005 raise NoHunks
1005 raise NoHunks
1006 return err
1006 return err
1007
1007
1008 def diffopts(ui, opts={}, untrusted=False):
1008 def diffopts(ui, opts={}, untrusted=False):
1009 def get(key, name=None):
1009 def get(key, name=None):
1010 return (opts.get(key) or
1010 return (opts.get(key) or
1011 ui.configbool('diff', name or key, None, untrusted=untrusted))
1011 ui.configbool('diff', name or key, None, untrusted=untrusted))
1012 return mdiff.diffopts(
1012 return mdiff.diffopts(
1013 text=opts.get('text'),
1013 text=opts.get('text'),
1014 git=get('git'),
1014 git=get('git'),
1015 nodates=get('nodates'),
1015 nodates=get('nodates'),
1016 showfunc=get('show_function', 'showfunc'),
1016 showfunc=get('show_function', 'showfunc'),
1017 ignorews=get('ignore_all_space', 'ignorews'),
1017 ignorews=get('ignore_all_space', 'ignorews'),
1018 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1018 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1019 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
1019 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
1020
1020
1021 def updatedir(ui, repo, patches):
1021 def updatedir(ui, repo, patches):
1022 '''Update dirstate after patch application according to metadata'''
1022 '''Update dirstate after patch application according to metadata'''
1023 if not patches:
1023 if not patches:
1024 return
1024 return
1025 copies = []
1025 copies = []
1026 removes = {}
1026 removes = {}
1027 cfiles = patches.keys()
1027 cfiles = patches.keys()
1028 cwd = repo.getcwd()
1028 cwd = repo.getcwd()
1029 if cwd:
1029 if cwd:
1030 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1030 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1031 for f in patches:
1031 for f in patches:
1032 ctype, gp = patches[f]
1032 ctype, gp = patches[f]
1033 if ctype == 'RENAME':
1033 if ctype == 'RENAME':
1034 copies.append((gp.oldpath, gp.path))
1034 copies.append((gp.oldpath, gp.path))
1035 removes[gp.oldpath] = 1
1035 removes[gp.oldpath] = 1
1036 elif ctype == 'COPY':
1036 elif ctype == 'COPY':
1037 copies.append((gp.oldpath, gp.path))
1037 copies.append((gp.oldpath, gp.path))
1038 elif ctype == 'DELETE':
1038 elif ctype == 'DELETE':
1039 removes[gp.path] = 1
1039 removes[gp.path] = 1
1040 for src, dst in copies:
1040 for src, dst in copies:
1041 repo.copy(src, dst)
1041 repo.copy(src, dst)
1042 removes = removes.keys()
1042 removes = removes.keys()
1043 if removes:
1043 if removes:
1044 removes.sort()
1044 removes.sort()
1045 repo.remove(removes, True)
1045 repo.remove(removes, True)
1046 for f in patches:
1046 for f in patches:
1047 ctype, gp = patches[f]
1047 ctype, gp = patches[f]
1048 if gp and gp.mode:
1048 if gp and gp.mode:
1049 x = gp.mode & 0100 != 0
1049 x = gp.mode & 0100 != 0
1050 l = gp.mode & 020000 != 0
1050 l = gp.mode & 020000 != 0
1051 dst = os.path.join(repo.root, gp.path)
1051 dst = os.path.join(repo.root, gp.path)
1052 # patch won't create empty files
1052 # patch won't create empty files
1053 if ctype == 'ADD' and not os.path.exists(dst):
1053 if ctype == 'ADD' and not os.path.exists(dst):
1054 repo.wwrite(gp.path, '', x and 'x' or '')
1054 repo.wwrite(gp.path, '', x and 'x' or '')
1055 else:
1055 else:
1056 util.set_link(dst, l)
1056 util.set_link(dst, l)
1057 if not l:
1057 if not l:
1058 util.set_exec(dst, x)
1058 util.set_exec(dst, x)
1059 cmdutil.addremove(repo, cfiles)
1059 cmdutil.addremove(repo, cfiles)
1060 files = patches.keys()
1060 files = patches.keys()
1061 files.extend([r for r in removes if r not in files])
1061 files.extend([r for r in removes if r not in files])
1062 files.sort()
1062 files.sort()
1063
1063
1064 return files
1064 return files
1065
1065
1066 def b85diff(to, tn):
1066 def b85diff(to, tn):
1067 '''print base85-encoded binary diff'''
1067 '''print base85-encoded binary diff'''
1068 def gitindex(text):
1068 def gitindex(text):
1069 if not text:
1069 if not text:
1070 return '0' * 40
1070 return '0' * 40
1071 l = len(text)
1071 l = len(text)
1072 s = sha.new('blob %d\0' % l)
1072 s = sha.new('blob %d\0' % l)
1073 s.update(text)
1073 s.update(text)
1074 return s.hexdigest()
1074 return s.hexdigest()
1075
1075
1076 def fmtline(line):
1076 def fmtline(line):
1077 l = len(line)
1077 l = len(line)
1078 if l <= 26:
1078 if l <= 26:
1079 l = chr(ord('A') + l - 1)
1079 l = chr(ord('A') + l - 1)
1080 else:
1080 else:
1081 l = chr(l - 26 + ord('a') - 1)
1081 l = chr(l - 26 + ord('a') - 1)
1082 return '%c%s\n' % (l, base85.b85encode(line, True))
1082 return '%c%s\n' % (l, base85.b85encode(line, True))
1083
1083
1084 def chunk(text, csize=52):
1084 def chunk(text, csize=52):
1085 l = len(text)
1085 l = len(text)
1086 i = 0
1086 i = 0
1087 while i < l:
1087 while i < l:
1088 yield text[i:i+csize]
1088 yield text[i:i+csize]
1089 i += csize
1089 i += csize
1090
1090
1091 tohash = gitindex(to)
1091 tohash = gitindex(to)
1092 tnhash = gitindex(tn)
1092 tnhash = gitindex(tn)
1093 if tohash == tnhash:
1093 if tohash == tnhash:
1094 return ""
1094 return ""
1095
1095
1096 # TODO: deltas
1096 # TODO: deltas
1097 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1097 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1098 (tohash, tnhash, len(tn))]
1098 (tohash, tnhash, len(tn))]
1099 for l in chunk(zlib.compress(tn)):
1099 for l in chunk(zlib.compress(tn)):
1100 ret.append(fmtline(l))
1100 ret.append(fmtline(l))
1101 ret.append('\n')
1101 ret.append('\n')
1102 return ''.join(ret)
1102 return ''.join(ret)
1103
1103
1104 def diff(repo, node1=None, node2=None, files=None, match=util.always,
1104 def diff(repo, node1=None, node2=None, files=None, match=util.always,
1105 fp=None, changes=None, opts=None):
1105 fp=None, changes=None, opts=None):
1106 '''print diff of changes to files between two nodes, or node and
1106 '''print diff of changes to files between two nodes, or node and
1107 working directory.
1107 working directory.
1108
1108
1109 if node1 is None, use first dirstate parent instead.
1109 if node1 is None, use first dirstate parent instead.
1110 if node2 is None, compare node1 with working directory.'''
1110 if node2 is None, compare node1 with working directory.'''
1111
1111
1112 if opts is None:
1112 if opts is None:
1113 opts = mdiff.defaultopts
1113 opts = mdiff.defaultopts
1114 if fp is None:
1114 if fp is None:
1115 fp = repo.ui
1115 fp = repo.ui
1116
1116
1117 if not node1:
1117 if not node1:
1118 node1 = repo.dirstate.parents()[0]
1118 node1 = repo.dirstate.parents()[0]
1119
1119
1120 ccache = {}
1120 ccache = {}
1121 def getctx(r):
1121 def getctx(r):
1122 if r not in ccache:
1122 if r not in ccache:
1123 ccache[r] = context.changectx(repo, r)
1123 ccache[r] = context.changectx(repo, r)
1124 return ccache[r]
1124 return ccache[r]
1125
1125
1126 flcache = {}
1126 flcache = {}
1127 def getfilectx(f, ctx):
1127 def getfilectx(f, ctx):
1128 flctx = ctx.filectx(f, filelog=flcache.get(f))
1128 flctx = ctx.filectx(f, filelog=flcache.get(f))
1129 if f not in flcache:
1129 if f not in flcache:
1130 flcache[f] = flctx._filelog
1130 flcache[f] = flctx._filelog
1131 return flctx
1131 return flctx
1132
1132
1133 # reading the data for node1 early allows it to play nicely
1133 # reading the data for node1 early allows it to play nicely
1134 # with repo.status and the revlog cache.
1134 # with repo.status and the revlog cache.
1135 ctx1 = context.changectx(repo, node1)
1135 ctx1 = context.changectx(repo, node1)
1136 # force manifest reading
1136 # force manifest reading
1137 man1 = ctx1.manifest()
1137 man1 = ctx1.manifest()
1138 date1 = util.datestr(ctx1.date())
1138 date1 = util.datestr(ctx1.date())
1139
1139
1140 if not changes:
1140 if not changes:
1141 changes = repo.status(node1, node2, files, match=match)[:5]
1141 changes = repo.status(node1, node2, files, match=match)[:5]
1142 modified, added, removed, deleted, unknown = changes
1142 modified, added, removed, deleted, unknown = changes
1143
1143
1144 if not modified and not added and not removed:
1144 if not modified and not added and not removed:
1145 return
1145 return
1146
1146
1147 if node2:
1147 if node2:
1148 ctx2 = context.changectx(repo, node2)
1148 ctx2 = context.changectx(repo, node2)
1149 execf2 = ctx2.manifest().execf
1149 execf2 = ctx2.manifest().execf
1150 linkf2 = ctx2.manifest().linkf
1150 linkf2 = ctx2.manifest().linkf
1151 else:
1151 else:
1152 ctx2 = context.workingctx(repo)
1152 ctx2 = context.workingctx(repo)
1153 execf2 = util.execfunc(repo.root, None)
1153 execf2 = util.execfunc(repo.root, None)
1154 linkf2 = util.linkfunc(repo.root, None)
1154 linkf2 = util.linkfunc(repo.root, None)
1155 if execf2 is None:
1155 if execf2 is None:
1156 mc = ctx2.parents()[0].manifest().copy()
1156 mc = ctx2.parents()[0].manifest().copy()
1157 execf2 = mc.execf
1157 execf2 = mc.execf
1158 linkf2 = mc.linkf
1158 linkf2 = mc.linkf
1159
1159
1160 # returns False if there was no rename between ctx1 and ctx2
1160 # returns False if there was no rename between ctx1 and ctx2
1161 # returns None if the file was created between ctx1 and ctx2
1161 # returns None if the file was created between ctx1 and ctx2
1162 # returns the (file, node) present in ctx1 that was renamed to f in ctx2
1162 # returns the (file, node) present in ctx1 that was renamed to f in ctx2
1163 # This will only really work if c1 is the Nth 1st parent of c2.
1163 # This will only really work if c1 is the Nth 1st parent of c2.
1164 def renamed(c1, c2, man, f):
1164 def renamed(c1, c2, man, f):
1165 startrev = c1.rev()
1165 startrev = c1.rev()
1166 c = c2
1166 c = c2
1167 crev = c.rev()
1167 crev = c.rev()
1168 if crev is None:
1168 if crev is None:
1169 crev = repo.changelog.count()
1169 crev = repo.changelog.count()
1170 orig = f
1170 orig = f
1171 files = (f,)
1171 files = (f,)
1172 while crev > startrev:
1172 while crev > startrev:
1173 if f in files:
1173 if f in files:
1174 try:
1174 try:
1175 src = getfilectx(f, c).renamed()
1175 src = getfilectx(f, c).renamed()
1176 except revlog.LookupError:
1176 except revlog.LookupError:
1177 return None
1177 return None
1178 if src:
1178 if src:
1179 f = src[0]
1179 f = src[0]
1180 crev = c.parents()[0].rev()
1180 crev = c.parents()[0].rev()
1181 # try to reuse
1181 # try to reuse
1182 c = getctx(crev)
1182 c = getctx(crev)
1183 files = c.files()
1183 files = c.files()
1184 if f not in man:
1184 if f not in man:
1185 return None
1185 return None
1186 if f == orig:
1186 if f == orig:
1187 return False
1187 return False
1188 return f
1188 return f
1189
1189
1190 if repo.ui.quiet:
1190 if repo.ui.quiet:
1191 r = None
1191 r = None
1192 else:
1192 else:
1193 hexfunc = repo.ui.debugflag and hex or short
1193 hexfunc = repo.ui.debugflag and hex or short
1194 r = [hexfunc(node) for node in [node1, node2] if node]
1194 r = [hexfunc(node) for node in [node1, node2] if node]
1195
1195
1196 if opts.git:
1196 if opts.git:
1197 copied = {}
1197 copied = {}
1198 c1, c2 = ctx1, ctx2
1198 c1, c2 = ctx1, ctx2
1199 files = added
1199 files = added
1200 man = man1
1200 man = man1
1201 if node2 and ctx1.rev() >= ctx2.rev():
1201 if node2 and ctx1.rev() >= ctx2.rev():
1202 # renamed() starts at c2 and walks back in history until c1.
1202 # renamed() starts at c2 and walks back in history until c1.
1203 # Since ctx1.rev() >= ctx2.rev(), invert ctx2 and ctx1 to
1203 # Since ctx1.rev() >= ctx2.rev(), invert ctx2 and ctx1 to
1204 # detect (inverted) copies.
1204 # detect (inverted) copies.
1205 c1, c2 = ctx2, ctx1
1205 c1, c2 = ctx2, ctx1
1206 files = removed
1206 files = removed
1207 man = ctx2.manifest()
1207 man = ctx2.manifest()
1208 for f in files:
1208 for f in files:
1209 src = renamed(c1, c2, man, f)
1209 src = renamed(c1, c2, man, f)
1210 if src:
1210 if src:
1211 copied[f] = src
1211 copied[f] = src
1212 if ctx1 == c2:
1212 if ctx1 == c2:
1213 # invert the copied dict
1213 # invert the copied dict
1214 copied = dict([(v, k) for (k, v) in copied.iteritems()])
1214 copied = dict([(v, k) for (k, v) in copied.iteritems()])
1215 # If we've renamed file foo to bar (copied['bar'] = 'foo'),
1215 # If we've renamed file foo to bar (copied['bar'] = 'foo'),
1216 # avoid showing a diff for foo if we're going to show
1216 # avoid showing a diff for foo if we're going to show
1217 # the rename to bar.
1217 # the rename to bar.
1218 srcs = [x[1] for x in copied.iteritems() if x[0] in added]
1218 srcs = [x[1] for x in copied.iteritems() if x[0] in added]
1219
1219
1220 all = modified + added + removed
1220 all = modified + added + removed
1221 all.sort()
1221 all.sort()
1222 gone = {}
1222 gone = {}
1223
1223
1224 for f in all:
1224 for f in all:
1225 to = None
1225 to = None
1226 tn = None
1226 tn = None
1227 dodiff = True
1227 dodiff = True
1228 header = []
1228 header = []
1229 if f in man1:
1229 if f in man1:
1230 to = getfilectx(f, ctx1).data()
1230 to = getfilectx(f, ctx1).data()
1231 if f not in removed:
1231 if f not in removed:
1232 tn = getfilectx(f, ctx2).data()
1232 tn = getfilectx(f, ctx2).data()
1233 a, b = f, f
1233 if opts.git:
1234 if opts.git:
1234 def gitmode(x, l):
1235 def gitmode(x, l):
1235 return l and '120000' or (x and '100755' or '100644')
1236 return l and '120000' or (x and '100755' or '100644')
1236 def addmodehdr(header, omode, nmode):
1237 def addmodehdr(header, omode, nmode):
1237 if omode != nmode:
1238 if omode != nmode:
1238 header.append('old mode %s\n' % omode)
1239 header.append('old mode %s\n' % omode)
1239 header.append('new mode %s\n' % nmode)
1240 header.append('new mode %s\n' % nmode)
1240
1241
1241 a, b = f, f
1242 if f in added:
1242 if f in added:
1243 mode = gitmode(execf2(f), linkf2(f))
1243 mode = gitmode(execf2(f), linkf2(f))
1244 if f in copied:
1244 if f in copied:
1245 a = copied[f]
1245 a = copied[f]
1246 omode = gitmode(man1.execf(a), man1.linkf(a))
1246 omode = gitmode(man1.execf(a), man1.linkf(a))
1247 addmodehdr(header, omode, mode)
1247 addmodehdr(header, omode, mode)
1248 if a in removed and a not in gone:
1248 if a in removed and a not in gone:
1249 op = 'rename'
1249 op = 'rename'
1250 gone[a] = 1
1250 gone[a] = 1
1251 else:
1251 else:
1252 op = 'copy'
1252 op = 'copy'
1253 header.append('%s from %s\n' % (op, a))
1253 header.append('%s from %s\n' % (op, a))
1254 header.append('%s to %s\n' % (op, f))
1254 header.append('%s to %s\n' % (op, f))
1255 to = getfilectx(a, ctx1).data()
1255 to = getfilectx(a, ctx1).data()
1256 else:
1256 else:
1257 header.append('new file mode %s\n' % mode)
1257 header.append('new file mode %s\n' % mode)
1258 if util.binary(tn):
1258 if util.binary(tn):
1259 dodiff = 'binary'
1259 dodiff = 'binary'
1260 elif f in removed:
1260 elif f in removed:
1261 if f in srcs:
1261 if f in srcs:
1262 dodiff = False
1262 dodiff = False
1263 else:
1263 else:
1264 mode = gitmode(man1.execf(f), man1.linkf(f))
1264 mode = gitmode(man1.execf(f), man1.linkf(f))
1265 header.append('deleted file mode %s\n' % mode)
1265 header.append('deleted file mode %s\n' % mode)
1266 else:
1266 else:
1267 omode = gitmode(man1.execf(f), man1.linkf(f))
1267 omode = gitmode(man1.execf(f), man1.linkf(f))
1268 nmode = gitmode(execf2(f), linkf2(f))
1268 nmode = gitmode(execf2(f), linkf2(f))
1269 addmodehdr(header, omode, nmode)
1269 addmodehdr(header, omode, nmode)
1270 if util.binary(to) or util.binary(tn):
1270 if util.binary(to) or util.binary(tn):
1271 dodiff = 'binary'
1271 dodiff = 'binary'
1272 r = None
1272 r = None
1273 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
1273 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
1274 if dodiff:
1274 if dodiff:
1275 if dodiff == 'binary':
1275 if dodiff == 'binary':
1276 text = b85diff(to, tn)
1276 text = b85diff(to, tn)
1277 else:
1277 else:
1278 text = mdiff.unidiff(to, date1,
1278 text = mdiff.unidiff(to, date1,
1279 # ctx2 date may be dynamic
1279 # ctx2 date may be dynamic
1280 tn, util.datestr(ctx2.date()),
1280 tn, util.datestr(ctx2.date()),
1281 f, r, opts=opts)
1281 a, b, r, opts=opts)
1282 if text or len(header) > 1:
1282 if text or len(header) > 1:
1283 fp.write(''.join(header))
1283 fp.write(''.join(header))
1284 fp.write(text)
1284 fp.write(text)
1285
1285
1286 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1286 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1287 opts=None):
1287 opts=None):
1288 '''export changesets as hg patches.'''
1288 '''export changesets as hg patches.'''
1289
1289
1290 total = len(revs)
1290 total = len(revs)
1291 revwidth = max([len(str(rev)) for rev in revs])
1291 revwidth = max([len(str(rev)) for rev in revs])
1292
1292
1293 def single(rev, seqno, fp):
1293 def single(rev, seqno, fp):
1294 ctx = repo.changectx(rev)
1294 ctx = repo.changectx(rev)
1295 node = ctx.node()
1295 node = ctx.node()
1296 parents = [p.node() for p in ctx.parents() if p]
1296 parents = [p.node() for p in ctx.parents() if p]
1297 branch = ctx.branch()
1297 branch = ctx.branch()
1298 if switch_parent:
1298 if switch_parent:
1299 parents.reverse()
1299 parents.reverse()
1300 prev = (parents and parents[0]) or nullid
1300 prev = (parents and parents[0]) or nullid
1301
1301
1302 if not fp:
1302 if not fp:
1303 fp = cmdutil.make_file(repo, template, node, total=total,
1303 fp = cmdutil.make_file(repo, template, node, total=total,
1304 seqno=seqno, revwidth=revwidth)
1304 seqno=seqno, revwidth=revwidth)
1305 if fp != sys.stdout and hasattr(fp, 'name'):
1305 if fp != sys.stdout and hasattr(fp, 'name'):
1306 repo.ui.note("%s\n" % fp.name)
1306 repo.ui.note("%s\n" % fp.name)
1307
1307
1308 fp.write("# HG changeset patch\n")
1308 fp.write("# HG changeset patch\n")
1309 fp.write("# User %s\n" % ctx.user())
1309 fp.write("# User %s\n" % ctx.user())
1310 fp.write("# Date %d %d\n" % ctx.date())
1310 fp.write("# Date %d %d\n" % ctx.date())
1311 if branch and (branch != 'default'):
1311 if branch and (branch != 'default'):
1312 fp.write("# Branch %s\n" % branch)
1312 fp.write("# Branch %s\n" % branch)
1313 fp.write("# Node ID %s\n" % hex(node))
1313 fp.write("# Node ID %s\n" % hex(node))
1314 fp.write("# Parent %s\n" % hex(prev))
1314 fp.write("# Parent %s\n" % hex(prev))
1315 if len(parents) > 1:
1315 if len(parents) > 1:
1316 fp.write("# Parent %s\n" % hex(parents[1]))
1316 fp.write("# Parent %s\n" % hex(parents[1]))
1317 fp.write(ctx.description().rstrip())
1317 fp.write(ctx.description().rstrip())
1318 fp.write("\n\n")
1318 fp.write("\n\n")
1319
1319
1320 diff(repo, prev, node, fp=fp, opts=opts)
1320 diff(repo, prev, node, fp=fp, opts=opts)
1321 if fp not in (sys.stdout, repo.ui):
1321 if fp not in (sys.stdout, repo.ui):
1322 fp.close()
1322 fp.close()
1323
1323
1324 for seqno, rev in enumerate(revs):
1324 for seqno, rev in enumerate(revs):
1325 single(rev, seqno+1, fp)
1325 single(rev, seqno+1, fp)
1326
1326
1327 def diffstat(patchlines):
1327 def diffstat(patchlines):
1328 if not util.find_exe('diffstat'):
1328 if not util.find_exe('diffstat'):
1329 return
1329 return
1330 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
1330 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
1331 try:
1331 try:
1332 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
1332 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
1333 try:
1333 try:
1334 for line in patchlines: print >> p.tochild, line
1334 for line in patchlines: print >> p.tochild, line
1335 p.tochild.close()
1335 p.tochild.close()
1336 if p.wait(): return
1336 if p.wait(): return
1337 fp = os.fdopen(fd, 'r')
1337 fp = os.fdopen(fd, 'r')
1338 stat = []
1338 stat = []
1339 for line in fp: stat.append(line.lstrip())
1339 for line in fp: stat.append(line.lstrip())
1340 last = stat.pop()
1340 last = stat.pop()
1341 stat.insert(0, last)
1341 stat.insert(0, last)
1342 stat = ''.join(stat)
1342 stat = ''.join(stat)
1343 if stat.startswith('0 files'): raise ValueError
1343 if stat.startswith('0 files'): raise ValueError
1344 return stat
1344 return stat
1345 except: raise
1345 except: raise
1346 finally:
1346 finally:
1347 try: os.unlink(name)
1347 try: os.unlink(name)
1348 except: pass
1348 except: pass
@@ -1,197 +1,197 b''
1 adding start
1 adding start
2 adding new
2 adding new
3 % new file
3 % new file
4 diff --git a/new b/new
4 diff --git a/new b/new
5 new file mode 100644
5 new file mode 100644
6 --- /dev/null
6 --- /dev/null
7 +++ b/new
7 +++ b/new
8 @@ -0,0 +1,1 @@
8 @@ -0,0 +1,1 @@
9 +new
9 +new
10 % copy
10 % copy
11 diff --git a/new b/copy
11 diff --git a/new b/copy
12 copy from new
12 copy from new
13 copy to copy
13 copy to copy
14 % rename
14 % rename
15 diff --git a/copy b/rename
15 diff --git a/copy b/rename
16 rename from copy
16 rename from copy
17 rename to rename
17 rename to rename
18 % delete
18 % delete
19 diff --git a/rename b/rename
19 diff --git a/rename b/rename
20 deleted file mode 100644
20 deleted file mode 100644
21 --- a/rename
21 --- a/rename
22 +++ /dev/null
22 +++ /dev/null
23 @@ -1,1 +0,0 @@
23 @@ -1,1 +0,0 @@
24 -new
24 -new
25 adding src
25 adding src
26 % chmod 644
26 % chmod 644
27 diff --git a/src b/src
27 diff --git a/src b/src
28 old mode 100644
28 old mode 100644
29 new mode 100755
29 new mode 100755
30 % rename+mod+chmod
30 % rename+mod+chmod
31 diff --git a/src b/dst
31 diff --git a/src b/dst
32 old mode 100755
32 old mode 100755
33 new mode 100644
33 new mode 100644
34 rename from src
34 rename from src
35 rename to dst
35 rename to dst
36 --- a/dst
36 --- a/src
37 +++ b/dst
37 +++ b/dst
38 @@ -3,3 +3,4 @@ 3
38 @@ -3,3 +3,4 @@ 3
39 3
39 3
40 4
40 4
41 5
41 5
42 +a
42 +a
43 % nonexistent in tip+chmod
43 % nonexistent in tip+chmod
44 diff --git a/src b/src
44 diff --git a/src b/src
45 old mode 100644
45 old mode 100644
46 new mode 100755
46 new mode 100755
47 % binary diff
47 % binary diff
48 diff --git a/binfile.bin b/binfile.bin
48 diff --git a/binfile.bin b/binfile.bin
49 new file mode 100644
49 new file mode 100644
50 index 0000000000000000000000000000000000000000..37ba3d1c6f17137d9c5f5776fa040caf5fe73ff9
50 index 0000000000000000000000000000000000000000..37ba3d1c6f17137d9c5f5776fa040caf5fe73ff9
51 GIT binary patch
51 GIT binary patch
52 literal 593
52 literal 593
53 zc$@)I0<QguP)<h;3K|Lk000e1NJLTq000mG000mO0ssI2kdbIM00009a7bBm000XU
53 zc$@)I0<QguP)<h;3K|Lk000e1NJLTq000mG000mO0ssI2kdbIM00009a7bBm000XU
54 z000XU0RWnu7ytkO2XskIMF-Uh9TW;VpMjwv0005-Nkl<ZD9@FWPs=e;7{<>W$NUkd
54 z000XU0RWnu7ytkO2XskIMF-Uh9TW;VpMjwv0005-Nkl<ZD9@FWPs=e;7{<>W$NUkd
55 zX$nnYLt$-$V!?uy+1V%`z&Eh=ah|duER<4|QWhju3gb^nF*8iYobxWG-qqXl=2~5M
55 zX$nnYLt$-$V!?uy+1V%`z&Eh=ah|duER<4|QWhju3gb^nF*8iYobxWG-qqXl=2~5M
56 z*IoDB)sG^CfNuoBmqLTVU^<;@nwHP!1wrWd`{(mHo6VNXWtyh{alzqmsH*yYzpvLT
56 z*IoDB)sG^CfNuoBmqLTVU^<;@nwHP!1wrWd`{(mHo6VNXWtyh{alzqmsH*yYzpvLT
57 zLdY<T=ks|woh-`&01!ej#(xbV1f|pI*=%;d-%F*E*X#ZH`4I%6SS+$EJDE&ct=8po
57 zLdY<T=ks|woh-`&01!ej#(xbV1f|pI*=%;d-%F*E*X#ZH`4I%6SS+$EJDE&ct=8po
58 ziN#{?_j|kD%Cd|oiqds`xm@;oJ-^?NG3Gdqrs?5u*zI;{nogxsx~^|Fn^Y?Gdc6<;
58 ziN#{?_j|kD%Cd|oiqds`xm@;oJ-^?NG3Gdqrs?5u*zI;{nogxsx~^|Fn^Y?Gdc6<;
59 zfMJ+iF1J`LMx&A2?dEwNW8ClebzPTbIh{@$hS6*`kH@1d%Lo7fA#}N1)oN7`gm$~V
59 zfMJ+iF1J`LMx&A2?dEwNW8ClebzPTbIh{@$hS6*`kH@1d%Lo7fA#}N1)oN7`gm$~V
60 z+wDx#)OFqMcE{s!JN0-xhG8ItAjVkJwEcb`3WWlJfU2r?;Pd%dmR+q@mSri5q9_W-
60 z+wDx#)OFqMcE{s!JN0-xhG8ItAjVkJwEcb`3WWlJfU2r?;Pd%dmR+q@mSri5q9_W-
61 zaR2~ECX?B2w+zELozC0s*6Z~|QG^f{3I#<`?)Q7U-JZ|q5W;9Q8i_=pBuSzunx=U;
61 zaR2~ECX?B2w+zELozC0s*6Z~|QG^f{3I#<`?)Q7U-JZ|q5W;9Q8i_=pBuSzunx=U;
62 z9C)5jBoYw9^?EHyQl(M}1OlQcCX>lXB*ODN003Z&P17_@)3Pi=i0wb04<W?v-u}7K
62 z9C)5jBoYw9^?EHyQl(M}1OlQcCX>lXB*ODN003Z&P17_@)3Pi=i0wb04<W?v-u}7K
63 zXmmQA+wDgE!qR9o8jr`%=ab_&uh(l?R=r;Tjiqon91I2-hIu?57~@*4h7h9uORK#=
63 zXmmQA+wDgE!qR9o8jr`%=ab_&uh(l?R=r;Tjiqon91I2-hIu?57~@*4h7h9uORK#=
64 fQItJW-{SoTm)8|5##k|m00000NkvXXu0mjf{mKw4
64 fQItJW-{SoTm)8|5##k|m00000NkvXXu0mjf{mKw4
65
65
66 % import binary diff
66 % import binary diff
67 applying b.diff
67 applying b.diff
68
68
69 % rename binary file
69 % rename binary file
70 diff --git a/binfile.bin b/renamed.bin
70 diff --git a/binfile.bin b/renamed.bin
71 rename from binfile.bin
71 rename from binfile.bin
72 rename to renamed.bin
72 rename to renamed.bin
73
73
74 % diff across many revisions
74 % diff across many revisions
75 diff --git a/dst2 b/dst3
75 diff --git a/dst2 b/dst3
76 rename from dst2
76 rename from dst2
77 rename to dst3
77 rename to dst3
78 % reversed
78 % reversed
79 diff --git a/dst3 b/dst2
79 diff --git a/dst3 b/dst2
80 rename from dst3
80 rename from dst3
81 rename to dst2
81 rename to dst2
82
82
83 % file created before r1 and renamed before r2
83 % file created before r1 and renamed before r2
84 diff --git a/foo b/bar
84 diff --git a/foo b/bar
85 rename from foo
85 rename from foo
86 rename to bar
86 rename to bar
87 --- a/bar
87 --- a/foo
88 +++ b/bar
88 +++ b/bar
89 @@ -1,2 +1,3 @@ a
89 @@ -1,2 +1,3 @@ a
90 a
90 a
91 b
91 b
92 +c
92 +c
93 % reversed
93 % reversed
94 diff --git a/bar b/foo
94 diff --git a/bar b/foo
95 rename from bar
95 rename from bar
96 rename to foo
96 rename to foo
97 --- a/foo
97 --- a/bar
98 +++ b/foo
98 +++ b/foo
99 @@ -1,3 +1,2 @@ a
99 @@ -1,3 +1,2 @@ a
100 a
100 a
101 b
101 b
102 -c
102 -c
103
103
104 % file created in r1 and renamed before r2
104 % file created in r1 and renamed before r2
105 diff --git a/foo b/bar
105 diff --git a/foo b/bar
106 rename from foo
106 rename from foo
107 rename to bar
107 rename to bar
108 --- a/bar
108 --- a/foo
109 +++ b/bar
109 +++ b/bar
110 @@ -1,1 +1,3 @@ a
110 @@ -1,1 +1,3 @@ a
111 a
111 a
112 +b
112 +b
113 +c
113 +c
114 % reversed
114 % reversed
115 diff --git a/bar b/foo
115 diff --git a/bar b/foo
116 rename from bar
116 rename from bar
117 rename to foo
117 rename to foo
118 --- a/foo
118 --- a/bar
119 +++ b/foo
119 +++ b/foo
120 @@ -1,3 +1,1 @@ a
120 @@ -1,3 +1,1 @@ a
121 a
121 a
122 -b
122 -b
123 -c
123 -c
124
124
125 % file created after r1 and renamed before r2
125 % file created after r1 and renamed before r2
126 diff --git a/bar b/bar
126 diff --git a/bar b/bar
127 new file mode 100644
127 new file mode 100644
128 --- /dev/null
128 --- /dev/null
129 +++ b/bar
129 +++ b/bar
130 @@ -0,0 +1,3 @@
130 @@ -0,0 +1,3 @@
131 +a
131 +a
132 +b
132 +b
133 +c
133 +c
134 % reversed
134 % reversed
135 diff --git a/bar b/bar
135 diff --git a/bar b/bar
136 deleted file mode 100644
136 deleted file mode 100644
137 --- a/bar
137 --- a/bar
138 +++ /dev/null
138 +++ /dev/null
139 @@ -1,3 +0,0 @@
139 @@ -1,3 +0,0 @@
140 -a
140 -a
141 -b
141 -b
142 -c
142 -c
143
143
144 % comparing with the working dir
144 % comparing with the working dir
145 % there's a copy in the working dir...
145 % there's a copy in the working dir...
146 diff --git a/created2 b/created3
146 diff --git a/created2 b/created3
147 rename from created2
147 rename from created2
148 rename to created3
148 rename to created3
149
149
150 % ...but there's another copy between the original rev and the wd
150 % ...but there's another copy between the original rev and the wd
151 diff --git a/created b/created3
151 diff --git a/created b/created3
152 rename from created
152 rename from created
153 rename to created3
153 rename to created3
154
154
155 % ...but the source of the copy was created after the original rev
155 % ...but the source of the copy was created after the original rev
156 diff --git a/created3 b/created3
156 diff --git a/created3 b/created3
157 new file mode 100644
157 new file mode 100644
158 --- /dev/null
158 --- /dev/null
159 +++ b/created3
159 +++ b/created3
160 @@ -0,0 +1,1 @@
160 @@ -0,0 +1,1 @@
161 +
161 +
162 % created in parent of wd; renamed in the wd
162 % created in parent of wd; renamed in the wd
163 diff --git a/brand-new b/brand-new2
163 diff --git a/brand-new b/brand-new2
164 rename from brand-new
164 rename from brand-new
165 rename to brand-new2
165 rename to brand-new2
166
166
167 % created between r1 and parent of wd; renamed in the wd
167 % created between r1 and parent of wd; renamed in the wd
168 diff --git a/brand-new2 b/brand-new2
168 diff --git a/brand-new2 b/brand-new2
169 new file mode 100644
169 new file mode 100644
170 --- /dev/null
170 --- /dev/null
171 +++ b/brand-new2
171 +++ b/brand-new2
172 @@ -0,0 +1,1 @@
172 @@ -0,0 +1,1 @@
173 +
173 +
174 % one file is copied to many destinations and removed
174 % one file is copied to many destinations and removed
175 diff --git a/brand-new2 b/brand-new3
175 diff --git a/brand-new2 b/brand-new3
176 rename from brand-new2
176 rename from brand-new2
177 rename to brand-new3
177 rename to brand-new3
178 diff --git a/brand-new2 b/brand-new3-2
178 diff --git a/brand-new2 b/brand-new3-2
179 copy from brand-new2
179 copy from brand-new2
180 copy to brand-new3-2
180 copy to brand-new3-2
181 % reversed
181 % reversed
182 diff --git a/brand-new3 b/brand-new2
182 diff --git a/brand-new3 b/brand-new2
183 rename from brand-new3
183 rename from brand-new3
184 rename to brand-new2
184 rename to brand-new2
185 diff --git a/brand-new3-2 b/brand-new3-2
185 diff --git a/brand-new3-2 b/brand-new3-2
186 deleted file mode 100644
186 deleted file mode 100644
187 --- a/brand-new3-2
187 --- a/brand-new3-2
188 +++ /dev/null
188 +++ /dev/null
189 @@ -1,1 +0,0 @@
189 @@ -1,1 +0,0 @@
190 -
190 -
191 % there should be a trailing TAB if there are spaces in the file name
191 % there should be a trailing TAB if there are spaces in the file name
192 diff --git a/with spaces b/with spaces
192 diff --git a/with spaces b/with spaces
193 new file mode 100644
193 new file mode 100644
194 --- /dev/null
194 --- /dev/null
195 +++ b/with spaces
195 +++ b/with spaces
196 @@ -0,0 +1,1 @@
196 @@ -0,0 +1,1 @@
197 +foo
197 +foo
General Comments 0
You need to be logged in to leave comments. Login now