##// END OF EJS Templates
remove various unused import
Benoit Boissinot -
r3963:ba450418 default
parent child Browse files
Show More
@@ -1,178 +1,178 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 import time, sys, signal, os
14 import sys
15 from mercurial.i18n import gettext as _
15 from mercurial.i18n import gettext as _
16 from mercurial import hg, mdiff, cmdutil, ui, util, templater, node
16 from mercurial import hg, mdiff, cmdutil, ui, util, templater, node
17
17
18 def __gather(ui, repo, node1, node2):
18 def __gather(ui, repo, node1, node2):
19 def dirtywork(f, mmap1, mmap2):
19 def dirtywork(f, mmap1, mmap2):
20 lines = 0
20 lines = 0
21
21
22 to = mmap1 and repo.file(f).read(mmap1[f]) or None
22 to = mmap1 and repo.file(f).read(mmap1[f]) or None
23 tn = mmap2 and repo.file(f).read(mmap2[f]) or None
23 tn = mmap2 and repo.file(f).read(mmap2[f]) or None
24
24
25 diff = mdiff.unidiff(to, "", tn, "", f).split("\n")
25 diff = mdiff.unidiff(to, "", tn, "", f).split("\n")
26
26
27 for line in diff:
27 for line in diff:
28 if not line:
28 if not line:
29 continue # skip EOF
29 continue # skip EOF
30 if line.startswith(" "):
30 if line.startswith(" "):
31 continue # context line
31 continue # context line
32 if line.startswith("--- ") or line.startswith("+++ "):
32 if line.startswith("--- ") or line.startswith("+++ "):
33 continue # begining of diff
33 continue # begining of diff
34 if line.startswith("@@ "):
34 if line.startswith("@@ "):
35 continue # info line
35 continue # info line
36
36
37 # changed lines
37 # changed lines
38 lines += 1
38 lines += 1
39
39
40 return lines
40 return lines
41
41
42 ##
42 ##
43
43
44 lines = 0
44 lines = 0
45
45
46 changes = repo.status(node1, node2, None, util.always)[:5]
46 changes = repo.status(node1, node2, None, util.always)[:5]
47
47
48 modified, added, removed, deleted, unknown = changes
48 modified, added, removed, deleted, unknown = changes
49
49
50 who = repo.changelog.read(node2)[1]
50 who = repo.changelog.read(node2)[1]
51 who = templater.email(who) # get the email of the person
51 who = templater.email(who) # get the email of the person
52
52
53 mmap1 = repo.manifest.read(repo.changelog.read(node1)[0])
53 mmap1 = repo.manifest.read(repo.changelog.read(node1)[0])
54 mmap2 = repo.manifest.read(repo.changelog.read(node2)[0])
54 mmap2 = repo.manifest.read(repo.changelog.read(node2)[0])
55 for f in modified:
55 for f in modified:
56 lines += dirtywork(f, mmap1, mmap2)
56 lines += dirtywork(f, mmap1, mmap2)
57
57
58 for f in added:
58 for f in added:
59 lines += dirtywork(f, None, mmap2)
59 lines += dirtywork(f, None, mmap2)
60
60
61 for f in removed:
61 for f in removed:
62 lines += dirtywork(f, mmap1, None)
62 lines += dirtywork(f, mmap1, None)
63
63
64 for f in deleted:
64 for f in deleted:
65 lines += dirtywork(f, mmap1, mmap2)
65 lines += dirtywork(f, mmap1, mmap2)
66
66
67 for f in unknown:
67 for f in unknown:
68 lines += dirtywork(f, mmap1, mmap2)
68 lines += dirtywork(f, mmap1, mmap2)
69
69
70 return (who, lines)
70 return (who, lines)
71
71
72 def gather_stats(ui, repo, amap, revs=None, progress=False):
72 def gather_stats(ui, repo, amap, revs=None, progress=False):
73 stats = {}
73 stats = {}
74
74
75 cl = repo.changelog
75 cl = repo.changelog
76
76
77 if not revs:
77 if not revs:
78 revs = range(0, cl.count())
78 revs = range(0, cl.count())
79
79
80 nr_revs = len(revs)
80 nr_revs = len(revs)
81 cur_rev = 0
81 cur_rev = 0
82
82
83 for rev in revs:
83 for rev in revs:
84 cur_rev += 1 # next revision
84 cur_rev += 1 # next revision
85
85
86 node2 = cl.node(rev)
86 node2 = cl.node(rev)
87 node1 = cl.parents(node2)[0]
87 node1 = cl.parents(node2)[0]
88
88
89 if cl.parents(node2)[1] != node.nullid:
89 if cl.parents(node2)[1] != node.nullid:
90 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
90 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
91 continue
91 continue
92
92
93 who, lines = __gather(ui, repo, node1, node2)
93 who, lines = __gather(ui, repo, node1, node2)
94
94
95 # remap the owner if possible
95 # remap the owner if possible
96 if amap.has_key(who):
96 if amap.has_key(who):
97 ui.note("using '%s' alias for '%s'\n" % (amap[who], who))
97 ui.note("using '%s' alias for '%s'\n" % (amap[who], who))
98 who = amap[who]
98 who = amap[who]
99
99
100 if not stats.has_key(who):
100 if not stats.has_key(who):
101 stats[who] = 0
101 stats[who] = 0
102 stats[who] += lines
102 stats[who] += lines
103
103
104 ui.note("rev %d: %d lines by %s\n" % (rev, lines, who))
104 ui.note("rev %d: %d lines by %s\n" % (rev, lines, who))
105
105
106 if progress:
106 if progress:
107 if int(100.0*(cur_rev - 1)/nr_revs) < int(100.0*cur_rev/nr_revs):
107 if int(100.0*(cur_rev - 1)/nr_revs) < int(100.0*cur_rev/nr_revs):
108 ui.write("%d%%.." % (int(100.0*cur_rev/nr_revs),))
108 ui.write("%d%%.." % (int(100.0*cur_rev/nr_revs),))
109 sys.stdout.flush()
109 sys.stdout.flush()
110
110
111 if progress:
111 if progress:
112 ui.write("done\n")
112 ui.write("done\n")
113 sys.stdout.flush()
113 sys.stdout.flush()
114
114
115 return stats
115 return stats
116
116
117 def churn(ui, repo, **opts):
117 def churn(ui, repo, **opts):
118 "Graphs the number of lines changed"
118 "Graphs the number of lines changed"
119
119
120 def pad(s, l):
120 def pad(s, l):
121 if len(s) < l:
121 if len(s) < l:
122 return s + " " * (l-len(s))
122 return s + " " * (l-len(s))
123 return s[0:l]
123 return s[0:l]
124
124
125 def graph(n, maximum, width, char):
125 def graph(n, maximum, width, char):
126 n = int(n * width / float(maximum))
126 n = int(n * width / float(maximum))
127
127
128 return char * (n)
128 return char * (n)
129
129
130 def get_aliases(f):
130 def get_aliases(f):
131 aliases = {}
131 aliases = {}
132
132
133 for l in f.readlines():
133 for l in f.readlines():
134 l = l.strip()
134 l = l.strip()
135 alias, actual = l.split(" ")
135 alias, actual = l.split(" ")
136 aliases[alias] = actual
136 aliases[alias] = actual
137
137
138 return aliases
138 return aliases
139
139
140 amap = {}
140 amap = {}
141 aliases = opts.get('aliases')
141 aliases = opts.get('aliases')
142 if aliases:
142 if aliases:
143 try:
143 try:
144 f = open(aliases,"r")
144 f = open(aliases,"r")
145 except OSError, e:
145 except OSError, e:
146 print "Error: " + e
146 print "Error: " + e
147 return
147 return
148
148
149 amap = get_aliases(f)
149 amap = get_aliases(f)
150 f.close()
150 f.close()
151
151
152 revs = [int(r) for r in cmdutil.revrange(repo, opts['rev'])]
152 revs = [int(r) for r in cmdutil.revrange(repo, opts['rev'])]
153 revs.sort()
153 revs.sort()
154 stats = gather_stats(ui, repo, amap, revs, opts.get('progress'))
154 stats = gather_stats(ui, repo, amap, revs, opts.get('progress'))
155
155
156 # make a list of tuples (name, lines) and sort it in descending order
156 # make a list of tuples (name, lines) and sort it in descending order
157 ordered = stats.items()
157 ordered = stats.items()
158 ordered.sort(lambda x, y: cmp(y[1], x[1]))
158 ordered.sort(lambda x, y: cmp(y[1], x[1]))
159
159
160 maximum = ordered[0][1]
160 maximum = ordered[0][1]
161
161
162 ui.note("Assuming 80 character terminal\n")
162 ui.note("Assuming 80 character terminal\n")
163 width = 80 - 1
163 width = 80 - 1
164
164
165 for i in ordered:
165 for i in ordered:
166 person = i[0]
166 person = i[0]
167 lines = i[1]
167 lines = i[1]
168 print "%s %6d %s" % (pad(person, 20), lines,
168 print "%s %6d %s" % (pad(person, 20), lines,
169 graph(lines, maximum, width - 20 - 1 - 6 - 2 - 2, '*'))
169 graph(lines, maximum, width - 20 - 1 - 6 - 2 - 2, '*'))
170
170
171 cmdtable = {
171 cmdtable = {
172 "churn":
172 "churn":
173 (churn,
173 (churn,
174 [('r', 'rev', [], _('limit statistics to the specified revisions')),
174 [('r', 'rev', [], _('limit statistics to the specified revisions')),
175 ('', 'aliases', '', _('file with email aliases')),
175 ('', 'aliases', '', _('file with email aliases')),
176 ('', 'progress', None, _('show progress'))],
176 ('', 'progress', None, _('show progress'))],
177 'hg churn [-r revision range] [-a file] [--progress]'),
177 'hg churn [-r revision range] [-a file] [--progress]'),
178 }
178 }
@@ -1,308 +1,308 b''
1 # Minimal support for git commands on an hg repository
1 # Minimal support for git commands on an hg repository
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import time, sys, signal, os
8 import sys, os
9 from mercurial import hg, fancyopts, commands, ui, util, patch, revlog
9 from mercurial import hg, fancyopts, commands, ui, util, patch, revlog
10
10
11 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
11 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
12 """diff trees from two commits"""
12 """diff trees from two commits"""
13 def __difftree(repo, node1, node2, files=[]):
13 def __difftree(repo, node1, node2, files=[]):
14 if node2:
14 if node2:
15 change = repo.changelog.read(node2)
15 change = repo.changelog.read(node2)
16 mmap2 = repo.manifest.read(change[0])
16 mmap2 = repo.manifest.read(change[0])
17 status = repo.status(node1, node2, files=files)[:5]
17 status = repo.status(node1, node2, files=files)[:5]
18 modified, added, removed, deleted, unknown = status
18 modified, added, removed, deleted, unknown = status
19 else:
19 else:
20 status = repo.status(node1, files=files)[:5]
20 status = repo.status(node1, files=files)[:5]
21 modified, added, removed, deleted, unknown = status
21 modified, added, removed, deleted, unknown = status
22 if not node1:
22 if not node1:
23 node1 = repo.dirstate.parents()[0]
23 node1 = repo.dirstate.parents()[0]
24
24
25 change = repo.changelog.read(node1)
25 change = repo.changelog.read(node1)
26 mmap = repo.manifest.read(change[0])
26 mmap = repo.manifest.read(change[0])
27 empty = hg.short(hg.nullid)
27 empty = hg.short(hg.nullid)
28
28
29 for f in modified:
29 for f in modified:
30 # TODO get file permissions
30 # TODO get file permissions
31 print ":100664 100664 %s %s M\t%s\t%s" % (hg.short(mmap[f]),
31 print ":100664 100664 %s %s M\t%s\t%s" % (hg.short(mmap[f]),
32 hg.short(mmap2[f]),
32 hg.short(mmap2[f]),
33 f, f)
33 f, f)
34 for f in added:
34 for f in added:
35 print ":000000 100664 %s %s N\t%s\t%s" % (empty,
35 print ":000000 100664 %s %s N\t%s\t%s" % (empty,
36 hg.short(mmap2[f]),
36 hg.short(mmap2[f]),
37 f, f)
37 f, f)
38 for f in removed:
38 for f in removed:
39 print ":100664 000000 %s %s D\t%s\t%s" % (hg.short(mmap[f]),
39 print ":100664 000000 %s %s D\t%s\t%s" % (hg.short(mmap[f]),
40 empty,
40 empty,
41 f, f)
41 f, f)
42 ##
42 ##
43
43
44 while True:
44 while True:
45 if opts['stdin']:
45 if opts['stdin']:
46 try:
46 try:
47 line = raw_input().split(' ')
47 line = raw_input().split(' ')
48 node1 = line[0]
48 node1 = line[0]
49 if len(line) > 1:
49 if len(line) > 1:
50 node2 = line[1]
50 node2 = line[1]
51 else:
51 else:
52 node2 = None
52 node2 = None
53 except EOFError:
53 except EOFError:
54 break
54 break
55 node1 = repo.lookup(node1)
55 node1 = repo.lookup(node1)
56 if node2:
56 if node2:
57 node2 = repo.lookup(node2)
57 node2 = repo.lookup(node2)
58 else:
58 else:
59 node2 = node1
59 node2 = node1
60 node1 = repo.changelog.parents(node1)[0]
60 node1 = repo.changelog.parents(node1)[0]
61 if opts['patch']:
61 if opts['patch']:
62 if opts['pretty']:
62 if opts['pretty']:
63 catcommit(repo, node2, "")
63 catcommit(repo, node2, "")
64 patch.diff(repo, node1, node2,
64 patch.diff(repo, node1, node2,
65 files=files,
65 files=files,
66 opts=patch.diffopts(ui, {'git': True}))
66 opts=patch.diffopts(ui, {'git': True}))
67 else:
67 else:
68 __difftree(repo, node1, node2, files=files)
68 __difftree(repo, node1, node2, files=files)
69 if not opts['stdin']:
69 if not opts['stdin']:
70 break
70 break
71
71
72 def catcommit(repo, n, prefix, changes=None):
72 def catcommit(repo, n, prefix, changes=None):
73 nlprefix = '\n' + prefix;
73 nlprefix = '\n' + prefix;
74 (p1, p2) = repo.changelog.parents(n)
74 (p1, p2) = repo.changelog.parents(n)
75 (h, h1, h2) = map(hg.short, (n, p1, p2))
75 (h, h1, h2) = map(hg.short, (n, p1, p2))
76 (i1, i2) = map(repo.changelog.rev, (p1, p2))
76 (i1, i2) = map(repo.changelog.rev, (p1, p2))
77 if not changes:
77 if not changes:
78 changes = repo.changelog.read(n)
78 changes = repo.changelog.read(n)
79 print "tree %s" % (hg.short(changes[0]))
79 print "tree %s" % (hg.short(changes[0]))
80 if i1 != hg.nullrev: print "parent %s" % (h1)
80 if i1 != hg.nullrev: print "parent %s" % (h1)
81 if i2 != hg.nullrev: print "parent %s" % (h2)
81 if i2 != hg.nullrev: print "parent %s" % (h2)
82 date_ar = changes[2]
82 date_ar = changes[2]
83 date = int(float(date_ar[0]))
83 date = int(float(date_ar[0]))
84 lines = changes[4].splitlines()
84 lines = changes[4].splitlines()
85 if lines and lines[-1].startswith('committer:'):
85 if lines and lines[-1].startswith('committer:'):
86 committer = lines[-1].split(': ')[1].rstrip()
86 committer = lines[-1].split(': ')[1].rstrip()
87 else:
87 else:
88 committer = changes[1]
88 committer = changes[1]
89
89
90 print "author %s %s %s" % (changes[1], date, date_ar[1])
90 print "author %s %s %s" % (changes[1], date, date_ar[1])
91 print "committer %s %s %s" % (committer, date, date_ar[1])
91 print "committer %s %s %s" % (committer, date, date_ar[1])
92 print "revision %d" % repo.changelog.rev(n)
92 print "revision %d" % repo.changelog.rev(n)
93 print ""
93 print ""
94 if prefix != "":
94 if prefix != "":
95 print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip())
95 print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip())
96 else:
96 else:
97 print changes[4]
97 print changes[4]
98 if prefix:
98 if prefix:
99 sys.stdout.write('\0')
99 sys.stdout.write('\0')
100
100
101 def base(ui, repo, node1, node2):
101 def base(ui, repo, node1, node2):
102 """Output common ancestor information"""
102 """Output common ancestor information"""
103 node1 = repo.lookup(node1)
103 node1 = repo.lookup(node1)
104 node2 = repo.lookup(node2)
104 node2 = repo.lookup(node2)
105 n = repo.changelog.ancestor(node1, node2)
105 n = repo.changelog.ancestor(node1, node2)
106 print hg.short(n)
106 print hg.short(n)
107
107
108 def catfile(ui, repo, type=None, r=None, **opts):
108 def catfile(ui, repo, type=None, r=None, **opts):
109 """cat a specific revision"""
109 """cat a specific revision"""
110 # in stdin mode, every line except the commit is prefixed with two
110 # in stdin mode, every line except the commit is prefixed with two
111 # spaces. This way the our caller can find the commit without magic
111 # spaces. This way the our caller can find the commit without magic
112 # strings
112 # strings
113 #
113 #
114 prefix = ""
114 prefix = ""
115 if opts['stdin']:
115 if opts['stdin']:
116 try:
116 try:
117 (type, r) = raw_input().split(' ');
117 (type, r) = raw_input().split(' ');
118 prefix = " "
118 prefix = " "
119 except EOFError:
119 except EOFError:
120 return
120 return
121
121
122 else:
122 else:
123 if not type or not r:
123 if not type or not r:
124 ui.warn("cat-file: type or revision not supplied\n")
124 ui.warn("cat-file: type or revision not supplied\n")
125 commands.help_(ui, 'cat-file')
125 commands.help_(ui, 'cat-file')
126
126
127 while r:
127 while r:
128 if type != "commit":
128 if type != "commit":
129 sys.stderr.write("aborting hg cat-file only understands commits\n")
129 sys.stderr.write("aborting hg cat-file only understands commits\n")
130 sys.exit(1);
130 sys.exit(1);
131 n = repo.lookup(r)
131 n = repo.lookup(r)
132 catcommit(repo, n, prefix)
132 catcommit(repo, n, prefix)
133 if opts['stdin']:
133 if opts['stdin']:
134 try:
134 try:
135 (type, r) = raw_input().split(' ');
135 (type, r) = raw_input().split(' ');
136 except EOFError:
136 except EOFError:
137 break
137 break
138 else:
138 else:
139 break
139 break
140
140
141 # git rev-tree is a confusing thing. You can supply a number of
141 # git rev-tree is a confusing thing. You can supply a number of
142 # commit sha1s on the command line, and it walks the commit history
142 # commit sha1s on the command line, and it walks the commit history
143 # telling you which commits are reachable from the supplied ones via
143 # telling you which commits are reachable from the supplied ones via
144 # a bitmask based on arg position.
144 # a bitmask based on arg position.
145 # you can specify a commit to stop at by starting the sha1 with ^
145 # you can specify a commit to stop at by starting the sha1 with ^
146 def revtree(args, repo, full="tree", maxnr=0, parents=False):
146 def revtree(args, repo, full="tree", maxnr=0, parents=False):
147 def chlogwalk():
147 def chlogwalk():
148 ch = repo.changelog
148 ch = repo.changelog
149 count = ch.count()
149 count = ch.count()
150 i = count
150 i = count
151 l = [0] * 100
151 l = [0] * 100
152 chunk = 100
152 chunk = 100
153 while True:
153 while True:
154 if chunk > i:
154 if chunk > i:
155 chunk = i
155 chunk = i
156 i = 0
156 i = 0
157 else:
157 else:
158 i -= chunk
158 i -= chunk
159
159
160 for x in xrange(0, chunk):
160 for x in xrange(0, chunk):
161 if i + x >= count:
161 if i + x >= count:
162 l[chunk - x:] = [0] * (chunk - x)
162 l[chunk - x:] = [0] * (chunk - x)
163 break
163 break
164 if full != None:
164 if full != None:
165 l[x] = ch.read(ch.node(i + x))
165 l[x] = ch.read(ch.node(i + x))
166 else:
166 else:
167 l[x] = 1
167 l[x] = 1
168 for x in xrange(chunk-1, -1, -1):
168 for x in xrange(chunk-1, -1, -1):
169 if l[x] != 0:
169 if l[x] != 0:
170 yield (i + x, full != None and l[x] or None)
170 yield (i + x, full != None and l[x] or None)
171 if i == 0:
171 if i == 0:
172 break
172 break
173
173
174 # calculate and return the reachability bitmask for sha
174 # calculate and return the reachability bitmask for sha
175 def is_reachable(ar, reachable, sha):
175 def is_reachable(ar, reachable, sha):
176 if len(ar) == 0:
176 if len(ar) == 0:
177 return 1
177 return 1
178 mask = 0
178 mask = 0
179 for i in xrange(len(ar)):
179 for i in xrange(len(ar)):
180 if sha in reachable[i]:
180 if sha in reachable[i]:
181 mask |= 1 << i
181 mask |= 1 << i
182
182
183 return mask
183 return mask
184
184
185 reachable = []
185 reachable = []
186 stop_sha1 = []
186 stop_sha1 = []
187 want_sha1 = []
187 want_sha1 = []
188 count = 0
188 count = 0
189
189
190 # figure out which commits they are asking for and which ones they
190 # figure out which commits they are asking for and which ones they
191 # want us to stop on
191 # want us to stop on
192 for i in xrange(len(args)):
192 for i in xrange(len(args)):
193 if args[i].startswith('^'):
193 if args[i].startswith('^'):
194 s = repo.lookup(args[i][1:])
194 s = repo.lookup(args[i][1:])
195 stop_sha1.append(s)
195 stop_sha1.append(s)
196 want_sha1.append(s)
196 want_sha1.append(s)
197 elif args[i] != 'HEAD':
197 elif args[i] != 'HEAD':
198 want_sha1.append(repo.lookup(args[i]))
198 want_sha1.append(repo.lookup(args[i]))
199
199
200 # calculate the graph for the supplied commits
200 # calculate the graph for the supplied commits
201 for i in xrange(len(want_sha1)):
201 for i in xrange(len(want_sha1)):
202 reachable.append({});
202 reachable.append({});
203 n = want_sha1[i];
203 n = want_sha1[i];
204 visit = [n];
204 visit = [n];
205 reachable[i][n] = 1
205 reachable[i][n] = 1
206 while visit:
206 while visit:
207 n = visit.pop(0)
207 n = visit.pop(0)
208 if n in stop_sha1:
208 if n in stop_sha1:
209 continue
209 continue
210 for p in repo.changelog.parents(n):
210 for p in repo.changelog.parents(n):
211 if p not in reachable[i]:
211 if p not in reachable[i]:
212 reachable[i][p] = 1
212 reachable[i][p] = 1
213 visit.append(p)
213 visit.append(p)
214 if p in stop_sha1:
214 if p in stop_sha1:
215 continue
215 continue
216
216
217 # walk the repository looking for commits that are in our
217 # walk the repository looking for commits that are in our
218 # reachability graph
218 # reachability graph
219 for i, changes in chlogwalk():
219 for i, changes in chlogwalk():
220 n = repo.changelog.node(i)
220 n = repo.changelog.node(i)
221 mask = is_reachable(want_sha1, reachable, n)
221 mask = is_reachable(want_sha1, reachable, n)
222 if mask:
222 if mask:
223 parentstr = ""
223 parentstr = ""
224 if parents:
224 if parents:
225 pp = repo.changelog.parents(n)
225 pp = repo.changelog.parents(n)
226 if pp[0] != hg.nullid:
226 if pp[0] != hg.nullid:
227 parentstr += " " + hg.short(pp[0])
227 parentstr += " " + hg.short(pp[0])
228 if pp[1] != hg.nullid:
228 if pp[1] != hg.nullid:
229 parentstr += " " + hg.short(pp[1])
229 parentstr += " " + hg.short(pp[1])
230 if not full:
230 if not full:
231 print hg.short(n) + parentstr
231 print hg.short(n) + parentstr
232 elif full == "commit":
232 elif full == "commit":
233 print hg.short(n) + parentstr
233 print hg.short(n) + parentstr
234 catcommit(repo, n, ' ', changes)
234 catcommit(repo, n, ' ', changes)
235 else:
235 else:
236 (p1, p2) = repo.changelog.parents(n)
236 (p1, p2) = repo.changelog.parents(n)
237 (h, h1, h2) = map(hg.short, (n, p1, p2))
237 (h, h1, h2) = map(hg.short, (n, p1, p2))
238 (i1, i2) = map(repo.changelog.rev, (p1, p2))
238 (i1, i2) = map(repo.changelog.rev, (p1, p2))
239
239
240 date = changes[2][0]
240 date = changes[2][0]
241 print "%s %s:%s" % (date, h, mask),
241 print "%s %s:%s" % (date, h, mask),
242 mask = is_reachable(want_sha1, reachable, p1)
242 mask = is_reachable(want_sha1, reachable, p1)
243 if i1 != hg.nullrev and mask > 0:
243 if i1 != hg.nullrev and mask > 0:
244 print "%s:%s " % (h1, mask),
244 print "%s:%s " % (h1, mask),
245 mask = is_reachable(want_sha1, reachable, p2)
245 mask = is_reachable(want_sha1, reachable, p2)
246 if i2 != hg.nullrev and mask > 0:
246 if i2 != hg.nullrev and mask > 0:
247 print "%s:%s " % (h2, mask),
247 print "%s:%s " % (h2, mask),
248 print ""
248 print ""
249 if maxnr and count >= maxnr:
249 if maxnr and count >= maxnr:
250 break
250 break
251 count += 1
251 count += 1
252
252
253 def revparse(ui, repo, *revs, **opts):
253 def revparse(ui, repo, *revs, **opts):
254 """Parse given revisions"""
254 """Parse given revisions"""
255 def revstr(rev):
255 def revstr(rev):
256 if rev == 'HEAD':
256 if rev == 'HEAD':
257 rev = 'tip'
257 rev = 'tip'
258 return revlog.hex(repo.lookup(rev))
258 return revlog.hex(repo.lookup(rev))
259
259
260 for r in revs:
260 for r in revs:
261 revrange = r.split(':', 1)
261 revrange = r.split(':', 1)
262 ui.write('%s\n' % revstr(revrange[0]))
262 ui.write('%s\n' % revstr(revrange[0]))
263 if len(revrange) == 2:
263 if len(revrange) == 2:
264 ui.write('^%s\n' % revstr(revrange[1]))
264 ui.write('^%s\n' % revstr(revrange[1]))
265
265
266 # git rev-list tries to order things by date, and has the ability to stop
266 # git rev-list tries to order things by date, and has the ability to stop
267 # at a given commit without walking the whole repo. TODO add the stop
267 # at a given commit without walking the whole repo. TODO add the stop
268 # parameter
268 # parameter
269 def revlist(ui, repo, *revs, **opts):
269 def revlist(ui, repo, *revs, **opts):
270 """print revisions"""
270 """print revisions"""
271 if opts['header']:
271 if opts['header']:
272 full = "commit"
272 full = "commit"
273 else:
273 else:
274 full = None
274 full = None
275 copy = [x for x in revs]
275 copy = [x for x in revs]
276 revtree(copy, repo, full, opts['max_count'], opts['parents'])
276 revtree(copy, repo, full, opts['max_count'], opts['parents'])
277
277
278 def view(ui, repo, *etc, **opts):
278 def view(ui, repo, *etc, **opts):
279 "start interactive history viewer"
279 "start interactive history viewer"
280 os.chdir(repo.root)
280 os.chdir(repo.root)
281 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
281 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
282 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
282 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
283 ui.debug("running %s\n" % cmd)
283 ui.debug("running %s\n" % cmd)
284 os.system(cmd)
284 os.system(cmd)
285
285
286 cmdtable = {
286 cmdtable = {
287 "^view": (view,
287 "^view": (view,
288 [('l', 'limit', '', 'limit number of changes displayed')],
288 [('l', 'limit', '', 'limit number of changes displayed')],
289 'hg view [-l LIMIT] [REVRANGE]'),
289 'hg view [-l LIMIT] [REVRANGE]'),
290 "debug-diff-tree": (difftree, [('p', 'patch', None, 'generate patch'),
290 "debug-diff-tree": (difftree, [('p', 'patch', None, 'generate patch'),
291 ('r', 'recursive', None, 'recursive'),
291 ('r', 'recursive', None, 'recursive'),
292 ('P', 'pretty', None, 'pretty'),
292 ('P', 'pretty', None, 'pretty'),
293 ('s', 'stdin', None, 'stdin'),
293 ('s', 'stdin', None, 'stdin'),
294 ('C', 'copy', None, 'detect copies'),
294 ('C', 'copy', None, 'detect copies'),
295 ('S', 'search', "", 'search')],
295 ('S', 'search', "", 'search')],
296 "hg git-diff-tree [options] node1 node2 [files...]"),
296 "hg git-diff-tree [options] node1 node2 [files...]"),
297 "debug-cat-file": (catfile, [('s', 'stdin', None, 'stdin')],
297 "debug-cat-file": (catfile, [('s', 'stdin', None, 'stdin')],
298 "hg debug-cat-file [options] type file"),
298 "hg debug-cat-file [options] type file"),
299 "debug-merge-base": (base, [], "hg debug-merge-base node node"),
299 "debug-merge-base": (base, [], "hg debug-merge-base node node"),
300 'debug-rev-parse': (revparse,
300 'debug-rev-parse': (revparse,
301 [('', 'default', '', 'ignored')],
301 [('', 'default', '', 'ignored')],
302 "hg debug-rev-parse rev"),
302 "hg debug-rev-parse rev"),
303 "debug-rev-list": (revlist, [('H', 'header', None, 'header'),
303 "debug-rev-list": (revlist, [('H', 'header', None, 'header'),
304 ('t', 'topo-order', None, 'topo-order'),
304 ('t', 'topo-order', None, 'topo-order'),
305 ('p', 'parents', None, 'parents'),
305 ('p', 'parents', None, 'parents'),
306 ('n', 'max-count', 0, 'max-count')],
306 ('n', 'max-count', 0, 'max-count')],
307 "hg debug-rev-list [options] revs"),
307 "hg debug-rev-list [options] revs"),
308 }
308 }
@@ -1,2190 +1,2190 b''
1 # queue.py - patch queues for mercurial
1 # queue.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 '''patch management and development
8 '''patch management and development
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use "hg help command" for more details):
17 Common tasks (use "hg help command" for more details):
18
18
19 prepare repository to work with patches qinit
19 prepare repository to work with patches qinit
20 create new patch qnew
20 create new patch qnew
21 import existing patch qimport
21 import existing patch qimport
22
22
23 print patch series qseries
23 print patch series qseries
24 print applied patches qapplied
24 print applied patches qapplied
25 print name of top applied patch qtop
25 print name of top applied patch qtop
26
26
27 add known patch to applied stack qpush
27 add known patch to applied stack qpush
28 remove patch from applied stack qpop
28 remove patch from applied stack qpop
29 refresh contents of top applied patch qrefresh
29 refresh contents of top applied patch qrefresh
30 '''
30 '''
31
31
32 from mercurial.i18n import _
32 from mercurial.i18n import _
33 from mercurial import commands, cmdutil, hg, patch, revlog, util, changegroup
33 from mercurial import commands, cmdutil, hg, patch, revlog, util, changegroup
34 import os, sys, re, struct, traceback, errno, bz2
34 import os, sys, re, errno
35
35
36 commands.norepo += " qclone qversion"
36 commands.norepo += " qclone qversion"
37
37
38 class statusentry:
38 class statusentry:
39 def __init__(self, rev, name=None):
39 def __init__(self, rev, name=None):
40 if not name:
40 if not name:
41 fields = rev.split(':', 1)
41 fields = rev.split(':', 1)
42 if len(fields) == 2:
42 if len(fields) == 2:
43 self.rev, self.name = fields
43 self.rev, self.name = fields
44 else:
44 else:
45 self.rev, self.name = None, None
45 self.rev, self.name = None, None
46 else:
46 else:
47 self.rev, self.name = rev, name
47 self.rev, self.name = rev, name
48
48
49 def __str__(self):
49 def __str__(self):
50 return self.rev + ':' + self.name
50 return self.rev + ':' + self.name
51
51
52 class queue:
52 class queue:
53 def __init__(self, ui, path, patchdir=None):
53 def __init__(self, ui, path, patchdir=None):
54 self.basepath = path
54 self.basepath = path
55 self.path = patchdir or os.path.join(path, "patches")
55 self.path = patchdir or os.path.join(path, "patches")
56 self.opener = util.opener(self.path)
56 self.opener = util.opener(self.path)
57 self.ui = ui
57 self.ui = ui
58 self.applied = []
58 self.applied = []
59 self.full_series = []
59 self.full_series = []
60 self.applied_dirty = 0
60 self.applied_dirty = 0
61 self.series_dirty = 0
61 self.series_dirty = 0
62 self.series_path = "series"
62 self.series_path = "series"
63 self.status_path = "status"
63 self.status_path = "status"
64 self.guards_path = "guards"
64 self.guards_path = "guards"
65 self.active_guards = None
65 self.active_guards = None
66 self.guards_dirty = False
66 self.guards_dirty = False
67 self._diffopts = None
67 self._diffopts = None
68
68
69 if os.path.exists(self.join(self.series_path)):
69 if os.path.exists(self.join(self.series_path)):
70 self.full_series = self.opener(self.series_path).read().splitlines()
70 self.full_series = self.opener(self.series_path).read().splitlines()
71 self.parse_series()
71 self.parse_series()
72
72
73 if os.path.exists(self.join(self.status_path)):
73 if os.path.exists(self.join(self.status_path)):
74 lines = self.opener(self.status_path).read().splitlines()
74 lines = self.opener(self.status_path).read().splitlines()
75 self.applied = [statusentry(l) for l in lines]
75 self.applied = [statusentry(l) for l in lines]
76
76
77 def diffopts(self):
77 def diffopts(self):
78 if self._diffopts is None:
78 if self._diffopts is None:
79 self._diffopts = patch.diffopts(self.ui)
79 self._diffopts = patch.diffopts(self.ui)
80 return self._diffopts
80 return self._diffopts
81
81
82 def join(self, *p):
82 def join(self, *p):
83 return os.path.join(self.path, *p)
83 return os.path.join(self.path, *p)
84
84
85 def find_series(self, patch):
85 def find_series(self, patch):
86 pre = re.compile("(\s*)([^#]+)")
86 pre = re.compile("(\s*)([^#]+)")
87 index = 0
87 index = 0
88 for l in self.full_series:
88 for l in self.full_series:
89 m = pre.match(l)
89 m = pre.match(l)
90 if m:
90 if m:
91 s = m.group(2)
91 s = m.group(2)
92 s = s.rstrip()
92 s = s.rstrip()
93 if s == patch:
93 if s == patch:
94 return index
94 return index
95 index += 1
95 index += 1
96 return None
96 return None
97
97
98 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
98 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
99
99
100 def parse_series(self):
100 def parse_series(self):
101 self.series = []
101 self.series = []
102 self.series_guards = []
102 self.series_guards = []
103 for l in self.full_series:
103 for l in self.full_series:
104 h = l.find('#')
104 h = l.find('#')
105 if h == -1:
105 if h == -1:
106 patch = l
106 patch = l
107 comment = ''
107 comment = ''
108 elif h == 0:
108 elif h == 0:
109 continue
109 continue
110 else:
110 else:
111 patch = l[:h]
111 patch = l[:h]
112 comment = l[h:]
112 comment = l[h:]
113 patch = patch.strip()
113 patch = patch.strip()
114 if patch:
114 if patch:
115 if patch in self.series:
115 if patch in self.series:
116 raise util.Abort(_('%s appears more than once in %s') %
116 raise util.Abort(_('%s appears more than once in %s') %
117 (patch, self.join(self.series_path)))
117 (patch, self.join(self.series_path)))
118 self.series.append(patch)
118 self.series.append(patch)
119 self.series_guards.append(self.guard_re.findall(comment))
119 self.series_guards.append(self.guard_re.findall(comment))
120
120
121 def check_guard(self, guard):
121 def check_guard(self, guard):
122 bad_chars = '# \t\r\n\f'
122 bad_chars = '# \t\r\n\f'
123 first = guard[0]
123 first = guard[0]
124 for c in '-+':
124 for c in '-+':
125 if first == c:
125 if first == c:
126 return (_('guard %r starts with invalid character: %r') %
126 return (_('guard %r starts with invalid character: %r') %
127 (guard, c))
127 (guard, c))
128 for c in bad_chars:
128 for c in bad_chars:
129 if c in guard:
129 if c in guard:
130 return _('invalid character in guard %r: %r') % (guard, c)
130 return _('invalid character in guard %r: %r') % (guard, c)
131
131
132 def set_active(self, guards):
132 def set_active(self, guards):
133 for guard in guards:
133 for guard in guards:
134 bad = self.check_guard(guard)
134 bad = self.check_guard(guard)
135 if bad:
135 if bad:
136 raise util.Abort(bad)
136 raise util.Abort(bad)
137 guards = dict.fromkeys(guards).keys()
137 guards = dict.fromkeys(guards).keys()
138 guards.sort()
138 guards.sort()
139 self.ui.debug('active guards: %s\n' % ' '.join(guards))
139 self.ui.debug('active guards: %s\n' % ' '.join(guards))
140 self.active_guards = guards
140 self.active_guards = guards
141 self.guards_dirty = True
141 self.guards_dirty = True
142
142
143 def active(self):
143 def active(self):
144 if self.active_guards is None:
144 if self.active_guards is None:
145 self.active_guards = []
145 self.active_guards = []
146 try:
146 try:
147 guards = self.opener(self.guards_path).read().split()
147 guards = self.opener(self.guards_path).read().split()
148 except IOError, err:
148 except IOError, err:
149 if err.errno != errno.ENOENT: raise
149 if err.errno != errno.ENOENT: raise
150 guards = []
150 guards = []
151 for i, guard in enumerate(guards):
151 for i, guard in enumerate(guards):
152 bad = self.check_guard(guard)
152 bad = self.check_guard(guard)
153 if bad:
153 if bad:
154 self.ui.warn('%s:%d: %s\n' %
154 self.ui.warn('%s:%d: %s\n' %
155 (self.join(self.guards_path), i + 1, bad))
155 (self.join(self.guards_path), i + 1, bad))
156 else:
156 else:
157 self.active_guards.append(guard)
157 self.active_guards.append(guard)
158 return self.active_guards
158 return self.active_guards
159
159
160 def set_guards(self, idx, guards):
160 def set_guards(self, idx, guards):
161 for g in guards:
161 for g in guards:
162 if len(g) < 2:
162 if len(g) < 2:
163 raise util.Abort(_('guard %r too short') % g)
163 raise util.Abort(_('guard %r too short') % g)
164 if g[0] not in '-+':
164 if g[0] not in '-+':
165 raise util.Abort(_('guard %r starts with invalid char') % g)
165 raise util.Abort(_('guard %r starts with invalid char') % g)
166 bad = self.check_guard(g[1:])
166 bad = self.check_guard(g[1:])
167 if bad:
167 if bad:
168 raise util.Abort(bad)
168 raise util.Abort(bad)
169 drop = self.guard_re.sub('', self.full_series[idx])
169 drop = self.guard_re.sub('', self.full_series[idx])
170 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
170 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
171 self.parse_series()
171 self.parse_series()
172 self.series_dirty = True
172 self.series_dirty = True
173
173
174 def pushable(self, idx):
174 def pushable(self, idx):
175 if isinstance(idx, str):
175 if isinstance(idx, str):
176 idx = self.series.index(idx)
176 idx = self.series.index(idx)
177 patchguards = self.series_guards[idx]
177 patchguards = self.series_guards[idx]
178 if not patchguards:
178 if not patchguards:
179 return True, None
179 return True, None
180 default = False
180 default = False
181 guards = self.active()
181 guards = self.active()
182 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
182 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
183 if exactneg:
183 if exactneg:
184 return False, exactneg[0]
184 return False, exactneg[0]
185 pos = [g for g in patchguards if g[0] == '+']
185 pos = [g for g in patchguards if g[0] == '+']
186 exactpos = [g for g in pos if g[1:] in guards]
186 exactpos = [g for g in pos if g[1:] in guards]
187 if pos:
187 if pos:
188 if exactpos:
188 if exactpos:
189 return True, exactpos[0]
189 return True, exactpos[0]
190 return False, pos
190 return False, pos
191 return True, ''
191 return True, ''
192
192
193 def explain_pushable(self, idx, all_patches=False):
193 def explain_pushable(self, idx, all_patches=False):
194 write = all_patches and self.ui.write or self.ui.warn
194 write = all_patches and self.ui.write or self.ui.warn
195 if all_patches or self.ui.verbose:
195 if all_patches or self.ui.verbose:
196 if isinstance(idx, str):
196 if isinstance(idx, str):
197 idx = self.series.index(idx)
197 idx = self.series.index(idx)
198 pushable, why = self.pushable(idx)
198 pushable, why = self.pushable(idx)
199 if all_patches and pushable:
199 if all_patches and pushable:
200 if why is None:
200 if why is None:
201 write(_('allowing %s - no guards in effect\n') %
201 write(_('allowing %s - no guards in effect\n') %
202 self.series[idx])
202 self.series[idx])
203 else:
203 else:
204 if not why:
204 if not why:
205 write(_('allowing %s - no matching negative guards\n') %
205 write(_('allowing %s - no matching negative guards\n') %
206 self.series[idx])
206 self.series[idx])
207 else:
207 else:
208 write(_('allowing %s - guarded by %r\n') %
208 write(_('allowing %s - guarded by %r\n') %
209 (self.series[idx], why))
209 (self.series[idx], why))
210 if not pushable:
210 if not pushable:
211 if why:
211 if why:
212 write(_('skipping %s - guarded by %r\n') %
212 write(_('skipping %s - guarded by %r\n') %
213 (self.series[idx], why))
213 (self.series[idx], why))
214 else:
214 else:
215 write(_('skipping %s - no matching guards\n') %
215 write(_('skipping %s - no matching guards\n') %
216 self.series[idx])
216 self.series[idx])
217
217
218 def save_dirty(self):
218 def save_dirty(self):
219 def write_list(items, path):
219 def write_list(items, path):
220 fp = self.opener(path, 'w')
220 fp = self.opener(path, 'w')
221 for i in items:
221 for i in items:
222 print >> fp, i
222 print >> fp, i
223 fp.close()
223 fp.close()
224 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
224 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
225 if self.series_dirty: write_list(self.full_series, self.series_path)
225 if self.series_dirty: write_list(self.full_series, self.series_path)
226 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
226 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
227
227
228 def readheaders(self, patch):
228 def readheaders(self, patch):
229 def eatdiff(lines):
229 def eatdiff(lines):
230 while lines:
230 while lines:
231 l = lines[-1]
231 l = lines[-1]
232 if (l.startswith("diff -") or
232 if (l.startswith("diff -") or
233 l.startswith("Index:") or
233 l.startswith("Index:") or
234 l.startswith("===========")):
234 l.startswith("===========")):
235 del lines[-1]
235 del lines[-1]
236 else:
236 else:
237 break
237 break
238 def eatempty(lines):
238 def eatempty(lines):
239 while lines:
239 while lines:
240 l = lines[-1]
240 l = lines[-1]
241 if re.match('\s*$', l):
241 if re.match('\s*$', l):
242 del lines[-1]
242 del lines[-1]
243 else:
243 else:
244 break
244 break
245
245
246 pf = self.join(patch)
246 pf = self.join(patch)
247 message = []
247 message = []
248 comments = []
248 comments = []
249 user = None
249 user = None
250 date = None
250 date = None
251 format = None
251 format = None
252 subject = None
252 subject = None
253 diffstart = 0
253 diffstart = 0
254
254
255 for line in file(pf):
255 for line in file(pf):
256 line = line.rstrip()
256 line = line.rstrip()
257 if line.startswith('diff --git'):
257 if line.startswith('diff --git'):
258 diffstart = 2
258 diffstart = 2
259 break
259 break
260 if diffstart:
260 if diffstart:
261 if line.startswith('+++ '):
261 if line.startswith('+++ '):
262 diffstart = 2
262 diffstart = 2
263 break
263 break
264 if line.startswith("--- "):
264 if line.startswith("--- "):
265 diffstart = 1
265 diffstart = 1
266 continue
266 continue
267 elif format == "hgpatch":
267 elif format == "hgpatch":
268 # parse values when importing the result of an hg export
268 # parse values when importing the result of an hg export
269 if line.startswith("# User "):
269 if line.startswith("# User "):
270 user = line[7:]
270 user = line[7:]
271 elif line.startswith("# Date "):
271 elif line.startswith("# Date "):
272 date = line[7:]
272 date = line[7:]
273 elif not line.startswith("# ") and line:
273 elif not line.startswith("# ") and line:
274 message.append(line)
274 message.append(line)
275 format = None
275 format = None
276 elif line == '# HG changeset patch':
276 elif line == '# HG changeset patch':
277 format = "hgpatch"
277 format = "hgpatch"
278 elif (format != "tagdone" and (line.startswith("Subject: ") or
278 elif (format != "tagdone" and (line.startswith("Subject: ") or
279 line.startswith("subject: "))):
279 line.startswith("subject: "))):
280 subject = line[9:]
280 subject = line[9:]
281 format = "tag"
281 format = "tag"
282 elif (format != "tagdone" and (line.startswith("From: ") or
282 elif (format != "tagdone" and (line.startswith("From: ") or
283 line.startswith("from: "))):
283 line.startswith("from: "))):
284 user = line[6:]
284 user = line[6:]
285 format = "tag"
285 format = "tag"
286 elif format == "tag" and line == "":
286 elif format == "tag" and line == "":
287 # when looking for tags (subject: from: etc) they
287 # when looking for tags (subject: from: etc) they
288 # end once you find a blank line in the source
288 # end once you find a blank line in the source
289 format = "tagdone"
289 format = "tagdone"
290 elif message or line:
290 elif message or line:
291 message.append(line)
291 message.append(line)
292 comments.append(line)
292 comments.append(line)
293
293
294 eatdiff(message)
294 eatdiff(message)
295 eatdiff(comments)
295 eatdiff(comments)
296 eatempty(message)
296 eatempty(message)
297 eatempty(comments)
297 eatempty(comments)
298
298
299 # make sure message isn't empty
299 # make sure message isn't empty
300 if format and format.startswith("tag") and subject:
300 if format and format.startswith("tag") and subject:
301 message.insert(0, "")
301 message.insert(0, "")
302 message.insert(0, subject)
302 message.insert(0, subject)
303 return (message, comments, user, date, diffstart > 1)
303 return (message, comments, user, date, diffstart > 1)
304
304
305 def printdiff(self, repo, node1, node2=None, files=None,
305 def printdiff(self, repo, node1, node2=None, files=None,
306 fp=None, changes=None, opts={}):
306 fp=None, changes=None, opts={}):
307 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
307 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
308
308
309 patch.diff(repo, node1, node2, fns, match=matchfn,
309 patch.diff(repo, node1, node2, fns, match=matchfn,
310 fp=fp, changes=changes, opts=self.diffopts())
310 fp=fp, changes=changes, opts=self.diffopts())
311
311
312 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
312 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
313 # first try just applying the patch
313 # first try just applying the patch
314 (err, n) = self.apply(repo, [ patch ], update_status=False,
314 (err, n) = self.apply(repo, [ patch ], update_status=False,
315 strict=True, merge=rev, wlock=wlock)
315 strict=True, merge=rev, wlock=wlock)
316
316
317 if err == 0:
317 if err == 0:
318 return (err, n)
318 return (err, n)
319
319
320 if n is None:
320 if n is None:
321 raise util.Abort(_("apply failed for patch %s") % patch)
321 raise util.Abort(_("apply failed for patch %s") % patch)
322
322
323 self.ui.warn("patch didn't work out, merging %s\n" % patch)
323 self.ui.warn("patch didn't work out, merging %s\n" % patch)
324
324
325 # apply failed, strip away that rev and merge.
325 # apply failed, strip away that rev and merge.
326 hg.clean(repo, head, wlock=wlock)
326 hg.clean(repo, head, wlock=wlock)
327 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
327 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
328
328
329 c = repo.changelog.read(rev)
329 c = repo.changelog.read(rev)
330 ret = hg.merge(repo, rev, wlock=wlock)
330 ret = hg.merge(repo, rev, wlock=wlock)
331 if ret:
331 if ret:
332 raise util.Abort(_("update returned %d") % ret)
332 raise util.Abort(_("update returned %d") % ret)
333 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
333 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
334 if n == None:
334 if n == None:
335 raise util.Abort(_("repo commit failed"))
335 raise util.Abort(_("repo commit failed"))
336 try:
336 try:
337 message, comments, user, date, patchfound = mergeq.readheaders(patch)
337 message, comments, user, date, patchfound = mergeq.readheaders(patch)
338 except:
338 except:
339 raise util.Abort(_("unable to read %s") % patch)
339 raise util.Abort(_("unable to read %s") % patch)
340
340
341 patchf = self.opener(patch, "w")
341 patchf = self.opener(patch, "w")
342 if comments:
342 if comments:
343 comments = "\n".join(comments) + '\n\n'
343 comments = "\n".join(comments) + '\n\n'
344 patchf.write(comments)
344 patchf.write(comments)
345 self.printdiff(repo, head, n, fp=patchf)
345 self.printdiff(repo, head, n, fp=patchf)
346 patchf.close()
346 patchf.close()
347 return (0, n)
347 return (0, n)
348
348
349 def qparents(self, repo, rev=None):
349 def qparents(self, repo, rev=None):
350 if rev is None:
350 if rev is None:
351 (p1, p2) = repo.dirstate.parents()
351 (p1, p2) = repo.dirstate.parents()
352 if p2 == revlog.nullid:
352 if p2 == revlog.nullid:
353 return p1
353 return p1
354 if len(self.applied) == 0:
354 if len(self.applied) == 0:
355 return None
355 return None
356 return revlog.bin(self.applied[-1].rev)
356 return revlog.bin(self.applied[-1].rev)
357 pp = repo.changelog.parents(rev)
357 pp = repo.changelog.parents(rev)
358 if pp[1] != revlog.nullid:
358 if pp[1] != revlog.nullid:
359 arevs = [ x.rev for x in self.applied ]
359 arevs = [ x.rev for x in self.applied ]
360 p0 = revlog.hex(pp[0])
360 p0 = revlog.hex(pp[0])
361 p1 = revlog.hex(pp[1])
361 p1 = revlog.hex(pp[1])
362 if p0 in arevs:
362 if p0 in arevs:
363 return pp[0]
363 return pp[0]
364 if p1 in arevs:
364 if p1 in arevs:
365 return pp[1]
365 return pp[1]
366 return pp[0]
366 return pp[0]
367
367
368 def mergepatch(self, repo, mergeq, series, wlock):
368 def mergepatch(self, repo, mergeq, series, wlock):
369 if len(self.applied) == 0:
369 if len(self.applied) == 0:
370 # each of the patches merged in will have two parents. This
370 # each of the patches merged in will have two parents. This
371 # can confuse the qrefresh, qdiff, and strip code because it
371 # can confuse the qrefresh, qdiff, and strip code because it
372 # needs to know which parent is actually in the patch queue.
372 # needs to know which parent is actually in the patch queue.
373 # so, we insert a merge marker with only one parent. This way
373 # so, we insert a merge marker with only one parent. This way
374 # the first patch in the queue is never a merge patch
374 # the first patch in the queue is never a merge patch
375 #
375 #
376 pname = ".hg.patches.merge.marker"
376 pname = ".hg.patches.merge.marker"
377 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
377 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
378 wlock=wlock)
378 wlock=wlock)
379 self.applied.append(statusentry(revlog.hex(n), pname))
379 self.applied.append(statusentry(revlog.hex(n), pname))
380 self.applied_dirty = 1
380 self.applied_dirty = 1
381
381
382 head = self.qparents(repo)
382 head = self.qparents(repo)
383
383
384 for patch in series:
384 for patch in series:
385 patch = mergeq.lookup(patch, strict=True)
385 patch = mergeq.lookup(patch, strict=True)
386 if not patch:
386 if not patch:
387 self.ui.warn("patch %s does not exist\n" % patch)
387 self.ui.warn("patch %s does not exist\n" % patch)
388 return (1, None)
388 return (1, None)
389 pushable, reason = self.pushable(patch)
389 pushable, reason = self.pushable(patch)
390 if not pushable:
390 if not pushable:
391 self.explain_pushable(patch, all_patches=True)
391 self.explain_pushable(patch, all_patches=True)
392 continue
392 continue
393 info = mergeq.isapplied(patch)
393 info = mergeq.isapplied(patch)
394 if not info:
394 if not info:
395 self.ui.warn("patch %s is not applied\n" % patch)
395 self.ui.warn("patch %s is not applied\n" % patch)
396 return (1, None)
396 return (1, None)
397 rev = revlog.bin(info[1])
397 rev = revlog.bin(info[1])
398 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
398 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
399 if head:
399 if head:
400 self.applied.append(statusentry(revlog.hex(head), patch))
400 self.applied.append(statusentry(revlog.hex(head), patch))
401 self.applied_dirty = 1
401 self.applied_dirty = 1
402 if err:
402 if err:
403 return (err, head)
403 return (err, head)
404 return (0, head)
404 return (0, head)
405
405
406 def patch(self, repo, patchfile):
406 def patch(self, repo, patchfile):
407 '''Apply patchfile to the working directory.
407 '''Apply patchfile to the working directory.
408 patchfile: file name of patch'''
408 patchfile: file name of patch'''
409 files = {}
409 files = {}
410 try:
410 try:
411 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
411 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
412 files=files)
412 files=files)
413 except Exception, inst:
413 except Exception, inst:
414 self.ui.note(str(inst) + '\n')
414 self.ui.note(str(inst) + '\n')
415 if not self.ui.verbose:
415 if not self.ui.verbose:
416 self.ui.warn("patch failed, unable to continue (try -v)\n")
416 self.ui.warn("patch failed, unable to continue (try -v)\n")
417 return (False, files, False)
417 return (False, files, False)
418
418
419 return (True, files, fuzz)
419 return (True, files, fuzz)
420
420
421 def apply(self, repo, series, list=False, update_status=True,
421 def apply(self, repo, series, list=False, update_status=True,
422 strict=False, patchdir=None, merge=None, wlock=None):
422 strict=False, patchdir=None, merge=None, wlock=None):
423 # TODO unify with commands.py
423 # TODO unify with commands.py
424 if not patchdir:
424 if not patchdir:
425 patchdir = self.path
425 patchdir = self.path
426 err = 0
426 err = 0
427 if not wlock:
427 if not wlock:
428 wlock = repo.wlock()
428 wlock = repo.wlock()
429 lock = repo.lock()
429 lock = repo.lock()
430 tr = repo.transaction()
430 tr = repo.transaction()
431 n = None
431 n = None
432 for patchname in series:
432 for patchname in series:
433 pushable, reason = self.pushable(patchname)
433 pushable, reason = self.pushable(patchname)
434 if not pushable:
434 if not pushable:
435 self.explain_pushable(patchname, all_patches=True)
435 self.explain_pushable(patchname, all_patches=True)
436 continue
436 continue
437 self.ui.warn("applying %s\n" % patchname)
437 self.ui.warn("applying %s\n" % patchname)
438 pf = os.path.join(patchdir, patchname)
438 pf = os.path.join(patchdir, patchname)
439
439
440 try:
440 try:
441 message, comments, user, date, patchfound = self.readheaders(patchname)
441 message, comments, user, date, patchfound = self.readheaders(patchname)
442 except:
442 except:
443 self.ui.warn("Unable to read %s\n" % patchname)
443 self.ui.warn("Unable to read %s\n" % patchname)
444 err = 1
444 err = 1
445 break
445 break
446
446
447 if not message:
447 if not message:
448 message = "imported patch %s\n" % patchname
448 message = "imported patch %s\n" % patchname
449 else:
449 else:
450 if list:
450 if list:
451 message.append("\nimported patch %s" % patchname)
451 message.append("\nimported patch %s" % patchname)
452 message = '\n'.join(message)
452 message = '\n'.join(message)
453
453
454 (patcherr, files, fuzz) = self.patch(repo, pf)
454 (patcherr, files, fuzz) = self.patch(repo, pf)
455 patcherr = not patcherr
455 patcherr = not patcherr
456
456
457 if merge and files:
457 if merge and files:
458 # Mark as merged and update dirstate parent info
458 # Mark as merged and update dirstate parent info
459 repo.dirstate.update(repo.dirstate.filterfiles(files.keys()), 'm')
459 repo.dirstate.update(repo.dirstate.filterfiles(files.keys()), 'm')
460 p1, p2 = repo.dirstate.parents()
460 p1, p2 = repo.dirstate.parents()
461 repo.dirstate.setparents(p1, merge)
461 repo.dirstate.setparents(p1, merge)
462 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
462 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
463 n = repo.commit(files, message, user, date, force=1, lock=lock,
463 n = repo.commit(files, message, user, date, force=1, lock=lock,
464 wlock=wlock)
464 wlock=wlock)
465
465
466 if n == None:
466 if n == None:
467 raise util.Abort(_("repo commit failed"))
467 raise util.Abort(_("repo commit failed"))
468
468
469 if update_status:
469 if update_status:
470 self.applied.append(statusentry(revlog.hex(n), patchname))
470 self.applied.append(statusentry(revlog.hex(n), patchname))
471
471
472 if patcherr:
472 if patcherr:
473 if not patchfound:
473 if not patchfound:
474 self.ui.warn("patch %s is empty\n" % patchname)
474 self.ui.warn("patch %s is empty\n" % patchname)
475 err = 0
475 err = 0
476 else:
476 else:
477 self.ui.warn("patch failed, rejects left in working dir\n")
477 self.ui.warn("patch failed, rejects left in working dir\n")
478 err = 1
478 err = 1
479 break
479 break
480
480
481 if fuzz and strict:
481 if fuzz and strict:
482 self.ui.warn("fuzz found when applying patch, stopping\n")
482 self.ui.warn("fuzz found when applying patch, stopping\n")
483 err = 1
483 err = 1
484 break
484 break
485 tr.close()
485 tr.close()
486 return (err, n)
486 return (err, n)
487
487
488 def delete(self, repo, patches, opts):
488 def delete(self, repo, patches, opts):
489 realpatches = []
489 realpatches = []
490 for patch in patches:
490 for patch in patches:
491 patch = self.lookup(patch, strict=True)
491 patch = self.lookup(patch, strict=True)
492 info = self.isapplied(patch)
492 info = self.isapplied(patch)
493 if info:
493 if info:
494 raise util.Abort(_("cannot delete applied patch %s") % patch)
494 raise util.Abort(_("cannot delete applied patch %s") % patch)
495 if patch not in self.series:
495 if patch not in self.series:
496 raise util.Abort(_("patch %s not in series file") % patch)
496 raise util.Abort(_("patch %s not in series file") % patch)
497 realpatches.append(patch)
497 realpatches.append(patch)
498
498
499 appliedbase = 0
499 appliedbase = 0
500 if opts.get('rev'):
500 if opts.get('rev'):
501 if not self.applied:
501 if not self.applied:
502 raise util.Abort(_('no patches applied'))
502 raise util.Abort(_('no patches applied'))
503 revs = cmdutil.revrange(repo, opts['rev'])
503 revs = cmdutil.revrange(repo, opts['rev'])
504 if len(revs) > 1 and revs[0] > revs[1]:
504 if len(revs) > 1 and revs[0] > revs[1]:
505 revs.reverse()
505 revs.reverse()
506 for rev in revs:
506 for rev in revs:
507 if appliedbase >= len(self.applied):
507 if appliedbase >= len(self.applied):
508 raise util.Abort(_("revision %d is not managed") % rev)
508 raise util.Abort(_("revision %d is not managed") % rev)
509
509
510 base = revlog.bin(self.applied[appliedbase].rev)
510 base = revlog.bin(self.applied[appliedbase].rev)
511 node = repo.changelog.node(rev)
511 node = repo.changelog.node(rev)
512 if node != base:
512 if node != base:
513 raise util.Abort(_("cannot delete revision %d above "
513 raise util.Abort(_("cannot delete revision %d above "
514 "applied patches") % rev)
514 "applied patches") % rev)
515 realpatches.append(self.applied[appliedbase].name)
515 realpatches.append(self.applied[appliedbase].name)
516 appliedbase += 1
516 appliedbase += 1
517
517
518 if not opts.get('keep'):
518 if not opts.get('keep'):
519 r = self.qrepo()
519 r = self.qrepo()
520 if r:
520 if r:
521 r.remove(realpatches, True)
521 r.remove(realpatches, True)
522 else:
522 else:
523 for p in realpatches:
523 for p in realpatches:
524 os.unlink(self.join(p))
524 os.unlink(self.join(p))
525
525
526 if appliedbase:
526 if appliedbase:
527 del self.applied[:appliedbase]
527 del self.applied[:appliedbase]
528 self.applied_dirty = 1
528 self.applied_dirty = 1
529 indices = [self.find_series(p) for p in realpatches]
529 indices = [self.find_series(p) for p in realpatches]
530 indices.sort()
530 indices.sort()
531 for i in indices[-1::-1]:
531 for i in indices[-1::-1]:
532 del self.full_series[i]
532 del self.full_series[i]
533 self.parse_series()
533 self.parse_series()
534 self.series_dirty = 1
534 self.series_dirty = 1
535
535
536 def check_toppatch(self, repo):
536 def check_toppatch(self, repo):
537 if len(self.applied) > 0:
537 if len(self.applied) > 0:
538 top = revlog.bin(self.applied[-1].rev)
538 top = revlog.bin(self.applied[-1].rev)
539 pp = repo.dirstate.parents()
539 pp = repo.dirstate.parents()
540 if top not in pp:
540 if top not in pp:
541 raise util.Abort(_("queue top not at same revision as working directory"))
541 raise util.Abort(_("queue top not at same revision as working directory"))
542 return top
542 return top
543 return None
543 return None
544 def check_localchanges(self, repo, force=False, refresh=True):
544 def check_localchanges(self, repo, force=False, refresh=True):
545 m, a, r, d = repo.status()[:4]
545 m, a, r, d = repo.status()[:4]
546 if m or a or r or d:
546 if m or a or r or d:
547 if not force:
547 if not force:
548 if refresh:
548 if refresh:
549 raise util.Abort(_("local changes found, refresh first"))
549 raise util.Abort(_("local changes found, refresh first"))
550 else:
550 else:
551 raise util.Abort(_("local changes found"))
551 raise util.Abort(_("local changes found"))
552 return m, a, r, d
552 return m, a, r, d
553 def new(self, repo, patch, msg=None, force=None):
553 def new(self, repo, patch, msg=None, force=None):
554 if os.path.exists(self.join(patch)):
554 if os.path.exists(self.join(patch)):
555 raise util.Abort(_('patch "%s" already exists') % patch)
555 raise util.Abort(_('patch "%s" already exists') % patch)
556 m, a, r, d = self.check_localchanges(repo, force)
556 m, a, r, d = self.check_localchanges(repo, force)
557 commitfiles = m + a + r
557 commitfiles = m + a + r
558 self.check_toppatch(repo)
558 self.check_toppatch(repo)
559 wlock = repo.wlock()
559 wlock = repo.wlock()
560 insert = self.full_series_end()
560 insert = self.full_series_end()
561 if msg:
561 if msg:
562 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
562 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
563 wlock=wlock)
563 wlock=wlock)
564 else:
564 else:
565 n = repo.commit(commitfiles,
565 n = repo.commit(commitfiles,
566 "New patch: %s" % patch, force=True, wlock=wlock)
566 "New patch: %s" % patch, force=True, wlock=wlock)
567 if n == None:
567 if n == None:
568 raise util.Abort(_("repo commit failed"))
568 raise util.Abort(_("repo commit failed"))
569 self.full_series[insert:insert] = [patch]
569 self.full_series[insert:insert] = [patch]
570 self.applied.append(statusentry(revlog.hex(n), patch))
570 self.applied.append(statusentry(revlog.hex(n), patch))
571 self.parse_series()
571 self.parse_series()
572 self.series_dirty = 1
572 self.series_dirty = 1
573 self.applied_dirty = 1
573 self.applied_dirty = 1
574 p = self.opener(patch, "w")
574 p = self.opener(patch, "w")
575 if msg:
575 if msg:
576 msg = msg + "\n"
576 msg = msg + "\n"
577 p.write(msg)
577 p.write(msg)
578 p.close()
578 p.close()
579 wlock = None
579 wlock = None
580 r = self.qrepo()
580 r = self.qrepo()
581 if r: r.add([patch])
581 if r: r.add([patch])
582 if commitfiles:
582 if commitfiles:
583 self.refresh(repo, short=True)
583 self.refresh(repo, short=True)
584
584
585 def strip(self, repo, rev, update=True, backup="all", wlock=None):
585 def strip(self, repo, rev, update=True, backup="all", wlock=None):
586 def limitheads(chlog, stop):
586 def limitheads(chlog, stop):
587 """return the list of all nodes that have no children"""
587 """return the list of all nodes that have no children"""
588 p = {}
588 p = {}
589 h = []
589 h = []
590 stoprev = 0
590 stoprev = 0
591 if stop in chlog.nodemap:
591 if stop in chlog.nodemap:
592 stoprev = chlog.rev(stop)
592 stoprev = chlog.rev(stop)
593
593
594 for r in xrange(chlog.count() - 1, -1, -1):
594 for r in xrange(chlog.count() - 1, -1, -1):
595 n = chlog.node(r)
595 n = chlog.node(r)
596 if n not in p:
596 if n not in p:
597 h.append(n)
597 h.append(n)
598 if n == stop:
598 if n == stop:
599 break
599 break
600 if r < stoprev:
600 if r < stoprev:
601 break
601 break
602 for pn in chlog.parents(n):
602 for pn in chlog.parents(n):
603 p[pn] = 1
603 p[pn] = 1
604 return h
604 return h
605
605
606 def bundle(cg):
606 def bundle(cg):
607 backupdir = repo.join("strip-backup")
607 backupdir = repo.join("strip-backup")
608 if not os.path.isdir(backupdir):
608 if not os.path.isdir(backupdir):
609 os.mkdir(backupdir)
609 os.mkdir(backupdir)
610 name = os.path.join(backupdir, "%s" % revlog.short(rev))
610 name = os.path.join(backupdir, "%s" % revlog.short(rev))
611 name = savename(name)
611 name = savename(name)
612 self.ui.warn("saving bundle to %s\n" % name)
612 self.ui.warn("saving bundle to %s\n" % name)
613 return changegroup.writebundle(cg, name, "HG10BZ")
613 return changegroup.writebundle(cg, name, "HG10BZ")
614
614
615 def stripall(rev, revnum):
615 def stripall(rev, revnum):
616 cl = repo.changelog
616 cl = repo.changelog
617 c = cl.read(rev)
617 c = cl.read(rev)
618 mm = repo.manifest.read(c[0])
618 mm = repo.manifest.read(c[0])
619 seen = {}
619 seen = {}
620
620
621 for x in xrange(revnum, cl.count()):
621 for x in xrange(revnum, cl.count()):
622 c = cl.read(cl.node(x))
622 c = cl.read(cl.node(x))
623 for f in c[3]:
623 for f in c[3]:
624 if f in seen:
624 if f in seen:
625 continue
625 continue
626 seen[f] = 1
626 seen[f] = 1
627 if f in mm:
627 if f in mm:
628 filerev = mm[f]
628 filerev = mm[f]
629 else:
629 else:
630 filerev = 0
630 filerev = 0
631 seen[f] = filerev
631 seen[f] = filerev
632 # we go in two steps here so the strip loop happens in a
632 # we go in two steps here so the strip loop happens in a
633 # sensible order. When stripping many files, this helps keep
633 # sensible order. When stripping many files, this helps keep
634 # our disk access patterns under control.
634 # our disk access patterns under control.
635 seen_list = seen.keys()
635 seen_list = seen.keys()
636 seen_list.sort()
636 seen_list.sort()
637 for f in seen_list:
637 for f in seen_list:
638 ff = repo.file(f)
638 ff = repo.file(f)
639 filerev = seen[f]
639 filerev = seen[f]
640 if filerev != 0:
640 if filerev != 0:
641 if filerev in ff.nodemap:
641 if filerev in ff.nodemap:
642 filerev = ff.rev(filerev)
642 filerev = ff.rev(filerev)
643 else:
643 else:
644 filerev = 0
644 filerev = 0
645 ff.strip(filerev, revnum)
645 ff.strip(filerev, revnum)
646
646
647 if not wlock:
647 if not wlock:
648 wlock = repo.wlock()
648 wlock = repo.wlock()
649 lock = repo.lock()
649 lock = repo.lock()
650 chlog = repo.changelog
650 chlog = repo.changelog
651 # TODO delete the undo files, and handle undo of merge sets
651 # TODO delete the undo files, and handle undo of merge sets
652 pp = chlog.parents(rev)
652 pp = chlog.parents(rev)
653 revnum = chlog.rev(rev)
653 revnum = chlog.rev(rev)
654
654
655 if update:
655 if update:
656 self.check_localchanges(repo, refresh=False)
656 self.check_localchanges(repo, refresh=False)
657 urev = self.qparents(repo, rev)
657 urev = self.qparents(repo, rev)
658 hg.clean(repo, urev, wlock=wlock)
658 hg.clean(repo, urev, wlock=wlock)
659 repo.dirstate.write()
659 repo.dirstate.write()
660
660
661 # save is a list of all the branches we are truncating away
661 # save is a list of all the branches we are truncating away
662 # that we actually want to keep. changegroup will be used
662 # that we actually want to keep. changegroup will be used
663 # to preserve them and add them back after the truncate
663 # to preserve them and add them back after the truncate
664 saveheads = []
664 saveheads = []
665 savebases = {}
665 savebases = {}
666
666
667 heads = limitheads(chlog, rev)
667 heads = limitheads(chlog, rev)
668 seen = {}
668 seen = {}
669
669
670 # search through all the heads, finding those where the revision
670 # search through all the heads, finding those where the revision
671 # we want to strip away is an ancestor. Also look for merges
671 # we want to strip away is an ancestor. Also look for merges
672 # that might be turned into new heads by the strip.
672 # that might be turned into new heads by the strip.
673 while heads:
673 while heads:
674 h = heads.pop()
674 h = heads.pop()
675 n = h
675 n = h
676 while True:
676 while True:
677 seen[n] = 1
677 seen[n] = 1
678 pp = chlog.parents(n)
678 pp = chlog.parents(n)
679 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
679 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
680 if pp[1] not in seen:
680 if pp[1] not in seen:
681 heads.append(pp[1])
681 heads.append(pp[1])
682 if pp[0] == revlog.nullid:
682 if pp[0] == revlog.nullid:
683 break
683 break
684 if chlog.rev(pp[0]) < revnum:
684 if chlog.rev(pp[0]) < revnum:
685 break
685 break
686 n = pp[0]
686 n = pp[0]
687 if n == rev:
687 if n == rev:
688 break
688 break
689 r = chlog.reachable(h, rev)
689 r = chlog.reachable(h, rev)
690 if rev not in r:
690 if rev not in r:
691 saveheads.append(h)
691 saveheads.append(h)
692 for x in r:
692 for x in r:
693 if chlog.rev(x) > revnum:
693 if chlog.rev(x) > revnum:
694 savebases[x] = 1
694 savebases[x] = 1
695
695
696 # create a changegroup for all the branches we need to keep
696 # create a changegroup for all the branches we need to keep
697 if backup == "all":
697 if backup == "all":
698 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
698 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
699 bundle(backupch)
699 bundle(backupch)
700 if saveheads:
700 if saveheads:
701 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
701 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
702 chgrpfile = bundle(backupch)
702 chgrpfile = bundle(backupch)
703 chgrpfile = 'file:%s' % chgrpfile
703 chgrpfile = 'file:%s' % chgrpfile
704
704
705 stripall(rev, revnum)
705 stripall(rev, revnum)
706
706
707 change = chlog.read(rev)
707 change = chlog.read(rev)
708 chlog.strip(revnum, revnum)
708 chlog.strip(revnum, revnum)
709 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
709 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
710 if saveheads:
710 if saveheads:
711 self.ui.status("adding branch\n")
711 self.ui.status("adding branch\n")
712 commands.unbundle(self.ui, repo, chgrpfile, update=False)
712 commands.unbundle(self.ui, repo, chgrpfile, update=False)
713 if backup != "strip":
713 if backup != "strip":
714 os.unlink(chgrpfile)
714 os.unlink(chgrpfile)
715
715
716 def isapplied(self, patch):
716 def isapplied(self, patch):
717 """returns (index, rev, patch)"""
717 """returns (index, rev, patch)"""
718 for i in xrange(len(self.applied)):
718 for i in xrange(len(self.applied)):
719 a = self.applied[i]
719 a = self.applied[i]
720 if a.name == patch:
720 if a.name == patch:
721 return (i, a.rev, a.name)
721 return (i, a.rev, a.name)
722 return None
722 return None
723
723
724 # if the exact patch name does not exist, we try a few
724 # if the exact patch name does not exist, we try a few
725 # variations. If strict is passed, we try only #1
725 # variations. If strict is passed, we try only #1
726 #
726 #
727 # 1) a number to indicate an offset in the series file
727 # 1) a number to indicate an offset in the series file
728 # 2) a unique substring of the patch name was given
728 # 2) a unique substring of the patch name was given
729 # 3) patchname[-+]num to indicate an offset in the series file
729 # 3) patchname[-+]num to indicate an offset in the series file
730 def lookup(self, patch, strict=False):
730 def lookup(self, patch, strict=False):
731 patch = patch and str(patch)
731 patch = patch and str(patch)
732
732
733 def partial_name(s):
733 def partial_name(s):
734 if s in self.series:
734 if s in self.series:
735 return s
735 return s
736 matches = [x for x in self.series if s in x]
736 matches = [x for x in self.series if s in x]
737 if len(matches) > 1:
737 if len(matches) > 1:
738 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
738 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
739 for m in matches:
739 for m in matches:
740 self.ui.warn(' %s\n' % m)
740 self.ui.warn(' %s\n' % m)
741 return None
741 return None
742 if matches:
742 if matches:
743 return matches[0]
743 return matches[0]
744 if len(self.series) > 0 and len(self.applied) > 0:
744 if len(self.series) > 0 and len(self.applied) > 0:
745 if s == 'qtip':
745 if s == 'qtip':
746 return self.series[self.series_end(True)-1]
746 return self.series[self.series_end(True)-1]
747 if s == 'qbase':
747 if s == 'qbase':
748 return self.series[0]
748 return self.series[0]
749 return None
749 return None
750 if patch == None:
750 if patch == None:
751 return None
751 return None
752
752
753 # we don't want to return a partial match until we make
753 # we don't want to return a partial match until we make
754 # sure the file name passed in does not exist (checked below)
754 # sure the file name passed in does not exist (checked below)
755 res = partial_name(patch)
755 res = partial_name(patch)
756 if res and res == patch:
756 if res and res == patch:
757 return res
757 return res
758
758
759 if not os.path.isfile(self.join(patch)):
759 if not os.path.isfile(self.join(patch)):
760 try:
760 try:
761 sno = int(patch)
761 sno = int(patch)
762 except(ValueError, OverflowError):
762 except(ValueError, OverflowError):
763 pass
763 pass
764 else:
764 else:
765 if sno < len(self.series):
765 if sno < len(self.series):
766 return self.series[sno]
766 return self.series[sno]
767 if not strict:
767 if not strict:
768 # return any partial match made above
768 # return any partial match made above
769 if res:
769 if res:
770 return res
770 return res
771 minus = patch.rfind('-')
771 minus = patch.rfind('-')
772 if minus >= 0:
772 if minus >= 0:
773 res = partial_name(patch[:minus])
773 res = partial_name(patch[:minus])
774 if res:
774 if res:
775 i = self.series.index(res)
775 i = self.series.index(res)
776 try:
776 try:
777 off = int(patch[minus+1:] or 1)
777 off = int(patch[minus+1:] or 1)
778 except(ValueError, OverflowError):
778 except(ValueError, OverflowError):
779 pass
779 pass
780 else:
780 else:
781 if i - off >= 0:
781 if i - off >= 0:
782 return self.series[i - off]
782 return self.series[i - off]
783 plus = patch.rfind('+')
783 plus = patch.rfind('+')
784 if plus >= 0:
784 if plus >= 0:
785 res = partial_name(patch[:plus])
785 res = partial_name(patch[:plus])
786 if res:
786 if res:
787 i = self.series.index(res)
787 i = self.series.index(res)
788 try:
788 try:
789 off = int(patch[plus+1:] or 1)
789 off = int(patch[plus+1:] or 1)
790 except(ValueError, OverflowError):
790 except(ValueError, OverflowError):
791 pass
791 pass
792 else:
792 else:
793 if i + off < len(self.series):
793 if i + off < len(self.series):
794 return self.series[i + off]
794 return self.series[i + off]
795 raise util.Abort(_("patch %s not in series") % patch)
795 raise util.Abort(_("patch %s not in series") % patch)
796
796
797 def push(self, repo, patch=None, force=False, list=False,
797 def push(self, repo, patch=None, force=False, list=False,
798 mergeq=None, wlock=None):
798 mergeq=None, wlock=None):
799 if not wlock:
799 if not wlock:
800 wlock = repo.wlock()
800 wlock = repo.wlock()
801 patch = self.lookup(patch)
801 patch = self.lookup(patch)
802 if patch and self.isapplied(patch):
802 if patch and self.isapplied(patch):
803 raise util.Abort(_("patch %s is already applied") % patch)
803 raise util.Abort(_("patch %s is already applied") % patch)
804 if self.series_end() == len(self.series):
804 if self.series_end() == len(self.series):
805 raise util.Abort(_("patch series fully applied"))
805 raise util.Abort(_("patch series fully applied"))
806 if not force:
806 if not force:
807 self.check_localchanges(repo)
807 self.check_localchanges(repo)
808
808
809 self.applied_dirty = 1;
809 self.applied_dirty = 1;
810 start = self.series_end()
810 start = self.series_end()
811 if start > 0:
811 if start > 0:
812 self.check_toppatch(repo)
812 self.check_toppatch(repo)
813 if not patch:
813 if not patch:
814 patch = self.series[start]
814 patch = self.series[start]
815 end = start + 1
815 end = start + 1
816 else:
816 else:
817 end = self.series.index(patch, start) + 1
817 end = self.series.index(patch, start) + 1
818 s = self.series[start:end]
818 s = self.series[start:end]
819 if mergeq:
819 if mergeq:
820 ret = self.mergepatch(repo, mergeq, s, wlock)
820 ret = self.mergepatch(repo, mergeq, s, wlock)
821 else:
821 else:
822 ret = self.apply(repo, s, list, wlock=wlock)
822 ret = self.apply(repo, s, list, wlock=wlock)
823 top = self.applied[-1].name
823 top = self.applied[-1].name
824 if ret[0]:
824 if ret[0]:
825 self.ui.write("Errors during apply, please fix and refresh %s\n" %
825 self.ui.write("Errors during apply, please fix and refresh %s\n" %
826 top)
826 top)
827 else:
827 else:
828 self.ui.write("Now at: %s\n" % top)
828 self.ui.write("Now at: %s\n" % top)
829 return ret[0]
829 return ret[0]
830
830
831 def pop(self, repo, patch=None, force=False, update=True, all=False,
831 def pop(self, repo, patch=None, force=False, update=True, all=False,
832 wlock=None):
832 wlock=None):
833 def getfile(f, rev):
833 def getfile(f, rev):
834 t = repo.file(f).read(rev)
834 t = repo.file(f).read(rev)
835 try:
835 try:
836 repo.wfile(f, "w").write(t)
836 repo.wfile(f, "w").write(t)
837 except IOError:
837 except IOError:
838 try:
838 try:
839 os.makedirs(os.path.dirname(repo.wjoin(f)))
839 os.makedirs(os.path.dirname(repo.wjoin(f)))
840 except OSError, err:
840 except OSError, err:
841 if err.errno != errno.EEXIST: raise
841 if err.errno != errno.EEXIST: raise
842 repo.wfile(f, "w").write(t)
842 repo.wfile(f, "w").write(t)
843
843
844 if not wlock:
844 if not wlock:
845 wlock = repo.wlock()
845 wlock = repo.wlock()
846 if patch:
846 if patch:
847 # index, rev, patch
847 # index, rev, patch
848 info = self.isapplied(patch)
848 info = self.isapplied(patch)
849 if not info:
849 if not info:
850 patch = self.lookup(patch)
850 patch = self.lookup(patch)
851 info = self.isapplied(patch)
851 info = self.isapplied(patch)
852 if not info:
852 if not info:
853 raise util.Abort(_("patch %s is not applied") % patch)
853 raise util.Abort(_("patch %s is not applied") % patch)
854 if len(self.applied) == 0:
854 if len(self.applied) == 0:
855 raise util.Abort(_("no patches applied"))
855 raise util.Abort(_("no patches applied"))
856
856
857 if not update:
857 if not update:
858 parents = repo.dirstate.parents()
858 parents = repo.dirstate.parents()
859 rr = [ revlog.bin(x.rev) for x in self.applied ]
859 rr = [ revlog.bin(x.rev) for x in self.applied ]
860 for p in parents:
860 for p in parents:
861 if p in rr:
861 if p in rr:
862 self.ui.warn("qpop: forcing dirstate update\n")
862 self.ui.warn("qpop: forcing dirstate update\n")
863 update = True
863 update = True
864
864
865 if not force and update:
865 if not force and update:
866 self.check_localchanges(repo)
866 self.check_localchanges(repo)
867
867
868 self.applied_dirty = 1;
868 self.applied_dirty = 1;
869 end = len(self.applied)
869 end = len(self.applied)
870 if not patch:
870 if not patch:
871 if all:
871 if all:
872 popi = 0
872 popi = 0
873 else:
873 else:
874 popi = len(self.applied) - 1
874 popi = len(self.applied) - 1
875 else:
875 else:
876 popi = info[0] + 1
876 popi = info[0] + 1
877 if popi >= end:
877 if popi >= end:
878 self.ui.warn("qpop: %s is already at the top\n" % patch)
878 self.ui.warn("qpop: %s is already at the top\n" % patch)
879 return
879 return
880 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
880 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
881
881
882 start = info[0]
882 start = info[0]
883 rev = revlog.bin(info[1])
883 rev = revlog.bin(info[1])
884
884
885 # we know there are no local changes, so we can make a simplified
885 # we know there are no local changes, so we can make a simplified
886 # form of hg.update.
886 # form of hg.update.
887 if update:
887 if update:
888 top = self.check_toppatch(repo)
888 top = self.check_toppatch(repo)
889 qp = self.qparents(repo, rev)
889 qp = self.qparents(repo, rev)
890 changes = repo.changelog.read(qp)
890 changes = repo.changelog.read(qp)
891 mmap = repo.manifest.read(changes[0])
891 mmap = repo.manifest.read(changes[0])
892 m, a, r, d, u = repo.status(qp, top)[:5]
892 m, a, r, d, u = repo.status(qp, top)[:5]
893 if d:
893 if d:
894 raise util.Abort("deletions found between repo revs")
894 raise util.Abort("deletions found between repo revs")
895 for f in m:
895 for f in m:
896 getfile(f, mmap[f])
896 getfile(f, mmap[f])
897 for f in r:
897 for f in r:
898 getfile(f, mmap[f])
898 getfile(f, mmap[f])
899 util.set_exec(repo.wjoin(f), mmap.execf(f))
899 util.set_exec(repo.wjoin(f), mmap.execf(f))
900 repo.dirstate.update(m + r, 'n')
900 repo.dirstate.update(m + r, 'n')
901 for f in a:
901 for f in a:
902 try:
902 try:
903 os.unlink(repo.wjoin(f))
903 os.unlink(repo.wjoin(f))
904 except OSError, e:
904 except OSError, e:
905 if e.errno != errno.ENOENT:
905 if e.errno != errno.ENOENT:
906 raise
906 raise
907 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
907 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
908 except: pass
908 except: pass
909 if a:
909 if a:
910 repo.dirstate.forget(a)
910 repo.dirstate.forget(a)
911 repo.dirstate.setparents(qp, revlog.nullid)
911 repo.dirstate.setparents(qp, revlog.nullid)
912 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
912 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
913 del self.applied[start:end]
913 del self.applied[start:end]
914 if len(self.applied):
914 if len(self.applied):
915 self.ui.write("Now at: %s\n" % self.applied[-1].name)
915 self.ui.write("Now at: %s\n" % self.applied[-1].name)
916 else:
916 else:
917 self.ui.write("Patch queue now empty\n")
917 self.ui.write("Patch queue now empty\n")
918
918
919 def diff(self, repo, pats, opts):
919 def diff(self, repo, pats, opts):
920 top = self.check_toppatch(repo)
920 top = self.check_toppatch(repo)
921 if not top:
921 if not top:
922 self.ui.write("No patches applied\n")
922 self.ui.write("No patches applied\n")
923 return
923 return
924 qp = self.qparents(repo, top)
924 qp = self.qparents(repo, top)
925 if opts.get('git'):
925 if opts.get('git'):
926 self.diffopts().git = True
926 self.diffopts().git = True
927 self.printdiff(repo, qp, files=pats, opts=opts)
927 self.printdiff(repo, qp, files=pats, opts=opts)
928
928
929 def refresh(self, repo, pats=None, **opts):
929 def refresh(self, repo, pats=None, **opts):
930 if len(self.applied) == 0:
930 if len(self.applied) == 0:
931 self.ui.write("No patches applied\n")
931 self.ui.write("No patches applied\n")
932 return 1
932 return 1
933 wlock = repo.wlock()
933 wlock = repo.wlock()
934 self.check_toppatch(repo)
934 self.check_toppatch(repo)
935 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
935 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
936 top = revlog.bin(top)
936 top = revlog.bin(top)
937 cparents = repo.changelog.parents(top)
937 cparents = repo.changelog.parents(top)
938 patchparent = self.qparents(repo, top)
938 patchparent = self.qparents(repo, top)
939 message, comments, user, date, patchfound = self.readheaders(patchfn)
939 message, comments, user, date, patchfound = self.readheaders(patchfn)
940
940
941 patchf = self.opener(patchfn, "w")
941 patchf = self.opener(patchfn, "w")
942 msg = opts.get('msg', '').rstrip()
942 msg = opts.get('msg', '').rstrip()
943 if msg:
943 if msg:
944 if comments:
944 if comments:
945 # Remove existing message.
945 # Remove existing message.
946 ci = 0
946 ci = 0
947 for mi in xrange(len(message)):
947 for mi in xrange(len(message)):
948 while message[mi] != comments[ci]:
948 while message[mi] != comments[ci]:
949 ci += 1
949 ci += 1
950 del comments[ci]
950 del comments[ci]
951 comments.append(msg)
951 comments.append(msg)
952 if comments:
952 if comments:
953 comments = "\n".join(comments) + '\n\n'
953 comments = "\n".join(comments) + '\n\n'
954 patchf.write(comments)
954 patchf.write(comments)
955
955
956 if opts.get('git'):
956 if opts.get('git'):
957 self.diffopts().git = True
957 self.diffopts().git = True
958 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
958 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
959 tip = repo.changelog.tip()
959 tip = repo.changelog.tip()
960 if top == tip:
960 if top == tip:
961 # if the top of our patch queue is also the tip, there is an
961 # if the top of our patch queue is also the tip, there is an
962 # optimization here. We update the dirstate in place and strip
962 # optimization here. We update the dirstate in place and strip
963 # off the tip commit. Then just commit the current directory
963 # off the tip commit. Then just commit the current directory
964 # tree. We can also send repo.commit the list of files
964 # tree. We can also send repo.commit the list of files
965 # changed to speed up the diff
965 # changed to speed up the diff
966 #
966 #
967 # in short mode, we only diff the files included in the
967 # in short mode, we only diff the files included in the
968 # patch already
968 # patch already
969 #
969 #
970 # this should really read:
970 # this should really read:
971 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
971 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
972 # but we do it backwards to take advantage of manifest/chlog
972 # but we do it backwards to take advantage of manifest/chlog
973 # caching against the next repo.status call
973 # caching against the next repo.status call
974 #
974 #
975 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
975 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
976 changes = repo.changelog.read(tip)
976 changes = repo.changelog.read(tip)
977 man = repo.manifest.read(changes[0])
977 man = repo.manifest.read(changes[0])
978 aaa = aa[:]
978 aaa = aa[:]
979 if opts.get('short'):
979 if opts.get('short'):
980 filelist = mm + aa + dd
980 filelist = mm + aa + dd
981 else:
981 else:
982 filelist = None
982 filelist = None
983 m, a, r, d, u = repo.status(files=filelist)[:5]
983 m, a, r, d, u = repo.status(files=filelist)[:5]
984
984
985 # we might end up with files that were added between tip and
985 # we might end up with files that were added between tip and
986 # the dirstate parent, but then changed in the local dirstate.
986 # the dirstate parent, but then changed in the local dirstate.
987 # in this case, we want them to only show up in the added section
987 # in this case, we want them to only show up in the added section
988 for x in m:
988 for x in m:
989 if x not in aa:
989 if x not in aa:
990 mm.append(x)
990 mm.append(x)
991 # we might end up with files added by the local dirstate that
991 # we might end up with files added by the local dirstate that
992 # were deleted by the patch. In this case, they should only
992 # were deleted by the patch. In this case, they should only
993 # show up in the changed section.
993 # show up in the changed section.
994 for x in a:
994 for x in a:
995 if x in dd:
995 if x in dd:
996 del dd[dd.index(x)]
996 del dd[dd.index(x)]
997 mm.append(x)
997 mm.append(x)
998 else:
998 else:
999 aa.append(x)
999 aa.append(x)
1000 # make sure any files deleted in the local dirstate
1000 # make sure any files deleted in the local dirstate
1001 # are not in the add or change column of the patch
1001 # are not in the add or change column of the patch
1002 forget = []
1002 forget = []
1003 for x in d + r:
1003 for x in d + r:
1004 if x in aa:
1004 if x in aa:
1005 del aa[aa.index(x)]
1005 del aa[aa.index(x)]
1006 forget.append(x)
1006 forget.append(x)
1007 continue
1007 continue
1008 elif x in mm:
1008 elif x in mm:
1009 del mm[mm.index(x)]
1009 del mm[mm.index(x)]
1010 dd.append(x)
1010 dd.append(x)
1011
1011
1012 m = util.unique(mm)
1012 m = util.unique(mm)
1013 r = util.unique(dd)
1013 r = util.unique(dd)
1014 a = util.unique(aa)
1014 a = util.unique(aa)
1015 filelist = filter(matchfn, util.unique(m + r + a))
1015 filelist = filter(matchfn, util.unique(m + r + a))
1016 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1016 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1017 fp=patchf, changes=(m, a, r, [], u),
1017 fp=patchf, changes=(m, a, r, [], u),
1018 opts=self.diffopts())
1018 opts=self.diffopts())
1019 patchf.close()
1019 patchf.close()
1020
1020
1021 repo.dirstate.setparents(*cparents)
1021 repo.dirstate.setparents(*cparents)
1022 copies = {}
1022 copies = {}
1023 for dst in a:
1023 for dst in a:
1024 src = repo.dirstate.copied(dst)
1024 src = repo.dirstate.copied(dst)
1025 if src is None:
1025 if src is None:
1026 continue
1026 continue
1027 copies.setdefault(src, []).append(dst)
1027 copies.setdefault(src, []).append(dst)
1028 repo.dirstate.update(a, 'a')
1028 repo.dirstate.update(a, 'a')
1029 # remember the copies between patchparent and tip
1029 # remember the copies between patchparent and tip
1030 # this may be slow, so don't do it if we're not tracking copies
1030 # this may be slow, so don't do it if we're not tracking copies
1031 if self.diffopts().git:
1031 if self.diffopts().git:
1032 for dst in aaa:
1032 for dst in aaa:
1033 f = repo.file(dst)
1033 f = repo.file(dst)
1034 src = f.renamed(man[dst])
1034 src = f.renamed(man[dst])
1035 if src:
1035 if src:
1036 copies[src[0]] = copies.get(dst, [])
1036 copies[src[0]] = copies.get(dst, [])
1037 if dst in a:
1037 if dst in a:
1038 copies[src[0]].append(dst)
1038 copies[src[0]].append(dst)
1039 # we can't copy a file created by the patch itself
1039 # we can't copy a file created by the patch itself
1040 if dst in copies:
1040 if dst in copies:
1041 del copies[dst]
1041 del copies[dst]
1042 for src, dsts in copies.iteritems():
1042 for src, dsts in copies.iteritems():
1043 for dst in dsts:
1043 for dst in dsts:
1044 repo.dirstate.copy(src, dst)
1044 repo.dirstate.copy(src, dst)
1045 repo.dirstate.update(r, 'r')
1045 repo.dirstate.update(r, 'r')
1046 # if the patch excludes a modified file, mark that file with mtime=0
1046 # if the patch excludes a modified file, mark that file with mtime=0
1047 # so status can see it.
1047 # so status can see it.
1048 mm = []
1048 mm = []
1049 for i in xrange(len(m)-1, -1, -1):
1049 for i in xrange(len(m)-1, -1, -1):
1050 if not matchfn(m[i]):
1050 if not matchfn(m[i]):
1051 mm.append(m[i])
1051 mm.append(m[i])
1052 del m[i]
1052 del m[i]
1053 repo.dirstate.update(m, 'n')
1053 repo.dirstate.update(m, 'n')
1054 repo.dirstate.update(mm, 'n', st_mtime=0)
1054 repo.dirstate.update(mm, 'n', st_mtime=0)
1055 repo.dirstate.forget(forget)
1055 repo.dirstate.forget(forget)
1056
1056
1057 if not msg:
1057 if not msg:
1058 if not message:
1058 if not message:
1059 message = "patch queue: %s\n" % patchfn
1059 message = "patch queue: %s\n" % patchfn
1060 else:
1060 else:
1061 message = "\n".join(message)
1061 message = "\n".join(message)
1062 else:
1062 else:
1063 message = msg
1063 message = msg
1064
1064
1065 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1065 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1066 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
1066 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
1067 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1067 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1068 self.applied_dirty = 1
1068 self.applied_dirty = 1
1069 else:
1069 else:
1070 self.printdiff(repo, patchparent, fp=patchf)
1070 self.printdiff(repo, patchparent, fp=patchf)
1071 patchf.close()
1071 patchf.close()
1072 added = repo.status()[1]
1072 added = repo.status()[1]
1073 for a in added:
1073 for a in added:
1074 f = repo.wjoin(a)
1074 f = repo.wjoin(a)
1075 try:
1075 try:
1076 os.unlink(f)
1076 os.unlink(f)
1077 except OSError, e:
1077 except OSError, e:
1078 if e.errno != errno.ENOENT:
1078 if e.errno != errno.ENOENT:
1079 raise
1079 raise
1080 try: os.removedirs(os.path.dirname(f))
1080 try: os.removedirs(os.path.dirname(f))
1081 except: pass
1081 except: pass
1082 # forget the file copies in the dirstate
1082 # forget the file copies in the dirstate
1083 # push should readd the files later on
1083 # push should readd the files later on
1084 repo.dirstate.forget(added)
1084 repo.dirstate.forget(added)
1085 self.pop(repo, force=True, wlock=wlock)
1085 self.pop(repo, force=True, wlock=wlock)
1086 self.push(repo, force=True, wlock=wlock)
1086 self.push(repo, force=True, wlock=wlock)
1087
1087
1088 def init(self, repo, create=False):
1088 def init(self, repo, create=False):
1089 if os.path.isdir(self.path):
1089 if os.path.isdir(self.path):
1090 raise util.Abort(_("patch queue directory already exists"))
1090 raise util.Abort(_("patch queue directory already exists"))
1091 os.mkdir(self.path)
1091 os.mkdir(self.path)
1092 if create:
1092 if create:
1093 return self.qrepo(create=True)
1093 return self.qrepo(create=True)
1094
1094
1095 def unapplied(self, repo, patch=None):
1095 def unapplied(self, repo, patch=None):
1096 if patch and patch not in self.series:
1096 if patch and patch not in self.series:
1097 raise util.Abort(_("patch %s is not in series file") % patch)
1097 raise util.Abort(_("patch %s is not in series file") % patch)
1098 if not patch:
1098 if not patch:
1099 start = self.series_end()
1099 start = self.series_end()
1100 else:
1100 else:
1101 start = self.series.index(patch) + 1
1101 start = self.series.index(patch) + 1
1102 unapplied = []
1102 unapplied = []
1103 for i in xrange(start, len(self.series)):
1103 for i in xrange(start, len(self.series)):
1104 pushable, reason = self.pushable(i)
1104 pushable, reason = self.pushable(i)
1105 if pushable:
1105 if pushable:
1106 unapplied.append((i, self.series[i]))
1106 unapplied.append((i, self.series[i]))
1107 self.explain_pushable(i)
1107 self.explain_pushable(i)
1108 return unapplied
1108 return unapplied
1109
1109
1110 def qseries(self, repo, missing=None, start=0, length=0, status=None,
1110 def qseries(self, repo, missing=None, start=0, length=0, status=None,
1111 summary=False):
1111 summary=False):
1112 def displayname(patchname):
1112 def displayname(patchname):
1113 if summary:
1113 if summary:
1114 msg = self.readheaders(patchname)[0]
1114 msg = self.readheaders(patchname)[0]
1115 msg = msg and ': ' + msg[0] or ': '
1115 msg = msg and ': ' + msg[0] or ': '
1116 else:
1116 else:
1117 msg = ''
1117 msg = ''
1118 return '%s%s' % (patchname, msg)
1118 return '%s%s' % (patchname, msg)
1119
1119
1120 def pname(i):
1120 def pname(i):
1121 if status == 'A':
1121 if status == 'A':
1122 return self.applied[i].name
1122 return self.applied[i].name
1123 else:
1123 else:
1124 return self.series[i]
1124 return self.series[i]
1125
1125
1126 applied = dict.fromkeys([p.name for p in self.applied])
1126 applied = dict.fromkeys([p.name for p in self.applied])
1127 if not length:
1127 if not length:
1128 length = len(self.series) - start
1128 length = len(self.series) - start
1129 if not missing:
1129 if not missing:
1130 for i in xrange(start, start+length):
1130 for i in xrange(start, start+length):
1131 pfx = ''
1131 pfx = ''
1132 patch = pname(i)
1132 patch = pname(i)
1133 if self.ui.verbose:
1133 if self.ui.verbose:
1134 if patch in applied:
1134 if patch in applied:
1135 stat = 'A'
1135 stat = 'A'
1136 elif self.pushable(i)[0]:
1136 elif self.pushable(i)[0]:
1137 stat = 'U'
1137 stat = 'U'
1138 else:
1138 else:
1139 stat = 'G'
1139 stat = 'G'
1140 pfx = '%d %s ' % (i, stat)
1140 pfx = '%d %s ' % (i, stat)
1141 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1141 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1142 else:
1142 else:
1143 msng_list = []
1143 msng_list = []
1144 for root, dirs, files in os.walk(self.path):
1144 for root, dirs, files in os.walk(self.path):
1145 d = root[len(self.path) + 1:]
1145 d = root[len(self.path) + 1:]
1146 for f in files:
1146 for f in files:
1147 fl = os.path.join(d, f)
1147 fl = os.path.join(d, f)
1148 if (fl not in self.series and
1148 if (fl not in self.series and
1149 fl not in (self.status_path, self.series_path)
1149 fl not in (self.status_path, self.series_path)
1150 and not fl.startswith('.')):
1150 and not fl.startswith('.')):
1151 msng_list.append(fl)
1151 msng_list.append(fl)
1152 msng_list.sort()
1152 msng_list.sort()
1153 for x in msng_list:
1153 for x in msng_list:
1154 pfx = self.ui.verbose and ('D ') or ''
1154 pfx = self.ui.verbose and ('D ') or ''
1155 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1155 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1156
1156
1157 def issaveline(self, l):
1157 def issaveline(self, l):
1158 if l.name == '.hg.patches.save.line':
1158 if l.name == '.hg.patches.save.line':
1159 return True
1159 return True
1160
1160
1161 def qrepo(self, create=False):
1161 def qrepo(self, create=False):
1162 if create or os.path.isdir(self.join(".hg")):
1162 if create or os.path.isdir(self.join(".hg")):
1163 return hg.repository(self.ui, path=self.path, create=create)
1163 return hg.repository(self.ui, path=self.path, create=create)
1164
1164
1165 def restore(self, repo, rev, delete=None, qupdate=None):
1165 def restore(self, repo, rev, delete=None, qupdate=None):
1166 c = repo.changelog.read(rev)
1166 c = repo.changelog.read(rev)
1167 desc = c[4].strip()
1167 desc = c[4].strip()
1168 lines = desc.splitlines()
1168 lines = desc.splitlines()
1169 i = 0
1169 i = 0
1170 datastart = None
1170 datastart = None
1171 series = []
1171 series = []
1172 applied = []
1172 applied = []
1173 qpp = None
1173 qpp = None
1174 for i in xrange(0, len(lines)):
1174 for i in xrange(0, len(lines)):
1175 if lines[i] == 'Patch Data:':
1175 if lines[i] == 'Patch Data:':
1176 datastart = i + 1
1176 datastart = i + 1
1177 elif lines[i].startswith('Dirstate:'):
1177 elif lines[i].startswith('Dirstate:'):
1178 l = lines[i].rstrip()
1178 l = lines[i].rstrip()
1179 l = l[10:].split(' ')
1179 l = l[10:].split(' ')
1180 qpp = [ hg.bin(x) for x in l ]
1180 qpp = [ hg.bin(x) for x in l ]
1181 elif datastart != None:
1181 elif datastart != None:
1182 l = lines[i].rstrip()
1182 l = lines[i].rstrip()
1183 se = statusentry(l)
1183 se = statusentry(l)
1184 file_ = se.name
1184 file_ = se.name
1185 if se.rev:
1185 if se.rev:
1186 applied.append(se)
1186 applied.append(se)
1187 else:
1187 else:
1188 series.append(file_)
1188 series.append(file_)
1189 if datastart == None:
1189 if datastart == None:
1190 self.ui.warn("No saved patch data found\n")
1190 self.ui.warn("No saved patch data found\n")
1191 return 1
1191 return 1
1192 self.ui.warn("restoring status: %s\n" % lines[0])
1192 self.ui.warn("restoring status: %s\n" % lines[0])
1193 self.full_series = series
1193 self.full_series = series
1194 self.applied = applied
1194 self.applied = applied
1195 self.parse_series()
1195 self.parse_series()
1196 self.series_dirty = 1
1196 self.series_dirty = 1
1197 self.applied_dirty = 1
1197 self.applied_dirty = 1
1198 heads = repo.changelog.heads()
1198 heads = repo.changelog.heads()
1199 if delete:
1199 if delete:
1200 if rev not in heads:
1200 if rev not in heads:
1201 self.ui.warn("save entry has children, leaving it alone\n")
1201 self.ui.warn("save entry has children, leaving it alone\n")
1202 else:
1202 else:
1203 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1203 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1204 pp = repo.dirstate.parents()
1204 pp = repo.dirstate.parents()
1205 if rev in pp:
1205 if rev in pp:
1206 update = True
1206 update = True
1207 else:
1207 else:
1208 update = False
1208 update = False
1209 self.strip(repo, rev, update=update, backup='strip')
1209 self.strip(repo, rev, update=update, backup='strip')
1210 if qpp:
1210 if qpp:
1211 self.ui.warn("saved queue repository parents: %s %s\n" %
1211 self.ui.warn("saved queue repository parents: %s %s\n" %
1212 (hg.short(qpp[0]), hg.short(qpp[1])))
1212 (hg.short(qpp[0]), hg.short(qpp[1])))
1213 if qupdate:
1213 if qupdate:
1214 print "queue directory updating"
1214 print "queue directory updating"
1215 r = self.qrepo()
1215 r = self.qrepo()
1216 if not r:
1216 if not r:
1217 self.ui.warn("Unable to load queue repository\n")
1217 self.ui.warn("Unable to load queue repository\n")
1218 return 1
1218 return 1
1219 hg.clean(r, qpp[0])
1219 hg.clean(r, qpp[0])
1220
1220
1221 def save(self, repo, msg=None):
1221 def save(self, repo, msg=None):
1222 if len(self.applied) == 0:
1222 if len(self.applied) == 0:
1223 self.ui.warn("save: no patches applied, exiting\n")
1223 self.ui.warn("save: no patches applied, exiting\n")
1224 return 1
1224 return 1
1225 if self.issaveline(self.applied[-1]):
1225 if self.issaveline(self.applied[-1]):
1226 self.ui.warn("status is already saved\n")
1226 self.ui.warn("status is already saved\n")
1227 return 1
1227 return 1
1228
1228
1229 ar = [ ':' + x for x in self.full_series ]
1229 ar = [ ':' + x for x in self.full_series ]
1230 if not msg:
1230 if not msg:
1231 msg = "hg patches saved state"
1231 msg = "hg patches saved state"
1232 else:
1232 else:
1233 msg = "hg patches: " + msg.rstrip('\r\n')
1233 msg = "hg patches: " + msg.rstrip('\r\n')
1234 r = self.qrepo()
1234 r = self.qrepo()
1235 if r:
1235 if r:
1236 pp = r.dirstate.parents()
1236 pp = r.dirstate.parents()
1237 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1237 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1238 msg += "\n\nPatch Data:\n"
1238 msg += "\n\nPatch Data:\n"
1239 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1239 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1240 "\n".join(ar) + '\n' or "")
1240 "\n".join(ar) + '\n' or "")
1241 n = repo.commit(None, text, user=None, force=1)
1241 n = repo.commit(None, text, user=None, force=1)
1242 if not n:
1242 if not n:
1243 self.ui.warn("repo commit failed\n")
1243 self.ui.warn("repo commit failed\n")
1244 return 1
1244 return 1
1245 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1245 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1246 self.applied_dirty = 1
1246 self.applied_dirty = 1
1247
1247
1248 def full_series_end(self):
1248 def full_series_end(self):
1249 if len(self.applied) > 0:
1249 if len(self.applied) > 0:
1250 p = self.applied[-1].name
1250 p = self.applied[-1].name
1251 end = self.find_series(p)
1251 end = self.find_series(p)
1252 if end == None:
1252 if end == None:
1253 return len(self.full_series)
1253 return len(self.full_series)
1254 return end + 1
1254 return end + 1
1255 return 0
1255 return 0
1256
1256
1257 def series_end(self, all_patches=False):
1257 def series_end(self, all_patches=False):
1258 end = 0
1258 end = 0
1259 def next(start):
1259 def next(start):
1260 if all_patches:
1260 if all_patches:
1261 return start
1261 return start
1262 i = start
1262 i = start
1263 while i < len(self.series):
1263 while i < len(self.series):
1264 p, reason = self.pushable(i)
1264 p, reason = self.pushable(i)
1265 if p:
1265 if p:
1266 break
1266 break
1267 self.explain_pushable(i)
1267 self.explain_pushable(i)
1268 i += 1
1268 i += 1
1269 return i
1269 return i
1270 if len(self.applied) > 0:
1270 if len(self.applied) > 0:
1271 p = self.applied[-1].name
1271 p = self.applied[-1].name
1272 try:
1272 try:
1273 end = self.series.index(p)
1273 end = self.series.index(p)
1274 except ValueError:
1274 except ValueError:
1275 return 0
1275 return 0
1276 return next(end + 1)
1276 return next(end + 1)
1277 return next(end)
1277 return next(end)
1278
1278
1279 def appliedname(self, index):
1279 def appliedname(self, index):
1280 pname = self.applied[index].name
1280 pname = self.applied[index].name
1281 if not self.ui.verbose:
1281 if not self.ui.verbose:
1282 p = pname
1282 p = pname
1283 else:
1283 else:
1284 p = str(self.series.index(pname)) + " " + pname
1284 p = str(self.series.index(pname)) + " " + pname
1285 return p
1285 return p
1286
1286
1287 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1287 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1288 force=None, git=False):
1288 force=None, git=False):
1289 def checkseries(patchname):
1289 def checkseries(patchname):
1290 if patchname in self.series:
1290 if patchname in self.series:
1291 raise util.Abort(_('patch %s is already in the series file')
1291 raise util.Abort(_('patch %s is already in the series file')
1292 % patchname)
1292 % patchname)
1293 def checkfile(patchname):
1293 def checkfile(patchname):
1294 if not force and os.path.exists(self.join(patchname)):
1294 if not force and os.path.exists(self.join(patchname)):
1295 raise util.Abort(_('patch "%s" already exists')
1295 raise util.Abort(_('patch "%s" already exists')
1296 % patchname)
1296 % patchname)
1297
1297
1298 if rev:
1298 if rev:
1299 if files:
1299 if files:
1300 raise util.Abort(_('option "-r" not valid when importing '
1300 raise util.Abort(_('option "-r" not valid when importing '
1301 'files'))
1301 'files'))
1302 rev = cmdutil.revrange(repo, rev)
1302 rev = cmdutil.revrange(repo, rev)
1303 rev.sort(lambda x, y: cmp(y, x))
1303 rev.sort(lambda x, y: cmp(y, x))
1304 if (len(files) > 1 or len(rev) > 1) and patchname:
1304 if (len(files) > 1 or len(rev) > 1) and patchname:
1305 raise util.Abort(_('option "-n" not valid when importing multiple '
1305 raise util.Abort(_('option "-n" not valid when importing multiple '
1306 'patches'))
1306 'patches'))
1307 i = 0
1307 i = 0
1308 added = []
1308 added = []
1309 if rev:
1309 if rev:
1310 # If mq patches are applied, we can only import revisions
1310 # If mq patches are applied, we can only import revisions
1311 # that form a linear path to qbase.
1311 # that form a linear path to qbase.
1312 # Otherwise, they should form a linear path to a head.
1312 # Otherwise, they should form a linear path to a head.
1313 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1313 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1314 if len(heads) > 1:
1314 if len(heads) > 1:
1315 raise util.Abort(_('revision %d is the root of more than one '
1315 raise util.Abort(_('revision %d is the root of more than one '
1316 'branch') % rev[-1])
1316 'branch') % rev[-1])
1317 if self.applied:
1317 if self.applied:
1318 base = revlog.hex(repo.changelog.node(rev[0]))
1318 base = revlog.hex(repo.changelog.node(rev[0]))
1319 if base in [n.rev for n in self.applied]:
1319 if base in [n.rev for n in self.applied]:
1320 raise util.Abort(_('revision %d is already managed')
1320 raise util.Abort(_('revision %d is already managed')
1321 % rev[0])
1321 % rev[0])
1322 if heads != [revlog.bin(self.applied[-1].rev)]:
1322 if heads != [revlog.bin(self.applied[-1].rev)]:
1323 raise util.Abort(_('revision %d is not the parent of '
1323 raise util.Abort(_('revision %d is not the parent of '
1324 'the queue') % rev[0])
1324 'the queue') % rev[0])
1325 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1325 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1326 lastparent = repo.changelog.parentrevs(base)[0]
1326 lastparent = repo.changelog.parentrevs(base)[0]
1327 else:
1327 else:
1328 if heads != [repo.changelog.node(rev[0])]:
1328 if heads != [repo.changelog.node(rev[0])]:
1329 raise util.Abort(_('revision %d has unmanaged children')
1329 raise util.Abort(_('revision %d has unmanaged children')
1330 % rev[0])
1330 % rev[0])
1331 lastparent = None
1331 lastparent = None
1332
1332
1333 if git:
1333 if git:
1334 self.diffopts().git = True
1334 self.diffopts().git = True
1335
1335
1336 for r in rev:
1336 for r in rev:
1337 p1, p2 = repo.changelog.parentrevs(r)
1337 p1, p2 = repo.changelog.parentrevs(r)
1338 n = repo.changelog.node(r)
1338 n = repo.changelog.node(r)
1339 if p2 != revlog.nullrev:
1339 if p2 != revlog.nullrev:
1340 raise util.Abort(_('cannot import merge revision %d') % r)
1340 raise util.Abort(_('cannot import merge revision %d') % r)
1341 if lastparent and lastparent != r:
1341 if lastparent and lastparent != r:
1342 raise util.Abort(_('revision %d is not the parent of %d')
1342 raise util.Abort(_('revision %d is not the parent of %d')
1343 % (r, lastparent))
1343 % (r, lastparent))
1344 lastparent = p1
1344 lastparent = p1
1345
1345
1346 if not patchname:
1346 if not patchname:
1347 patchname = '%d.diff' % r
1347 patchname = '%d.diff' % r
1348 checkseries(patchname)
1348 checkseries(patchname)
1349 checkfile(patchname)
1349 checkfile(patchname)
1350 self.full_series.insert(0, patchname)
1350 self.full_series.insert(0, patchname)
1351
1351
1352 patchf = self.opener(patchname, "w")
1352 patchf = self.opener(patchname, "w")
1353 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1353 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1354 patchf.close()
1354 patchf.close()
1355
1355
1356 se = statusentry(revlog.hex(n), patchname)
1356 se = statusentry(revlog.hex(n), patchname)
1357 self.applied.insert(0, se)
1357 self.applied.insert(0, se)
1358
1358
1359 added.append(patchname)
1359 added.append(patchname)
1360 patchname = None
1360 patchname = None
1361 self.parse_series()
1361 self.parse_series()
1362 self.applied_dirty = 1
1362 self.applied_dirty = 1
1363
1363
1364 for filename in files:
1364 for filename in files:
1365 if existing:
1365 if existing:
1366 if filename == '-':
1366 if filename == '-':
1367 raise util.Abort(_('-e is incompatible with import from -'))
1367 raise util.Abort(_('-e is incompatible with import from -'))
1368 if not patchname:
1368 if not patchname:
1369 patchname = filename
1369 patchname = filename
1370 if not os.path.isfile(self.join(patchname)):
1370 if not os.path.isfile(self.join(patchname)):
1371 raise util.Abort(_("patch %s does not exist") % patchname)
1371 raise util.Abort(_("patch %s does not exist") % patchname)
1372 else:
1372 else:
1373 try:
1373 try:
1374 if filename == '-':
1374 if filename == '-':
1375 if not patchname:
1375 if not patchname:
1376 raise util.Abort(_('need --name to import a patch from -'))
1376 raise util.Abort(_('need --name to import a patch from -'))
1377 text = sys.stdin.read()
1377 text = sys.stdin.read()
1378 else:
1378 else:
1379 text = file(filename).read()
1379 text = file(filename).read()
1380 except IOError:
1380 except IOError:
1381 raise util.Abort(_("unable to read %s") % patchname)
1381 raise util.Abort(_("unable to read %s") % patchname)
1382 if not patchname:
1382 if not patchname:
1383 patchname = os.path.basename(filename)
1383 patchname = os.path.basename(filename)
1384 checkfile(patchname)
1384 checkfile(patchname)
1385 patchf = self.opener(patchname, "w")
1385 patchf = self.opener(patchname, "w")
1386 patchf.write(text)
1386 patchf.write(text)
1387 checkseries(patchname)
1387 checkseries(patchname)
1388 index = self.full_series_end() + i
1388 index = self.full_series_end() + i
1389 self.full_series[index:index] = [patchname]
1389 self.full_series[index:index] = [patchname]
1390 self.parse_series()
1390 self.parse_series()
1391 self.ui.warn("adding %s to series file\n" % patchname)
1391 self.ui.warn("adding %s to series file\n" % patchname)
1392 i += 1
1392 i += 1
1393 added.append(patchname)
1393 added.append(patchname)
1394 patchname = None
1394 patchname = None
1395 self.series_dirty = 1
1395 self.series_dirty = 1
1396 qrepo = self.qrepo()
1396 qrepo = self.qrepo()
1397 if qrepo:
1397 if qrepo:
1398 qrepo.add(added)
1398 qrepo.add(added)
1399
1399
1400 def delete(ui, repo, *patches, **opts):
1400 def delete(ui, repo, *patches, **opts):
1401 """remove patches from queue
1401 """remove patches from queue
1402
1402
1403 With --rev, mq will stop managing the named revisions. The
1403 With --rev, mq will stop managing the named revisions. The
1404 patches must be applied and at the base of the stack. This option
1404 patches must be applied and at the base of the stack. This option
1405 is useful when the patches have been applied upstream.
1405 is useful when the patches have been applied upstream.
1406
1406
1407 Otherwise, the patches must not be applied.
1407 Otherwise, the patches must not be applied.
1408
1408
1409 With --keep, the patch files are preserved in the patch directory."""
1409 With --keep, the patch files are preserved in the patch directory."""
1410 q = repo.mq
1410 q = repo.mq
1411 q.delete(repo, patches, opts)
1411 q.delete(repo, patches, opts)
1412 q.save_dirty()
1412 q.save_dirty()
1413 return 0
1413 return 0
1414
1414
1415 def applied(ui, repo, patch=None, **opts):
1415 def applied(ui, repo, patch=None, **opts):
1416 """print the patches already applied"""
1416 """print the patches already applied"""
1417 q = repo.mq
1417 q = repo.mq
1418 if patch:
1418 if patch:
1419 if patch not in q.series:
1419 if patch not in q.series:
1420 raise util.Abort(_("patch %s is not in series file") % patch)
1420 raise util.Abort(_("patch %s is not in series file") % patch)
1421 end = q.series.index(patch) + 1
1421 end = q.series.index(patch) + 1
1422 else:
1422 else:
1423 end = len(q.applied)
1423 end = len(q.applied)
1424 if not end:
1424 if not end:
1425 return
1425 return
1426
1426
1427 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1427 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1428
1428
1429 def unapplied(ui, repo, patch=None, **opts):
1429 def unapplied(ui, repo, patch=None, **opts):
1430 """print the patches not yet applied"""
1430 """print the patches not yet applied"""
1431 q = repo.mq
1431 q = repo.mq
1432 if patch:
1432 if patch:
1433 if patch not in q.series:
1433 if patch not in q.series:
1434 raise util.Abort(_("patch %s is not in series file") % patch)
1434 raise util.Abort(_("patch %s is not in series file") % patch)
1435 start = q.series.index(patch) + 1
1435 start = q.series.index(patch) + 1
1436 else:
1436 else:
1437 start = q.series_end()
1437 start = q.series_end()
1438 q.qseries(repo, start=start, summary=opts.get('summary'))
1438 q.qseries(repo, start=start, summary=opts.get('summary'))
1439
1439
1440 def qimport(ui, repo, *filename, **opts):
1440 def qimport(ui, repo, *filename, **opts):
1441 """import a patch
1441 """import a patch
1442
1442
1443 The patch will have the same name as its source file unless you
1443 The patch will have the same name as its source file unless you
1444 give it a new one with --name.
1444 give it a new one with --name.
1445
1445
1446 You can register an existing patch inside the patch directory
1446 You can register an existing patch inside the patch directory
1447 with the --existing flag.
1447 with the --existing flag.
1448
1448
1449 With --force, an existing patch of the same name will be overwritten.
1449 With --force, an existing patch of the same name will be overwritten.
1450
1450
1451 An existing changeset may be placed under mq control with --rev
1451 An existing changeset may be placed under mq control with --rev
1452 (e.g. qimport --rev tip -n patch will place tip under mq control).
1452 (e.g. qimport --rev tip -n patch will place tip under mq control).
1453 With --git, patches imported with --rev will use the git diff
1453 With --git, patches imported with --rev will use the git diff
1454 format.
1454 format.
1455 """
1455 """
1456 q = repo.mq
1456 q = repo.mq
1457 q.qimport(repo, filename, patchname=opts['name'],
1457 q.qimport(repo, filename, patchname=opts['name'],
1458 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1458 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1459 git=opts['git'])
1459 git=opts['git'])
1460 q.save_dirty()
1460 q.save_dirty()
1461 return 0
1461 return 0
1462
1462
1463 def init(ui, repo, **opts):
1463 def init(ui, repo, **opts):
1464 """init a new queue repository
1464 """init a new queue repository
1465
1465
1466 The queue repository is unversioned by default. If -c is
1466 The queue repository is unversioned by default. If -c is
1467 specified, qinit will create a separate nested repository
1467 specified, qinit will create a separate nested repository
1468 for patches. Use qcommit to commit changes to this queue
1468 for patches. Use qcommit to commit changes to this queue
1469 repository."""
1469 repository."""
1470 q = repo.mq
1470 q = repo.mq
1471 r = q.init(repo, create=opts['create_repo'])
1471 r = q.init(repo, create=opts['create_repo'])
1472 q.save_dirty()
1472 q.save_dirty()
1473 if r:
1473 if r:
1474 fp = r.wopener('.hgignore', 'w')
1474 fp = r.wopener('.hgignore', 'w')
1475 print >> fp, 'syntax: glob'
1475 print >> fp, 'syntax: glob'
1476 print >> fp, 'status'
1476 print >> fp, 'status'
1477 print >> fp, 'guards'
1477 print >> fp, 'guards'
1478 fp.close()
1478 fp.close()
1479 r.wopener('series', 'w').close()
1479 r.wopener('series', 'w').close()
1480 r.add(['.hgignore', 'series'])
1480 r.add(['.hgignore', 'series'])
1481 return 0
1481 return 0
1482
1482
1483 def clone(ui, source, dest=None, **opts):
1483 def clone(ui, source, dest=None, **opts):
1484 '''clone main and patch repository at same time
1484 '''clone main and patch repository at same time
1485
1485
1486 If source is local, destination will have no patches applied. If
1486 If source is local, destination will have no patches applied. If
1487 source is remote, this command can not check if patches are
1487 source is remote, this command can not check if patches are
1488 applied in source, so cannot guarantee that patches are not
1488 applied in source, so cannot guarantee that patches are not
1489 applied in destination. If you clone remote repository, be sure
1489 applied in destination. If you clone remote repository, be sure
1490 before that it has no patches applied.
1490 before that it has no patches applied.
1491
1491
1492 Source patch repository is looked for in <src>/.hg/patches by
1492 Source patch repository is looked for in <src>/.hg/patches by
1493 default. Use -p <url> to change.
1493 default. Use -p <url> to change.
1494 '''
1494 '''
1495 commands.setremoteconfig(ui, opts)
1495 commands.setremoteconfig(ui, opts)
1496 if dest is None:
1496 if dest is None:
1497 dest = hg.defaultdest(source)
1497 dest = hg.defaultdest(source)
1498 sr = hg.repository(ui, ui.expandpath(source))
1498 sr = hg.repository(ui, ui.expandpath(source))
1499 qbase, destrev = None, None
1499 qbase, destrev = None, None
1500 if sr.local():
1500 if sr.local():
1501 reposetup(ui, sr)
1501 reposetup(ui, sr)
1502 if sr.mq.applied:
1502 if sr.mq.applied:
1503 qbase = revlog.bin(sr.mq.applied[0].rev)
1503 qbase = revlog.bin(sr.mq.applied[0].rev)
1504 if not hg.islocal(dest):
1504 if not hg.islocal(dest):
1505 destrev = sr.parents(qbase)[0]
1505 destrev = sr.parents(qbase)[0]
1506 ui.note(_('cloning main repo\n'))
1506 ui.note(_('cloning main repo\n'))
1507 sr, dr = hg.clone(ui, sr, dest,
1507 sr, dr = hg.clone(ui, sr, dest,
1508 pull=opts['pull'],
1508 pull=opts['pull'],
1509 rev=destrev,
1509 rev=destrev,
1510 update=False,
1510 update=False,
1511 stream=opts['uncompressed'])
1511 stream=opts['uncompressed'])
1512 ui.note(_('cloning patch repo\n'))
1512 ui.note(_('cloning patch repo\n'))
1513 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1513 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1514 dr.url() + '/.hg/patches',
1514 dr.url() + '/.hg/patches',
1515 pull=opts['pull'],
1515 pull=opts['pull'],
1516 update=not opts['noupdate'],
1516 update=not opts['noupdate'],
1517 stream=opts['uncompressed'])
1517 stream=opts['uncompressed'])
1518 if dr.local():
1518 if dr.local():
1519 if qbase:
1519 if qbase:
1520 ui.note(_('stripping applied patches from destination repo\n'))
1520 ui.note(_('stripping applied patches from destination repo\n'))
1521 reposetup(ui, dr)
1521 reposetup(ui, dr)
1522 dr.mq.strip(dr, qbase, update=False, backup=None)
1522 dr.mq.strip(dr, qbase, update=False, backup=None)
1523 if not opts['noupdate']:
1523 if not opts['noupdate']:
1524 ui.note(_('updating destination repo\n'))
1524 ui.note(_('updating destination repo\n'))
1525 hg.update(dr, dr.changelog.tip())
1525 hg.update(dr, dr.changelog.tip())
1526
1526
1527 def commit(ui, repo, *pats, **opts):
1527 def commit(ui, repo, *pats, **opts):
1528 """commit changes in the queue repository"""
1528 """commit changes in the queue repository"""
1529 q = repo.mq
1529 q = repo.mq
1530 r = q.qrepo()
1530 r = q.qrepo()
1531 if not r: raise util.Abort('no queue repository')
1531 if not r: raise util.Abort('no queue repository')
1532 commands.commit(r.ui, r, *pats, **opts)
1532 commands.commit(r.ui, r, *pats, **opts)
1533
1533
1534 def series(ui, repo, **opts):
1534 def series(ui, repo, **opts):
1535 """print the entire series file"""
1535 """print the entire series file"""
1536 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1536 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1537 return 0
1537 return 0
1538
1538
1539 def top(ui, repo, **opts):
1539 def top(ui, repo, **opts):
1540 """print the name of the current patch"""
1540 """print the name of the current patch"""
1541 q = repo.mq
1541 q = repo.mq
1542 t = len(q.applied)
1542 t = len(q.applied)
1543 if t:
1543 if t:
1544 return q.qseries(repo, start=t-1, length=1, status='A',
1544 return q.qseries(repo, start=t-1, length=1, status='A',
1545 summary=opts.get('summary'))
1545 summary=opts.get('summary'))
1546 else:
1546 else:
1547 ui.write("No patches applied\n")
1547 ui.write("No patches applied\n")
1548 return 1
1548 return 1
1549
1549
1550 def next(ui, repo, **opts):
1550 def next(ui, repo, **opts):
1551 """print the name of the next patch"""
1551 """print the name of the next patch"""
1552 q = repo.mq
1552 q = repo.mq
1553 end = q.series_end()
1553 end = q.series_end()
1554 if end == len(q.series):
1554 if end == len(q.series):
1555 ui.write("All patches applied\n")
1555 ui.write("All patches applied\n")
1556 return 1
1556 return 1
1557 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1557 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1558
1558
1559 def prev(ui, repo, **opts):
1559 def prev(ui, repo, **opts):
1560 """print the name of the previous patch"""
1560 """print the name of the previous patch"""
1561 q = repo.mq
1561 q = repo.mq
1562 l = len(q.applied)
1562 l = len(q.applied)
1563 if l == 1:
1563 if l == 1:
1564 ui.write("Only one patch applied\n")
1564 ui.write("Only one patch applied\n")
1565 return 1
1565 return 1
1566 if not l:
1566 if not l:
1567 ui.write("No patches applied\n")
1567 ui.write("No patches applied\n")
1568 return 1
1568 return 1
1569 return q.qseries(repo, start=l-2, length=1, status='A',
1569 return q.qseries(repo, start=l-2, length=1, status='A',
1570 summary=opts.get('summary'))
1570 summary=opts.get('summary'))
1571
1571
1572 def new(ui, repo, patch, **opts):
1572 def new(ui, repo, patch, **opts):
1573 """create a new patch
1573 """create a new patch
1574
1574
1575 qnew creates a new patch on top of the currently-applied patch
1575 qnew creates a new patch on top of the currently-applied patch
1576 (if any). It will refuse to run if there are any outstanding
1576 (if any). It will refuse to run if there are any outstanding
1577 changes unless -f is specified, in which case the patch will
1577 changes unless -f is specified, in which case the patch will
1578 be initialised with them.
1578 be initialised with them.
1579
1579
1580 -e, -m or -l set the patch header as well as the commit message.
1580 -e, -m or -l set the patch header as well as the commit message.
1581 If none is specified, the patch header is empty and the
1581 If none is specified, the patch header is empty and the
1582 commit message is 'New patch: PATCH'"""
1582 commit message is 'New patch: PATCH'"""
1583 q = repo.mq
1583 q = repo.mq
1584 message = commands.logmessage(opts)
1584 message = commands.logmessage(opts)
1585 if opts['edit']:
1585 if opts['edit']:
1586 message = ui.edit(message, ui.username())
1586 message = ui.edit(message, ui.username())
1587 q.new(repo, patch, msg=message, force=opts['force'])
1587 q.new(repo, patch, msg=message, force=opts['force'])
1588 q.save_dirty()
1588 q.save_dirty()
1589 return 0
1589 return 0
1590
1590
1591 def refresh(ui, repo, *pats, **opts):
1591 def refresh(ui, repo, *pats, **opts):
1592 """update the current patch
1592 """update the current patch
1593
1593
1594 If any file patterns are provided, the refreshed patch will contain only
1594 If any file patterns are provided, the refreshed patch will contain only
1595 the modifications that match those patterns; the remaining modifications
1595 the modifications that match those patterns; the remaining modifications
1596 will remain in the working directory.
1596 will remain in the working directory.
1597 """
1597 """
1598 q = repo.mq
1598 q = repo.mq
1599 message = commands.logmessage(opts)
1599 message = commands.logmessage(opts)
1600 if opts['edit']:
1600 if opts['edit']:
1601 if message:
1601 if message:
1602 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1602 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1603 patch = q.applied[-1].name
1603 patch = q.applied[-1].name
1604 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1604 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1605 message = ui.edit('\n'.join(message), user or ui.username())
1605 message = ui.edit('\n'.join(message), user or ui.username())
1606 ret = q.refresh(repo, pats, msg=message, **opts)
1606 ret = q.refresh(repo, pats, msg=message, **opts)
1607 q.save_dirty()
1607 q.save_dirty()
1608 return ret
1608 return ret
1609
1609
1610 def diff(ui, repo, *pats, **opts):
1610 def diff(ui, repo, *pats, **opts):
1611 """diff of the current patch"""
1611 """diff of the current patch"""
1612 repo.mq.diff(repo, pats, opts)
1612 repo.mq.diff(repo, pats, opts)
1613 return 0
1613 return 0
1614
1614
1615 def fold(ui, repo, *files, **opts):
1615 def fold(ui, repo, *files, **opts):
1616 """fold the named patches into the current patch
1616 """fold the named patches into the current patch
1617
1617
1618 Patches must not yet be applied. Each patch will be successively
1618 Patches must not yet be applied. Each patch will be successively
1619 applied to the current patch in the order given. If all the
1619 applied to the current patch in the order given. If all the
1620 patches apply successfully, the current patch will be refreshed
1620 patches apply successfully, the current patch will be refreshed
1621 with the new cumulative patch, and the folded patches will
1621 with the new cumulative patch, and the folded patches will
1622 be deleted. With -k/--keep, the folded patch files will not
1622 be deleted. With -k/--keep, the folded patch files will not
1623 be removed afterwards.
1623 be removed afterwards.
1624
1624
1625 The header for each folded patch will be concatenated with
1625 The header for each folded patch will be concatenated with
1626 the current patch header, separated by a line of '* * *'."""
1626 the current patch header, separated by a line of '* * *'."""
1627
1627
1628 q = repo.mq
1628 q = repo.mq
1629
1629
1630 if not files:
1630 if not files:
1631 raise util.Abort(_('qfold requires at least one patch name'))
1631 raise util.Abort(_('qfold requires at least one patch name'))
1632 if not q.check_toppatch(repo):
1632 if not q.check_toppatch(repo):
1633 raise util.Abort(_('No patches applied'))
1633 raise util.Abort(_('No patches applied'))
1634
1634
1635 message = commands.logmessage(opts)
1635 message = commands.logmessage(opts)
1636 if opts['edit']:
1636 if opts['edit']:
1637 if message:
1637 if message:
1638 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1638 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1639
1639
1640 parent = q.lookup('qtip')
1640 parent = q.lookup('qtip')
1641 patches = []
1641 patches = []
1642 messages = []
1642 messages = []
1643 for f in files:
1643 for f in files:
1644 p = q.lookup(f)
1644 p = q.lookup(f)
1645 if p in patches or p == parent:
1645 if p in patches or p == parent:
1646 ui.warn(_('Skipping already folded patch %s') % p)
1646 ui.warn(_('Skipping already folded patch %s') % p)
1647 if q.isapplied(p):
1647 if q.isapplied(p):
1648 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1648 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1649 patches.append(p)
1649 patches.append(p)
1650
1650
1651 for p in patches:
1651 for p in patches:
1652 if not message:
1652 if not message:
1653 messages.append(q.readheaders(p)[0])
1653 messages.append(q.readheaders(p)[0])
1654 pf = q.join(p)
1654 pf = q.join(p)
1655 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1655 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1656 if not patchsuccess:
1656 if not patchsuccess:
1657 raise util.Abort(_('Error folding patch %s') % p)
1657 raise util.Abort(_('Error folding patch %s') % p)
1658 patch.updatedir(ui, repo, files)
1658 patch.updatedir(ui, repo, files)
1659
1659
1660 if not message:
1660 if not message:
1661 message, comments, user = q.readheaders(parent)[0:3]
1661 message, comments, user = q.readheaders(parent)[0:3]
1662 for msg in messages:
1662 for msg in messages:
1663 message.append('* * *')
1663 message.append('* * *')
1664 message.extend(msg)
1664 message.extend(msg)
1665 message = '\n'.join(message)
1665 message = '\n'.join(message)
1666
1666
1667 if opts['edit']:
1667 if opts['edit']:
1668 message = ui.edit(message, user or ui.username())
1668 message = ui.edit(message, user or ui.username())
1669
1669
1670 q.refresh(repo, msg=message)
1670 q.refresh(repo, msg=message)
1671 q.delete(repo, patches, opts)
1671 q.delete(repo, patches, opts)
1672 q.save_dirty()
1672 q.save_dirty()
1673
1673
1674 def guard(ui, repo, *args, **opts):
1674 def guard(ui, repo, *args, **opts):
1675 '''set or print guards for a patch
1675 '''set or print guards for a patch
1676
1676
1677 Guards control whether a patch can be pushed. A patch with no
1677 Guards control whether a patch can be pushed. A patch with no
1678 guards is always pushed. A patch with a positive guard ("+foo") is
1678 guards is always pushed. A patch with a positive guard ("+foo") is
1679 pushed only if the qselect command has activated it. A patch with
1679 pushed only if the qselect command has activated it. A patch with
1680 a negative guard ("-foo") is never pushed if the qselect command
1680 a negative guard ("-foo") is never pushed if the qselect command
1681 has activated it.
1681 has activated it.
1682
1682
1683 With no arguments, print the currently active guards.
1683 With no arguments, print the currently active guards.
1684 With arguments, set guards for the named patch.
1684 With arguments, set guards for the named patch.
1685
1685
1686 To set a negative guard "-foo" on topmost patch ("--" is needed so
1686 To set a negative guard "-foo" on topmost patch ("--" is needed so
1687 hg will not interpret "-foo" as an option):
1687 hg will not interpret "-foo" as an option):
1688 hg qguard -- -foo
1688 hg qguard -- -foo
1689
1689
1690 To set guards on another patch:
1690 To set guards on another patch:
1691 hg qguard other.patch +2.6.17 -stable
1691 hg qguard other.patch +2.6.17 -stable
1692 '''
1692 '''
1693 def status(idx):
1693 def status(idx):
1694 guards = q.series_guards[idx] or ['unguarded']
1694 guards = q.series_guards[idx] or ['unguarded']
1695 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1695 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1696 q = repo.mq
1696 q = repo.mq
1697 patch = None
1697 patch = None
1698 args = list(args)
1698 args = list(args)
1699 if opts['list']:
1699 if opts['list']:
1700 if args or opts['none']:
1700 if args or opts['none']:
1701 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1701 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1702 for i in xrange(len(q.series)):
1702 for i in xrange(len(q.series)):
1703 status(i)
1703 status(i)
1704 return
1704 return
1705 if not args or args[0][0:1] in '-+':
1705 if not args or args[0][0:1] in '-+':
1706 if not q.applied:
1706 if not q.applied:
1707 raise util.Abort(_('no patches applied'))
1707 raise util.Abort(_('no patches applied'))
1708 patch = q.applied[-1].name
1708 patch = q.applied[-1].name
1709 if patch is None and args[0][0:1] not in '-+':
1709 if patch is None and args[0][0:1] not in '-+':
1710 patch = args.pop(0)
1710 patch = args.pop(0)
1711 if patch is None:
1711 if patch is None:
1712 raise util.Abort(_('no patch to work with'))
1712 raise util.Abort(_('no patch to work with'))
1713 if args or opts['none']:
1713 if args or opts['none']:
1714 q.set_guards(q.find_series(patch), args)
1714 q.set_guards(q.find_series(patch), args)
1715 q.save_dirty()
1715 q.save_dirty()
1716 else:
1716 else:
1717 status(q.series.index(q.lookup(patch)))
1717 status(q.series.index(q.lookup(patch)))
1718
1718
1719 def header(ui, repo, patch=None):
1719 def header(ui, repo, patch=None):
1720 """Print the header of the topmost or specified patch"""
1720 """Print the header of the topmost or specified patch"""
1721 q = repo.mq
1721 q = repo.mq
1722
1722
1723 if patch:
1723 if patch:
1724 patch = q.lookup(patch)
1724 patch = q.lookup(patch)
1725 else:
1725 else:
1726 if not q.applied:
1726 if not q.applied:
1727 ui.write('No patches applied\n')
1727 ui.write('No patches applied\n')
1728 return 1
1728 return 1
1729 patch = q.lookup('qtip')
1729 patch = q.lookup('qtip')
1730 message = repo.mq.readheaders(patch)[0]
1730 message = repo.mq.readheaders(patch)[0]
1731
1731
1732 ui.write('\n'.join(message) + '\n')
1732 ui.write('\n'.join(message) + '\n')
1733
1733
1734 def lastsavename(path):
1734 def lastsavename(path):
1735 (directory, base) = os.path.split(path)
1735 (directory, base) = os.path.split(path)
1736 names = os.listdir(directory)
1736 names = os.listdir(directory)
1737 namere = re.compile("%s.([0-9]+)" % base)
1737 namere = re.compile("%s.([0-9]+)" % base)
1738 maxindex = None
1738 maxindex = None
1739 maxname = None
1739 maxname = None
1740 for f in names:
1740 for f in names:
1741 m = namere.match(f)
1741 m = namere.match(f)
1742 if m:
1742 if m:
1743 index = int(m.group(1))
1743 index = int(m.group(1))
1744 if maxindex == None or index > maxindex:
1744 if maxindex == None or index > maxindex:
1745 maxindex = index
1745 maxindex = index
1746 maxname = f
1746 maxname = f
1747 if maxname:
1747 if maxname:
1748 return (os.path.join(directory, maxname), maxindex)
1748 return (os.path.join(directory, maxname), maxindex)
1749 return (None, None)
1749 return (None, None)
1750
1750
1751 def savename(path):
1751 def savename(path):
1752 (last, index) = lastsavename(path)
1752 (last, index) = lastsavename(path)
1753 if last is None:
1753 if last is None:
1754 index = 0
1754 index = 0
1755 newpath = path + ".%d" % (index + 1)
1755 newpath = path + ".%d" % (index + 1)
1756 return newpath
1756 return newpath
1757
1757
1758 def push(ui, repo, patch=None, **opts):
1758 def push(ui, repo, patch=None, **opts):
1759 """push the next patch onto the stack"""
1759 """push the next patch onto the stack"""
1760 q = repo.mq
1760 q = repo.mq
1761 mergeq = None
1761 mergeq = None
1762
1762
1763 if opts['all']:
1763 if opts['all']:
1764 if not q.series:
1764 if not q.series:
1765 raise util.Abort(_('no patches in series'))
1765 raise util.Abort(_('no patches in series'))
1766 patch = q.series[-1]
1766 patch = q.series[-1]
1767 if opts['merge']:
1767 if opts['merge']:
1768 if opts['name']:
1768 if opts['name']:
1769 newpath = opts['name']
1769 newpath = opts['name']
1770 else:
1770 else:
1771 newpath, i = lastsavename(q.path)
1771 newpath, i = lastsavename(q.path)
1772 if not newpath:
1772 if not newpath:
1773 ui.warn("no saved queues found, please use -n\n")
1773 ui.warn("no saved queues found, please use -n\n")
1774 return 1
1774 return 1
1775 mergeq = queue(ui, repo.join(""), newpath)
1775 mergeq = queue(ui, repo.join(""), newpath)
1776 ui.warn("merging with queue at: %s\n" % mergeq.path)
1776 ui.warn("merging with queue at: %s\n" % mergeq.path)
1777 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1777 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1778 mergeq=mergeq)
1778 mergeq=mergeq)
1779 q.save_dirty()
1779 q.save_dirty()
1780 return ret
1780 return ret
1781
1781
1782 def pop(ui, repo, patch=None, **opts):
1782 def pop(ui, repo, patch=None, **opts):
1783 """pop the current patch off the stack"""
1783 """pop the current patch off the stack"""
1784 localupdate = True
1784 localupdate = True
1785 if opts['name']:
1785 if opts['name']:
1786 q = queue(ui, repo.join(""), repo.join(opts['name']))
1786 q = queue(ui, repo.join(""), repo.join(opts['name']))
1787 ui.warn('using patch queue: %s\n' % q.path)
1787 ui.warn('using patch queue: %s\n' % q.path)
1788 localupdate = False
1788 localupdate = False
1789 else:
1789 else:
1790 q = repo.mq
1790 q = repo.mq
1791 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1791 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1792 q.save_dirty()
1792 q.save_dirty()
1793 return 0
1793 return 0
1794
1794
1795 def rename(ui, repo, patch, name=None, **opts):
1795 def rename(ui, repo, patch, name=None, **opts):
1796 """rename a patch
1796 """rename a patch
1797
1797
1798 With one argument, renames the current patch to PATCH1.
1798 With one argument, renames the current patch to PATCH1.
1799 With two arguments, renames PATCH1 to PATCH2."""
1799 With two arguments, renames PATCH1 to PATCH2."""
1800
1800
1801 q = repo.mq
1801 q = repo.mq
1802
1802
1803 if not name:
1803 if not name:
1804 name = patch
1804 name = patch
1805 patch = None
1805 patch = None
1806
1806
1807 if patch:
1807 if patch:
1808 patch = q.lookup(patch)
1808 patch = q.lookup(patch)
1809 else:
1809 else:
1810 if not q.applied:
1810 if not q.applied:
1811 ui.write(_('No patches applied\n'))
1811 ui.write(_('No patches applied\n'))
1812 return
1812 return
1813 patch = q.lookup('qtip')
1813 patch = q.lookup('qtip')
1814 absdest = q.join(name)
1814 absdest = q.join(name)
1815 if os.path.isdir(absdest):
1815 if os.path.isdir(absdest):
1816 name = os.path.join(name, os.path.basename(patch))
1816 name = os.path.join(name, os.path.basename(patch))
1817 absdest = q.join(name)
1817 absdest = q.join(name)
1818 if os.path.exists(absdest):
1818 if os.path.exists(absdest):
1819 raise util.Abort(_('%s already exists') % absdest)
1819 raise util.Abort(_('%s already exists') % absdest)
1820
1820
1821 if name in q.series:
1821 if name in q.series:
1822 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1822 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1823
1823
1824 if ui.verbose:
1824 if ui.verbose:
1825 ui.write('Renaming %s to %s\n' % (patch, name))
1825 ui.write('Renaming %s to %s\n' % (patch, name))
1826 i = q.find_series(patch)
1826 i = q.find_series(patch)
1827 guards = q.guard_re.findall(q.full_series[i])
1827 guards = q.guard_re.findall(q.full_series[i])
1828 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1828 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1829 q.parse_series()
1829 q.parse_series()
1830 q.series_dirty = 1
1830 q.series_dirty = 1
1831
1831
1832 info = q.isapplied(patch)
1832 info = q.isapplied(patch)
1833 if info:
1833 if info:
1834 q.applied[info[0]] = statusentry(info[1], name)
1834 q.applied[info[0]] = statusentry(info[1], name)
1835 q.applied_dirty = 1
1835 q.applied_dirty = 1
1836
1836
1837 util.rename(q.join(patch), absdest)
1837 util.rename(q.join(patch), absdest)
1838 r = q.qrepo()
1838 r = q.qrepo()
1839 if r:
1839 if r:
1840 wlock = r.wlock()
1840 wlock = r.wlock()
1841 if r.dirstate.state(name) == 'r':
1841 if r.dirstate.state(name) == 'r':
1842 r.undelete([name], wlock)
1842 r.undelete([name], wlock)
1843 r.copy(patch, name, wlock)
1843 r.copy(patch, name, wlock)
1844 r.remove([patch], False, wlock)
1844 r.remove([patch], False, wlock)
1845
1845
1846 q.save_dirty()
1846 q.save_dirty()
1847
1847
1848 def restore(ui, repo, rev, **opts):
1848 def restore(ui, repo, rev, **opts):
1849 """restore the queue state saved by a rev"""
1849 """restore the queue state saved by a rev"""
1850 rev = repo.lookup(rev)
1850 rev = repo.lookup(rev)
1851 q = repo.mq
1851 q = repo.mq
1852 q.restore(repo, rev, delete=opts['delete'],
1852 q.restore(repo, rev, delete=opts['delete'],
1853 qupdate=opts['update'])
1853 qupdate=opts['update'])
1854 q.save_dirty()
1854 q.save_dirty()
1855 return 0
1855 return 0
1856
1856
1857 def save(ui, repo, **opts):
1857 def save(ui, repo, **opts):
1858 """save current queue state"""
1858 """save current queue state"""
1859 q = repo.mq
1859 q = repo.mq
1860 message = commands.logmessage(opts)
1860 message = commands.logmessage(opts)
1861 ret = q.save(repo, msg=message)
1861 ret = q.save(repo, msg=message)
1862 if ret:
1862 if ret:
1863 return ret
1863 return ret
1864 q.save_dirty()
1864 q.save_dirty()
1865 if opts['copy']:
1865 if opts['copy']:
1866 path = q.path
1866 path = q.path
1867 if opts['name']:
1867 if opts['name']:
1868 newpath = os.path.join(q.basepath, opts['name'])
1868 newpath = os.path.join(q.basepath, opts['name'])
1869 if os.path.exists(newpath):
1869 if os.path.exists(newpath):
1870 if not os.path.isdir(newpath):
1870 if not os.path.isdir(newpath):
1871 raise util.Abort(_('destination %s exists and is not '
1871 raise util.Abort(_('destination %s exists and is not '
1872 'a directory') % newpath)
1872 'a directory') % newpath)
1873 if not opts['force']:
1873 if not opts['force']:
1874 raise util.Abort(_('destination %s exists, '
1874 raise util.Abort(_('destination %s exists, '
1875 'use -f to force') % newpath)
1875 'use -f to force') % newpath)
1876 else:
1876 else:
1877 newpath = savename(path)
1877 newpath = savename(path)
1878 ui.warn("copy %s to %s\n" % (path, newpath))
1878 ui.warn("copy %s to %s\n" % (path, newpath))
1879 util.copyfiles(path, newpath)
1879 util.copyfiles(path, newpath)
1880 if opts['empty']:
1880 if opts['empty']:
1881 try:
1881 try:
1882 os.unlink(q.join(q.status_path))
1882 os.unlink(q.join(q.status_path))
1883 except:
1883 except:
1884 pass
1884 pass
1885 return 0
1885 return 0
1886
1886
1887 def strip(ui, repo, rev, **opts):
1887 def strip(ui, repo, rev, **opts):
1888 """strip a revision and all later revs on the same branch"""
1888 """strip a revision and all later revs on the same branch"""
1889 rev = repo.lookup(rev)
1889 rev = repo.lookup(rev)
1890 backup = 'all'
1890 backup = 'all'
1891 if opts['backup']:
1891 if opts['backup']:
1892 backup = 'strip'
1892 backup = 'strip'
1893 elif opts['nobackup']:
1893 elif opts['nobackup']:
1894 backup = 'none'
1894 backup = 'none'
1895 update = repo.dirstate.parents()[0] != revlog.nullid
1895 update = repo.dirstate.parents()[0] != revlog.nullid
1896 repo.mq.strip(repo, rev, backup=backup, update=update)
1896 repo.mq.strip(repo, rev, backup=backup, update=update)
1897 return 0
1897 return 0
1898
1898
1899 def select(ui, repo, *args, **opts):
1899 def select(ui, repo, *args, **opts):
1900 '''set or print guarded patches to push
1900 '''set or print guarded patches to push
1901
1901
1902 Use the qguard command to set or print guards on patch, then use
1902 Use the qguard command to set or print guards on patch, then use
1903 qselect to tell mq which guards to use. A patch will be pushed if it
1903 qselect to tell mq which guards to use. A patch will be pushed if it
1904 has no guards or any positive guards match the currently selected guard,
1904 has no guards or any positive guards match the currently selected guard,
1905 but will not be pushed if any negative guards match the current guard.
1905 but will not be pushed if any negative guards match the current guard.
1906 For example:
1906 For example:
1907
1907
1908 qguard foo.patch -stable (negative guard)
1908 qguard foo.patch -stable (negative guard)
1909 qguard bar.patch +stable (positive guard)
1909 qguard bar.patch +stable (positive guard)
1910 qselect stable
1910 qselect stable
1911
1911
1912 This activates the "stable" guard. mq will skip foo.patch (because
1912 This activates the "stable" guard. mq will skip foo.patch (because
1913 it has a negative match) but push bar.patch (because it
1913 it has a negative match) but push bar.patch (because it
1914 has a positive match).
1914 has a positive match).
1915
1915
1916 With no arguments, prints the currently active guards.
1916 With no arguments, prints the currently active guards.
1917 With one argument, sets the active guard.
1917 With one argument, sets the active guard.
1918
1918
1919 Use -n/--none to deactivate guards (no other arguments needed).
1919 Use -n/--none to deactivate guards (no other arguments needed).
1920 When no guards are active, patches with positive guards are skipped
1920 When no guards are active, patches with positive guards are skipped
1921 and patches with negative guards are pushed.
1921 and patches with negative guards are pushed.
1922
1922
1923 qselect can change the guards on applied patches. It does not pop
1923 qselect can change the guards on applied patches. It does not pop
1924 guarded patches by default. Use --pop to pop back to the last applied
1924 guarded patches by default. Use --pop to pop back to the last applied
1925 patch that is not guarded. Use --reapply (which implies --pop) to push
1925 patch that is not guarded. Use --reapply (which implies --pop) to push
1926 back to the current patch afterwards, but skip guarded patches.
1926 back to the current patch afterwards, but skip guarded patches.
1927
1927
1928 Use -s/--series to print a list of all guards in the series file (no
1928 Use -s/--series to print a list of all guards in the series file (no
1929 other arguments needed). Use -v for more information.'''
1929 other arguments needed). Use -v for more information.'''
1930
1930
1931 q = repo.mq
1931 q = repo.mq
1932 guards = q.active()
1932 guards = q.active()
1933 if args or opts['none']:
1933 if args or opts['none']:
1934 old_unapplied = q.unapplied(repo)
1934 old_unapplied = q.unapplied(repo)
1935 old_guarded = [i for i in xrange(len(q.applied)) if
1935 old_guarded = [i for i in xrange(len(q.applied)) if
1936 not q.pushable(i)[0]]
1936 not q.pushable(i)[0]]
1937 q.set_active(args)
1937 q.set_active(args)
1938 q.save_dirty()
1938 q.save_dirty()
1939 if not args:
1939 if not args:
1940 ui.status(_('guards deactivated\n'))
1940 ui.status(_('guards deactivated\n'))
1941 if not opts['pop'] and not opts['reapply']:
1941 if not opts['pop'] and not opts['reapply']:
1942 unapplied = q.unapplied(repo)
1942 unapplied = q.unapplied(repo)
1943 guarded = [i for i in xrange(len(q.applied))
1943 guarded = [i for i in xrange(len(q.applied))
1944 if not q.pushable(i)[0]]
1944 if not q.pushable(i)[0]]
1945 if len(unapplied) != len(old_unapplied):
1945 if len(unapplied) != len(old_unapplied):
1946 ui.status(_('number of unguarded, unapplied patches has '
1946 ui.status(_('number of unguarded, unapplied patches has '
1947 'changed from %d to %d\n') %
1947 'changed from %d to %d\n') %
1948 (len(old_unapplied), len(unapplied)))
1948 (len(old_unapplied), len(unapplied)))
1949 if len(guarded) != len(old_guarded):
1949 if len(guarded) != len(old_guarded):
1950 ui.status(_('number of guarded, applied patches has changed '
1950 ui.status(_('number of guarded, applied patches has changed '
1951 'from %d to %d\n') %
1951 'from %d to %d\n') %
1952 (len(old_guarded), len(guarded)))
1952 (len(old_guarded), len(guarded)))
1953 elif opts['series']:
1953 elif opts['series']:
1954 guards = {}
1954 guards = {}
1955 noguards = 0
1955 noguards = 0
1956 for gs in q.series_guards:
1956 for gs in q.series_guards:
1957 if not gs:
1957 if not gs:
1958 noguards += 1
1958 noguards += 1
1959 for g in gs:
1959 for g in gs:
1960 guards.setdefault(g, 0)
1960 guards.setdefault(g, 0)
1961 guards[g] += 1
1961 guards[g] += 1
1962 if ui.verbose:
1962 if ui.verbose:
1963 guards['NONE'] = noguards
1963 guards['NONE'] = noguards
1964 guards = guards.items()
1964 guards = guards.items()
1965 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1965 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1966 if guards:
1966 if guards:
1967 ui.note(_('guards in series file:\n'))
1967 ui.note(_('guards in series file:\n'))
1968 for guard, count in guards:
1968 for guard, count in guards:
1969 ui.note('%2d ' % count)
1969 ui.note('%2d ' % count)
1970 ui.write(guard, '\n')
1970 ui.write(guard, '\n')
1971 else:
1971 else:
1972 ui.note(_('no guards in series file\n'))
1972 ui.note(_('no guards in series file\n'))
1973 else:
1973 else:
1974 if guards:
1974 if guards:
1975 ui.note(_('active guards:\n'))
1975 ui.note(_('active guards:\n'))
1976 for g in guards:
1976 for g in guards:
1977 ui.write(g, '\n')
1977 ui.write(g, '\n')
1978 else:
1978 else:
1979 ui.write(_('no active guards\n'))
1979 ui.write(_('no active guards\n'))
1980 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1980 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1981 popped = False
1981 popped = False
1982 if opts['pop'] or opts['reapply']:
1982 if opts['pop'] or opts['reapply']:
1983 for i in xrange(len(q.applied)):
1983 for i in xrange(len(q.applied)):
1984 pushable, reason = q.pushable(i)
1984 pushable, reason = q.pushable(i)
1985 if not pushable:
1985 if not pushable:
1986 ui.status(_('popping guarded patches\n'))
1986 ui.status(_('popping guarded patches\n'))
1987 popped = True
1987 popped = True
1988 if i == 0:
1988 if i == 0:
1989 q.pop(repo, all=True)
1989 q.pop(repo, all=True)
1990 else:
1990 else:
1991 q.pop(repo, i-1)
1991 q.pop(repo, i-1)
1992 break
1992 break
1993 if popped:
1993 if popped:
1994 try:
1994 try:
1995 if reapply:
1995 if reapply:
1996 ui.status(_('reapplying unguarded patches\n'))
1996 ui.status(_('reapplying unguarded patches\n'))
1997 q.push(repo, reapply)
1997 q.push(repo, reapply)
1998 finally:
1998 finally:
1999 q.save_dirty()
1999 q.save_dirty()
2000
2000
2001 def reposetup(ui, repo):
2001 def reposetup(ui, repo):
2002 class mqrepo(repo.__class__):
2002 class mqrepo(repo.__class__):
2003 def abort_if_wdir_patched(self, errmsg, force=False):
2003 def abort_if_wdir_patched(self, errmsg, force=False):
2004 if self.mq.applied and not force:
2004 if self.mq.applied and not force:
2005 parent = revlog.hex(self.dirstate.parents()[0])
2005 parent = revlog.hex(self.dirstate.parents()[0])
2006 if parent in [s.rev for s in self.mq.applied]:
2006 if parent in [s.rev for s in self.mq.applied]:
2007 raise util.Abort(errmsg)
2007 raise util.Abort(errmsg)
2008
2008
2009 def commit(self, *args, **opts):
2009 def commit(self, *args, **opts):
2010 if len(args) >= 6:
2010 if len(args) >= 6:
2011 force = args[5]
2011 force = args[5]
2012 else:
2012 else:
2013 force = opts.get('force')
2013 force = opts.get('force')
2014 self.abort_if_wdir_patched(
2014 self.abort_if_wdir_patched(
2015 _('cannot commit over an applied mq patch'),
2015 _('cannot commit over an applied mq patch'),
2016 force)
2016 force)
2017
2017
2018 return super(mqrepo, self).commit(*args, **opts)
2018 return super(mqrepo, self).commit(*args, **opts)
2019
2019
2020 def push(self, remote, force=False, revs=None):
2020 def push(self, remote, force=False, revs=None):
2021 if self.mq.applied and not force:
2021 if self.mq.applied and not force:
2022 raise util.Abort(_('source has mq patches applied'))
2022 raise util.Abort(_('source has mq patches applied'))
2023 return super(mqrepo, self).push(remote, force, revs)
2023 return super(mqrepo, self).push(remote, force, revs)
2024
2024
2025 def tags(self):
2025 def tags(self):
2026 if self.tagscache:
2026 if self.tagscache:
2027 return self.tagscache
2027 return self.tagscache
2028
2028
2029 tagscache = super(mqrepo, self).tags()
2029 tagscache = super(mqrepo, self).tags()
2030
2030
2031 q = self.mq
2031 q = self.mq
2032 if not q.applied:
2032 if not q.applied:
2033 return tagscache
2033 return tagscache
2034
2034
2035 mqtags = [(patch.rev, patch.name) for patch in q.applied]
2035 mqtags = [(patch.rev, patch.name) for patch in q.applied]
2036 mqtags.append((mqtags[-1][0], 'qtip'))
2036 mqtags.append((mqtags[-1][0], 'qtip'))
2037 mqtags.append((mqtags[0][0], 'qbase'))
2037 mqtags.append((mqtags[0][0], 'qbase'))
2038 for patch in mqtags:
2038 for patch in mqtags:
2039 if patch[1] in tagscache:
2039 if patch[1] in tagscache:
2040 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2040 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2041 else:
2041 else:
2042 tagscache[patch[1]] = revlog.bin(patch[0])
2042 tagscache[patch[1]] = revlog.bin(patch[0])
2043
2043
2044 return tagscache
2044 return tagscache
2045
2045
2046 def _branchtags(self):
2046 def _branchtags(self):
2047 q = self.mq
2047 q = self.mq
2048 if not q.applied:
2048 if not q.applied:
2049 return super(mqrepo, self)._branchtags()
2049 return super(mqrepo, self)._branchtags()
2050
2050
2051 self.branchcache = {} # avoid recursion in changectx
2051 self.branchcache = {} # avoid recursion in changectx
2052 cl = self.changelog
2052 cl = self.changelog
2053 partial, last, lrev = self._readbranchcache()
2053 partial, last, lrev = self._readbranchcache()
2054
2054
2055 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2055 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2056 start = lrev + 1
2056 start = lrev + 1
2057 if start < qbase:
2057 if start < qbase:
2058 # update the cache (excluding the patches) and save it
2058 # update the cache (excluding the patches) and save it
2059 self._updatebranchcache(partial, lrev+1, qbase)
2059 self._updatebranchcache(partial, lrev+1, qbase)
2060 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2060 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2061 start = qbase
2061 start = qbase
2062 # if start = qbase, the cache is as updated as it should be.
2062 # if start = qbase, the cache is as updated as it should be.
2063 # if start > qbase, the cache includes (part of) the patches.
2063 # if start > qbase, the cache includes (part of) the patches.
2064 # we might as well use it, but we won't save it.
2064 # we might as well use it, but we won't save it.
2065
2065
2066 # update the cache up to the tip
2066 # update the cache up to the tip
2067 self._updatebranchcache(partial, start, cl.count())
2067 self._updatebranchcache(partial, start, cl.count())
2068
2068
2069 return partial
2069 return partial
2070
2070
2071 if repo.local():
2071 if repo.local():
2072 repo.__class__ = mqrepo
2072 repo.__class__ = mqrepo
2073 repo.mq = queue(ui, repo.join(""))
2073 repo.mq = queue(ui, repo.join(""))
2074
2074
2075 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2075 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2076
2076
2077 cmdtable = {
2077 cmdtable = {
2078 "qapplied": (applied, [] + seriesopts, 'hg qapplied [-s] [PATCH]'),
2078 "qapplied": (applied, [] + seriesopts, 'hg qapplied [-s] [PATCH]'),
2079 "qclone": (clone,
2079 "qclone": (clone,
2080 [('', 'pull', None, _('use pull protocol to copy metadata')),
2080 [('', 'pull', None, _('use pull protocol to copy metadata')),
2081 ('U', 'noupdate', None, _('do not update the new working directories')),
2081 ('U', 'noupdate', None, _('do not update the new working directories')),
2082 ('', 'uncompressed', None,
2082 ('', 'uncompressed', None,
2083 _('use uncompressed transfer (fast over LAN)')),
2083 _('use uncompressed transfer (fast over LAN)')),
2084 ('e', 'ssh', '', _('specify ssh command to use')),
2084 ('e', 'ssh', '', _('specify ssh command to use')),
2085 ('p', 'patches', '', _('location of source patch repo')),
2085 ('p', 'patches', '', _('location of source patch repo')),
2086 ('', 'remotecmd', '',
2086 ('', 'remotecmd', '',
2087 _('specify hg command to run on the remote side'))],
2087 _('specify hg command to run on the remote side'))],
2088 'hg qclone [OPTION]... SOURCE [DEST]'),
2088 'hg qclone [OPTION]... SOURCE [DEST]'),
2089 "qcommit|qci":
2089 "qcommit|qci":
2090 (commit,
2090 (commit,
2091 commands.table["^commit|ci"][1],
2091 commands.table["^commit|ci"][1],
2092 'hg qcommit [OPTION]... [FILE]...'),
2092 'hg qcommit [OPTION]... [FILE]...'),
2093 "^qdiff": (diff,
2093 "^qdiff": (diff,
2094 [('g', 'git', None, _('use git extended diff format')),
2094 [('g', 'git', None, _('use git extended diff format')),
2095 ('I', 'include', [], _('include names matching the given patterns')),
2095 ('I', 'include', [], _('include names matching the given patterns')),
2096 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2096 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2097 'hg qdiff [-I] [-X] [FILE]...'),
2097 'hg qdiff [-I] [-X] [FILE]...'),
2098 "qdelete|qremove|qrm":
2098 "qdelete|qremove|qrm":
2099 (delete,
2099 (delete,
2100 [('k', 'keep', None, _('keep patch file')),
2100 [('k', 'keep', None, _('keep patch file')),
2101 ('r', 'rev', [], _('stop managing a revision'))],
2101 ('r', 'rev', [], _('stop managing a revision'))],
2102 'hg qdelete [-k] [-r REV]... PATCH...'),
2102 'hg qdelete [-k] [-r REV]... PATCH...'),
2103 'qfold':
2103 'qfold':
2104 (fold,
2104 (fold,
2105 [('e', 'edit', None, _('edit patch header')),
2105 [('e', 'edit', None, _('edit patch header')),
2106 ('k', 'keep', None, _('keep folded patch files'))
2106 ('k', 'keep', None, _('keep folded patch files'))
2107 ] + commands.commitopts,
2107 ] + commands.commitopts,
2108 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
2108 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
2109 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
2109 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
2110 ('n', 'none', None, _('drop all guards'))],
2110 ('n', 'none', None, _('drop all guards'))],
2111 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
2111 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
2112 'qheader': (header, [],
2112 'qheader': (header, [],
2113 _('hg qheader [PATCH]')),
2113 _('hg qheader [PATCH]')),
2114 "^qimport":
2114 "^qimport":
2115 (qimport,
2115 (qimport,
2116 [('e', 'existing', None, 'import file in patch dir'),
2116 [('e', 'existing', None, 'import file in patch dir'),
2117 ('n', 'name', '', 'patch file name'),
2117 ('n', 'name', '', 'patch file name'),
2118 ('f', 'force', None, 'overwrite existing files'),
2118 ('f', 'force', None, 'overwrite existing files'),
2119 ('r', 'rev', [], 'place existing revisions under mq control'),
2119 ('r', 'rev', [], 'place existing revisions under mq control'),
2120 ('g', 'git', None, _('use git extended diff format'))],
2120 ('g', 'git', None, _('use git extended diff format'))],
2121 'hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...'),
2121 'hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...'),
2122 "^qinit":
2122 "^qinit":
2123 (init,
2123 (init,
2124 [('c', 'create-repo', None, 'create queue repository')],
2124 [('c', 'create-repo', None, 'create queue repository')],
2125 'hg qinit [-c]'),
2125 'hg qinit [-c]'),
2126 "qnew":
2126 "qnew":
2127 (new,
2127 (new,
2128 [('e', 'edit', None, _('edit commit message')),
2128 [('e', 'edit', None, _('edit commit message')),
2129 ('f', 'force', None, _('import uncommitted changes into patch'))
2129 ('f', 'force', None, _('import uncommitted changes into patch'))
2130 ] + commands.commitopts,
2130 ] + commands.commitopts,
2131 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
2131 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
2132 "qnext": (next, [] + seriesopts, 'hg qnext [-s]'),
2132 "qnext": (next, [] + seriesopts, 'hg qnext [-s]'),
2133 "qprev": (prev, [] + seriesopts, 'hg qprev [-s]'),
2133 "qprev": (prev, [] + seriesopts, 'hg qprev [-s]'),
2134 "^qpop":
2134 "^qpop":
2135 (pop,
2135 (pop,
2136 [('a', 'all', None, 'pop all patches'),
2136 [('a', 'all', None, 'pop all patches'),
2137 ('n', 'name', '', 'queue name to pop'),
2137 ('n', 'name', '', 'queue name to pop'),
2138 ('f', 'force', None, 'forget any local changes')],
2138 ('f', 'force', None, 'forget any local changes')],
2139 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
2139 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
2140 "^qpush":
2140 "^qpush":
2141 (push,
2141 (push,
2142 [('f', 'force', None, 'apply if the patch has rejects'),
2142 [('f', 'force', None, 'apply if the patch has rejects'),
2143 ('l', 'list', None, 'list patch name in commit text'),
2143 ('l', 'list', None, 'list patch name in commit text'),
2144 ('a', 'all', None, 'apply all patches'),
2144 ('a', 'all', None, 'apply all patches'),
2145 ('m', 'merge', None, 'merge from another queue'),
2145 ('m', 'merge', None, 'merge from another queue'),
2146 ('n', 'name', '', 'merge queue name')],
2146 ('n', 'name', '', 'merge queue name')],
2147 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
2147 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
2148 "^qrefresh":
2148 "^qrefresh":
2149 (refresh,
2149 (refresh,
2150 [('e', 'edit', None, _('edit commit message')),
2150 [('e', 'edit', None, _('edit commit message')),
2151 ('g', 'git', None, _('use git extended diff format')),
2151 ('g', 'git', None, _('use git extended diff format')),
2152 ('s', 'short', None, 'refresh only files already in the patch'),
2152 ('s', 'short', None, 'refresh only files already in the patch'),
2153 ('I', 'include', [], _('include names matching the given patterns')),
2153 ('I', 'include', [], _('include names matching the given patterns')),
2154 ('X', 'exclude', [], _('exclude names matching the given patterns'))
2154 ('X', 'exclude', [], _('exclude names matching the given patterns'))
2155 ] + commands.commitopts,
2155 ] + commands.commitopts,
2156 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] FILES...'),
2156 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] FILES...'),
2157 'qrename|qmv':
2157 'qrename|qmv':
2158 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
2158 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
2159 "qrestore":
2159 "qrestore":
2160 (restore,
2160 (restore,
2161 [('d', 'delete', None, 'delete save entry'),
2161 [('d', 'delete', None, 'delete save entry'),
2162 ('u', 'update', None, 'update queue working dir')],
2162 ('u', 'update', None, 'update queue working dir')],
2163 'hg qrestore [-d] [-u] REV'),
2163 'hg qrestore [-d] [-u] REV'),
2164 "qsave":
2164 "qsave":
2165 (save,
2165 (save,
2166 [('c', 'copy', None, 'copy patch directory'),
2166 [('c', 'copy', None, 'copy patch directory'),
2167 ('n', 'name', '', 'copy directory name'),
2167 ('n', 'name', '', 'copy directory name'),
2168 ('e', 'empty', None, 'clear queue status file'),
2168 ('e', 'empty', None, 'clear queue status file'),
2169 ('f', 'force', None, 'force copy')] + commands.commitopts,
2169 ('f', 'force', None, 'force copy')] + commands.commitopts,
2170 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
2170 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
2171 "qselect": (select,
2171 "qselect": (select,
2172 [('n', 'none', None, _('disable all guards')),
2172 [('n', 'none', None, _('disable all guards')),
2173 ('s', 'series', None, _('list all guards in series file')),
2173 ('s', 'series', None, _('list all guards in series file')),
2174 ('', 'pop', None,
2174 ('', 'pop', None,
2175 _('pop to before first guarded applied patch')),
2175 _('pop to before first guarded applied patch')),
2176 ('', 'reapply', None, _('pop, then reapply patches'))],
2176 ('', 'reapply', None, _('pop, then reapply patches'))],
2177 'hg qselect [OPTION...] [GUARD...]'),
2177 'hg qselect [OPTION...] [GUARD...]'),
2178 "qseries":
2178 "qseries":
2179 (series,
2179 (series,
2180 [('m', 'missing', None, 'print patches not in series')] + seriesopts,
2180 [('m', 'missing', None, 'print patches not in series')] + seriesopts,
2181 'hg qseries [-ms]'),
2181 'hg qseries [-ms]'),
2182 "^strip":
2182 "^strip":
2183 (strip,
2183 (strip,
2184 [('f', 'force', None, 'force multi-head removal'),
2184 [('f', 'force', None, 'force multi-head removal'),
2185 ('b', 'backup', None, 'bundle unrelated changesets'),
2185 ('b', 'backup', None, 'bundle unrelated changesets'),
2186 ('n', 'nobackup', None, 'no backups')],
2186 ('n', 'nobackup', None, 'no backups')],
2187 'hg strip [-f] [-b] [-n] REV'),
2187 'hg strip [-f] [-b] [-n] REV'),
2188 "qtop": (top, [] + seriesopts, 'hg qtop [-s]'),
2188 "qtop": (top, [] + seriesopts, 'hg qtop [-s]'),
2189 "qunapplied": (unapplied, [] + seriesopts, 'hg qunapplied [-s] [PATCH]'),
2189 "qunapplied": (unapplied, [] + seriesopts, 'hg qunapplied [-s] [PATCH]'),
2190 }
2190 }
@@ -1,309 +1,309 b''
1 # Command for sending a collection of Mercurial changesets as a series
1 # Command for sending a collection of Mercurial changesets as a series
2 # of patch emails.
2 # of patch emails.
3 #
3 #
4 # The series is started off with a "[PATCH 0 of N]" introduction,
4 # The series is started off with a "[PATCH 0 of N]" introduction,
5 # which describes the series as a whole.
5 # which describes the series as a whole.
6 #
6 #
7 # Each patch email has a Subject line of "[PATCH M of N] ...", using
7 # Each patch email has a Subject line of "[PATCH M of N] ...", using
8 # the first line of the changeset description as the subject text.
8 # the first line of the changeset description as the subject text.
9 # The message contains two or three body parts:
9 # The message contains two or three body parts:
10 #
10 #
11 # The remainder of the changeset description.
11 # The remainder of the changeset description.
12 #
12 #
13 # [Optional] If the diffstat program is installed, the result of
13 # [Optional] If the diffstat program is installed, the result of
14 # running diffstat on the patch.
14 # running diffstat on the patch.
15 #
15 #
16 # The patch itself, as generated by "hg export".
16 # The patch itself, as generated by "hg export".
17 #
17 #
18 # Each message refers to all of its predecessors using the In-Reply-To
18 # Each message refers to all of its predecessors using the In-Reply-To
19 # and References headers, so they will show up as a sequence in
19 # and References headers, so they will show up as a sequence in
20 # threaded mail and news readers, and in mail archives.
20 # threaded mail and news readers, and in mail archives.
21 #
21 #
22 # For each changeset, you will be prompted with a diffstat summary and
22 # For each changeset, you will be prompted with a diffstat summary and
23 # the changeset summary, so you can be sure you are sending the right
23 # the changeset summary, so you can be sure you are sending the right
24 # changes.
24 # changes.
25 #
25 #
26 # To enable this extension:
26 # To enable this extension:
27 #
27 #
28 # [extensions]
28 # [extensions]
29 # hgext.patchbomb =
29 # hgext.patchbomb =
30 #
30 #
31 # To configure other defaults, add a section like this to your hgrc
31 # To configure other defaults, add a section like this to your hgrc
32 # file:
32 # file:
33 #
33 #
34 # [email]
34 # [email]
35 # from = My Name <my@email>
35 # from = My Name <my@email>
36 # to = recipient1, recipient2, ...
36 # to = recipient1, recipient2, ...
37 # cc = cc1, cc2, ...
37 # cc = cc1, cc2, ...
38 # bcc = bcc1, bcc2, ...
38 # bcc = bcc1, bcc2, ...
39 #
39 #
40 # Then you can use the "hg email" command to mail a series of changesets
40 # Then you can use the "hg email" command to mail a series of changesets
41 # as a patchbomb.
41 # as a patchbomb.
42 #
42 #
43 # To avoid sending patches prematurely, it is a good idea to first run
43 # To avoid sending patches prematurely, it is a good idea to first run
44 # the "email" command with the "-n" option (test only). You will be
44 # the "email" command with the "-n" option (test only). You will be
45 # prompted for an email recipient address, a subject an an introductory
45 # prompted for an email recipient address, a subject an an introductory
46 # message describing the patches of your patchbomb. Then when all is
46 # message describing the patches of your patchbomb. Then when all is
47 # done, your pager will be fired up once for each patchbomb message, so
47 # done, your pager will be fired up once for each patchbomb message, so
48 # you can verify everything is alright.
48 # you can verify everything is alright.
49 #
49 #
50 # The "-m" (mbox) option is also very useful. Instead of previewing
50 # The "-m" (mbox) option is also very useful. Instead of previewing
51 # each patchbomb message in a pager or sending the messages directly,
51 # each patchbomb message in a pager or sending the messages directly,
52 # it will create a UNIX mailbox file with the patch emails. This
52 # it will create a UNIX mailbox file with the patch emails. This
53 # mailbox file can be previewed with any mail user agent which supports
53 # mailbox file can be previewed with any mail user agent which supports
54 # UNIX mbox files, i.e. with mutt:
54 # UNIX mbox files, i.e. with mutt:
55 #
55 #
56 # % mutt -R -f mbox
56 # % mutt -R -f mbox
57 #
57 #
58 # When you are previewing the patchbomb messages, you can use `formail'
58 # When you are previewing the patchbomb messages, you can use `formail'
59 # (a utility that is commonly installed as part of the procmail package),
59 # (a utility that is commonly installed as part of the procmail package),
60 # to send each message out:
60 # to send each message out:
61 #
61 #
62 # % formail -s sendmail -bm -t < mbox
62 # % formail -s sendmail -bm -t < mbox
63 #
63 #
64 # That should be all. Now your patchbomb is on its way out.
64 # That should be all. Now your patchbomb is on its way out.
65
65
66 import os, errno, popen2, socket, sys, tempfile, time
66 import os, errno, socket, time
67 import email.MIMEMultipart, email.MIMEText, email.Utils
67 import email.MIMEMultipart, email.MIMEText, email.Utils
68 from mercurial import cmdutil, commands, hg, mail, ui, patch
68 from mercurial import cmdutil, commands, hg, mail, ui, patch
69 from mercurial.i18n import _
69 from mercurial.i18n import _
70 from mercurial.node import *
70 from mercurial.node import *
71
71
72 try:
72 try:
73 # readline gives raw_input editing capabilities, but is not
73 # readline gives raw_input editing capabilities, but is not
74 # present on windows
74 # present on windows
75 import readline
75 import readline
76 except ImportError: pass
76 except ImportError: pass
77
77
78 def patchbomb(ui, repo, *revs, **opts):
78 def patchbomb(ui, repo, *revs, **opts):
79 '''send changesets as a series of patch emails
79 '''send changesets as a series of patch emails
80
80
81 The series starts with a "[PATCH 0 of N]" introduction, which
81 The series starts with a "[PATCH 0 of N]" introduction, which
82 describes the series as a whole.
82 describes the series as a whole.
83
83
84 Each patch email has a Subject line of "[PATCH M of N] ...", using
84 Each patch email has a Subject line of "[PATCH M of N] ...", using
85 the first line of the changeset description as the subject text.
85 the first line of the changeset description as the subject text.
86 The message contains two or three body parts. First, the rest of
86 The message contains two or three body parts. First, the rest of
87 the changeset description. Next, (optionally) if the diffstat
87 the changeset description. Next, (optionally) if the diffstat
88 program is installed, the result of running diffstat on the patch.
88 program is installed, the result of running diffstat on the patch.
89 Finally, the patch itself, as generated by "hg export".'''
89 Finally, the patch itself, as generated by "hg export".'''
90 def prompt(prompt, default = None, rest = ': ', empty_ok = False):
90 def prompt(prompt, default = None, rest = ': ', empty_ok = False):
91 if default: prompt += ' [%s]' % default
91 if default: prompt += ' [%s]' % default
92 prompt += rest
92 prompt += rest
93 while True:
93 while True:
94 r = raw_input(prompt)
94 r = raw_input(prompt)
95 if r: return r
95 if r: return r
96 if default is not None: return default
96 if default is not None: return default
97 if empty_ok: return r
97 if empty_ok: return r
98 ui.warn(_('Please enter a valid value.\n'))
98 ui.warn(_('Please enter a valid value.\n'))
99
99
100 def confirm(s):
100 def confirm(s):
101 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
101 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
102 raise ValueError
102 raise ValueError
103
103
104 def cdiffstat(summary, patchlines):
104 def cdiffstat(summary, patchlines):
105 s = patch.diffstat(patchlines)
105 s = patch.diffstat(patchlines)
106 if s:
106 if s:
107 if summary:
107 if summary:
108 ui.write(summary, '\n')
108 ui.write(summary, '\n')
109 ui.write(s, '\n')
109 ui.write(s, '\n')
110 confirm(_('Does the diffstat above look okay'))
110 confirm(_('Does the diffstat above look okay'))
111 return s
111 return s
112
112
113 def makepatch(patch, idx, total):
113 def makepatch(patch, idx, total):
114 desc = []
114 desc = []
115 node = None
115 node = None
116 body = ''
116 body = ''
117 for line in patch:
117 for line in patch:
118 if line.startswith('#'):
118 if line.startswith('#'):
119 if line.startswith('# Node ID'): node = line.split()[-1]
119 if line.startswith('# Node ID'): node = line.split()[-1]
120 continue
120 continue
121 if (line.startswith('diff -r')
121 if (line.startswith('diff -r')
122 or line.startswith('diff --git')):
122 or line.startswith('diff --git')):
123 break
123 break
124 desc.append(line)
124 desc.append(line)
125 if not node: raise ValueError
125 if not node: raise ValueError
126
126
127 #body = ('\n'.join(desc[1:]).strip() or
127 #body = ('\n'.join(desc[1:]).strip() or
128 # 'Patch subject is complete summary.')
128 # 'Patch subject is complete summary.')
129 #body += '\n\n\n'
129 #body += '\n\n\n'
130
130
131 if opts['plain']:
131 if opts['plain']:
132 while patch and patch[0].startswith('# '): patch.pop(0)
132 while patch and patch[0].startswith('# '): patch.pop(0)
133 if patch: patch.pop(0)
133 if patch: patch.pop(0)
134 while patch and not patch[0].strip(): patch.pop(0)
134 while patch and not patch[0].strip(): patch.pop(0)
135 if opts['diffstat']:
135 if opts['diffstat']:
136 body += cdiffstat('\n'.join(desc), patch) + '\n\n'
136 body += cdiffstat('\n'.join(desc), patch) + '\n\n'
137 if opts['attach']:
137 if opts['attach']:
138 msg = email.MIMEMultipart.MIMEMultipart()
138 msg = email.MIMEMultipart.MIMEMultipart()
139 if body: msg.attach(email.MIMEText.MIMEText(body, 'plain'))
139 if body: msg.attach(email.MIMEText.MIMEText(body, 'plain'))
140 p = email.MIMEText.MIMEText('\n'.join(patch), 'x-patch')
140 p = email.MIMEText.MIMEText('\n'.join(patch), 'x-patch')
141 binnode = bin(node)
141 binnode = bin(node)
142 # if node is mq patch, it will have patch file name as tag
142 # if node is mq patch, it will have patch file name as tag
143 patchname = [t for t in repo.nodetags(binnode)
143 patchname = [t for t in repo.nodetags(binnode)
144 if t.endswith('.patch') or t.endswith('.diff')]
144 if t.endswith('.patch') or t.endswith('.diff')]
145 if patchname:
145 if patchname:
146 patchname = patchname[0]
146 patchname = patchname[0]
147 elif total > 1:
147 elif total > 1:
148 patchname = cmdutil.make_filename(repo, '%b-%n.patch',
148 patchname = cmdutil.make_filename(repo, '%b-%n.patch',
149 binnode, idx, total)
149 binnode, idx, total)
150 else:
150 else:
151 patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
151 patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
152 p['Content-Disposition'] = 'inline; filename=' + patchname
152 p['Content-Disposition'] = 'inline; filename=' + patchname
153 msg.attach(p)
153 msg.attach(p)
154 else:
154 else:
155 body += '\n'.join(patch)
155 body += '\n'.join(patch)
156 msg = email.MIMEText.MIMEText(body)
156 msg = email.MIMEText.MIMEText(body)
157 if total == 1:
157 if total == 1:
158 subj = '[PATCH] ' + desc[0].strip()
158 subj = '[PATCH] ' + desc[0].strip()
159 else:
159 else:
160 tlen = len(str(total))
160 tlen = len(str(total))
161 subj = '[PATCH %0*d of %d] %s' % (tlen, idx, total, desc[0].strip())
161 subj = '[PATCH %0*d of %d] %s' % (tlen, idx, total, desc[0].strip())
162 if subj.endswith('.'): subj = subj[:-1]
162 if subj.endswith('.'): subj = subj[:-1]
163 msg['Subject'] = subj
163 msg['Subject'] = subj
164 msg['X-Mercurial-Node'] = node
164 msg['X-Mercurial-Node'] = node
165 return msg
165 return msg
166
166
167 start_time = int(time.time())
167 start_time = int(time.time())
168
168
169 def genmsgid(id):
169 def genmsgid(id):
170 return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn())
170 return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn())
171
171
172 patches = []
172 patches = []
173
173
174 class exportee:
174 class exportee:
175 def __init__(self, container):
175 def __init__(self, container):
176 self.lines = []
176 self.lines = []
177 self.container = container
177 self.container = container
178 self.name = 'email'
178 self.name = 'email'
179
179
180 def write(self, data):
180 def write(self, data):
181 self.lines.append(data)
181 self.lines.append(data)
182
182
183 def close(self):
183 def close(self):
184 self.container.append(''.join(self.lines).split('\n'))
184 self.container.append(''.join(self.lines).split('\n'))
185 self.lines = []
185 self.lines = []
186
186
187 commands.export(ui, repo, *revs, **{'output': exportee(patches),
187 commands.export(ui, repo, *revs, **{'output': exportee(patches),
188 'switch_parent': False,
188 'switch_parent': False,
189 'text': None,
189 'text': None,
190 'git': opts.get('git')})
190 'git': opts.get('git')})
191
191
192 jumbo = []
192 jumbo = []
193 msgs = []
193 msgs = []
194
194
195 ui.write(_('This patch series consists of %d patches.\n\n') % len(patches))
195 ui.write(_('This patch series consists of %d patches.\n\n') % len(patches))
196
196
197 for p, i in zip(patches, xrange(len(patches))):
197 for p, i in zip(patches, xrange(len(patches))):
198 jumbo.extend(p)
198 jumbo.extend(p)
199 msgs.append(makepatch(p, i + 1, len(patches)))
199 msgs.append(makepatch(p, i + 1, len(patches)))
200
200
201 sender = (opts['from'] or ui.config('email', 'from') or
201 sender = (opts['from'] or ui.config('email', 'from') or
202 ui.config('patchbomb', 'from') or
202 ui.config('patchbomb', 'from') or
203 prompt('From', ui.username()))
203 prompt('From', ui.username()))
204
204
205 def getaddrs(opt, prpt, default = None):
205 def getaddrs(opt, prpt, default = None):
206 addrs = opts[opt] or (ui.config('email', opt) or
206 addrs = opts[opt] or (ui.config('email', opt) or
207 ui.config('patchbomb', opt) or
207 ui.config('patchbomb', opt) or
208 prompt(prpt, default = default)).split(',')
208 prompt(prpt, default = default)).split(',')
209 return [a.strip() for a in addrs if a.strip()]
209 return [a.strip() for a in addrs if a.strip()]
210 to = getaddrs('to', 'To')
210 to = getaddrs('to', 'To')
211 cc = getaddrs('cc', 'Cc', '')
211 cc = getaddrs('cc', 'Cc', '')
212
212
213 bcc = opts['bcc'] or (ui.config('email', 'bcc') or
213 bcc = opts['bcc'] or (ui.config('email', 'bcc') or
214 ui.config('patchbomb', 'bcc') or '').split(',')
214 ui.config('patchbomb', 'bcc') or '').split(',')
215 bcc = [a.strip() for a in bcc if a.strip()]
215 bcc = [a.strip() for a in bcc if a.strip()]
216
216
217 if len(patches) > 1:
217 if len(patches) > 1:
218 tlen = len(str(len(patches)))
218 tlen = len(str(len(patches)))
219
219
220 subj = '[PATCH %0*d of %d] %s' % (
220 subj = '[PATCH %0*d of %d] %s' % (
221 tlen, 0,
221 tlen, 0,
222 len(patches),
222 len(patches),
223 opts['subject'] or
223 opts['subject'] or
224 prompt('Subject:', rest = ' [PATCH %0*d of %d] ' % (tlen, 0,
224 prompt('Subject:', rest = ' [PATCH %0*d of %d] ' % (tlen, 0,
225 len(patches))))
225 len(patches))))
226
226
227 body = ''
227 body = ''
228 if opts['diffstat']:
228 if opts['diffstat']:
229 d = cdiffstat(_('Final summary:\n'), jumbo)
229 d = cdiffstat(_('Final summary:\n'), jumbo)
230 if d: body = '\n' + d
230 if d: body = '\n' + d
231
231
232 ui.write(_('\nWrite the introductory message for the patch series.\n\n'))
232 ui.write(_('\nWrite the introductory message for the patch series.\n\n'))
233 body = ui.edit(body, sender)
233 body = ui.edit(body, sender)
234
234
235 msg = email.MIMEText.MIMEText(body)
235 msg = email.MIMEText.MIMEText(body)
236 msg['Subject'] = subj
236 msg['Subject'] = subj
237
237
238 msgs.insert(0, msg)
238 msgs.insert(0, msg)
239
239
240 ui.write('\n')
240 ui.write('\n')
241
241
242 if not opts['test'] and not opts['mbox']:
242 if not opts['test'] and not opts['mbox']:
243 mailer = mail.connect(ui)
243 mailer = mail.connect(ui)
244 parent = None
244 parent = None
245
245
246 # Calculate UTC offset
246 # Calculate UTC offset
247 if time.daylight: offset = time.altzone
247 if time.daylight: offset = time.altzone
248 else: offset = time.timezone
248 else: offset = time.timezone
249 if offset <= 0: sign, offset = '+', -offset
249 if offset <= 0: sign, offset = '+', -offset
250 else: sign = '-'
250 else: sign = '-'
251 offset = '%s%02d%02d' % (sign, offset / 3600, (offset % 3600) / 60)
251 offset = '%s%02d%02d' % (sign, offset / 3600, (offset % 3600) / 60)
252
252
253 sender_addr = email.Utils.parseaddr(sender)[1]
253 sender_addr = email.Utils.parseaddr(sender)[1]
254 for m in msgs:
254 for m in msgs:
255 try:
255 try:
256 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
256 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
257 except TypeError:
257 except TypeError:
258 m['Message-Id'] = genmsgid('patchbomb')
258 m['Message-Id'] = genmsgid('patchbomb')
259 if parent:
259 if parent:
260 m['In-Reply-To'] = parent
260 m['In-Reply-To'] = parent
261 else:
261 else:
262 parent = m['Message-Id']
262 parent = m['Message-Id']
263 m['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime(start_time)) + ' ' + offset
263 m['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime(start_time)) + ' ' + offset
264
264
265 start_time += 1
265 start_time += 1
266 m['From'] = sender
266 m['From'] = sender
267 m['To'] = ', '.join(to)
267 m['To'] = ', '.join(to)
268 if cc: m['Cc'] = ', '.join(cc)
268 if cc: m['Cc'] = ', '.join(cc)
269 if bcc: m['Bcc'] = ', '.join(bcc)
269 if bcc: m['Bcc'] = ', '.join(bcc)
270 if opts['test']:
270 if opts['test']:
271 ui.status('Displaying ', m['Subject'], ' ...\n')
271 ui.status('Displaying ', m['Subject'], ' ...\n')
272 fp = os.popen(os.getenv('PAGER', 'more'), 'w')
272 fp = os.popen(os.getenv('PAGER', 'more'), 'w')
273 try:
273 try:
274 fp.write(m.as_string(0))
274 fp.write(m.as_string(0))
275 fp.write('\n')
275 fp.write('\n')
276 except IOError, inst:
276 except IOError, inst:
277 if inst.errno != errno.EPIPE:
277 if inst.errno != errno.EPIPE:
278 raise
278 raise
279 fp.close()
279 fp.close()
280 elif opts['mbox']:
280 elif opts['mbox']:
281 ui.status('Writing ', m['Subject'], ' ...\n')
281 ui.status('Writing ', m['Subject'], ' ...\n')
282 fp = open(opts['mbox'], m.has_key('In-Reply-To') and 'ab+' or 'wb+')
282 fp = open(opts['mbox'], m.has_key('In-Reply-To') and 'ab+' or 'wb+')
283 date = time.asctime(time.localtime(start_time))
283 date = time.asctime(time.localtime(start_time))
284 fp.write('From %s %s\n' % (sender_addr, date))
284 fp.write('From %s %s\n' % (sender_addr, date))
285 fp.write(m.as_string(0))
285 fp.write(m.as_string(0))
286 fp.write('\n\n')
286 fp.write('\n\n')
287 fp.close()
287 fp.close()
288 else:
288 else:
289 ui.status('Sending ', m['Subject'], ' ...\n')
289 ui.status('Sending ', m['Subject'], ' ...\n')
290 # Exim does not remove the Bcc field
290 # Exim does not remove the Bcc field
291 del m['Bcc']
291 del m['Bcc']
292 mailer.sendmail(sender, to + bcc + cc, m.as_string(0))
292 mailer.sendmail(sender, to + bcc + cc, m.as_string(0))
293
293
294 cmdtable = {
294 cmdtable = {
295 'email':
295 'email':
296 (patchbomb,
296 (patchbomb,
297 [('a', 'attach', None, 'send patches as inline attachments'),
297 [('a', 'attach', None, 'send patches as inline attachments'),
298 ('', 'bcc', [], 'email addresses of blind copy recipients'),
298 ('', 'bcc', [], 'email addresses of blind copy recipients'),
299 ('c', 'cc', [], 'email addresses of copy recipients'),
299 ('c', 'cc', [], 'email addresses of copy recipients'),
300 ('d', 'diffstat', None, 'add diffstat output to messages'),
300 ('d', 'diffstat', None, 'add diffstat output to messages'),
301 ('g', 'git', None, _('use git extended diff format')),
301 ('g', 'git', None, _('use git extended diff format')),
302 ('f', 'from', '', 'email address of sender'),
302 ('f', 'from', '', 'email address of sender'),
303 ('', 'plain', None, 'omit hg patch header'),
303 ('', 'plain', None, 'omit hg patch header'),
304 ('n', 'test', None, 'print messages that would be sent'),
304 ('n', 'test', None, 'print messages that would be sent'),
305 ('m', 'mbox', '', 'write messages to mbox file instead of sending them'),
305 ('m', 'mbox', '', 'write messages to mbox file instead of sending them'),
306 ('s', 'subject', '', 'subject of introductory message'),
306 ('s', 'subject', '', 'subject of introductory message'),
307 ('t', 'to', [], 'email addresses of recipients')],
307 ('t', 'to', [], 'email addresses of recipients')],
308 "hg email [OPTION]... [REV]...")
308 "hg email [OPTION]... [REV]...")
309 }
309 }
@@ -1,89 +1,89 b''
1 # hgweb/request.py - An http request from either CGI or the standalone server.
1 # hgweb/request.py - An http request from either CGI or the standalone server.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import socket, sys, cgi, os, errno
9 import socket, cgi, errno
10 from mercurial.i18n import gettext as _
10 from mercurial.i18n import gettext as _
11
11
12 class wsgiapplication(object):
12 class wsgiapplication(object):
13 def __init__(self, destmaker):
13 def __init__(self, destmaker):
14 self.destmaker = destmaker
14 self.destmaker = destmaker
15
15
16 def __call__(self, wsgienv, start_response):
16 def __call__(self, wsgienv, start_response):
17 return _wsgirequest(self.destmaker(), wsgienv, start_response)
17 return _wsgirequest(self.destmaker(), wsgienv, start_response)
18
18
19 class _wsgioutputfile(object):
19 class _wsgioutputfile(object):
20 def __init__(self, request):
20 def __init__(self, request):
21 self.request = request
21 self.request = request
22
22
23 def write(self, data):
23 def write(self, data):
24 self.request.write(data)
24 self.request.write(data)
25 def writelines(self, lines):
25 def writelines(self, lines):
26 for line in lines:
26 for line in lines:
27 self.write(line)
27 self.write(line)
28 def flush(self):
28 def flush(self):
29 return None
29 return None
30 def close(self):
30 def close(self):
31 return None
31 return None
32
32
33 class _wsgirequest(object):
33 class _wsgirequest(object):
34 def __init__(self, destination, wsgienv, start_response):
34 def __init__(self, destination, wsgienv, start_response):
35 version = wsgienv['wsgi.version']
35 version = wsgienv['wsgi.version']
36 if (version < (1, 0)) or (version >= (2, 0)):
36 if (version < (1, 0)) or (version >= (2, 0)):
37 raise RuntimeError("Unknown and unsupported WSGI version %d.%d" \
37 raise RuntimeError("Unknown and unsupported WSGI version %d.%d" \
38 % version)
38 % version)
39 self.inp = wsgienv['wsgi.input']
39 self.inp = wsgienv['wsgi.input']
40 self.out = _wsgioutputfile(self)
40 self.out = _wsgioutputfile(self)
41 self.server_write = None
41 self.server_write = None
42 self.err = wsgienv['wsgi.errors']
42 self.err = wsgienv['wsgi.errors']
43 self.threaded = wsgienv['wsgi.multithread']
43 self.threaded = wsgienv['wsgi.multithread']
44 self.multiprocess = wsgienv['wsgi.multiprocess']
44 self.multiprocess = wsgienv['wsgi.multiprocess']
45 self.run_once = wsgienv['wsgi.run_once']
45 self.run_once = wsgienv['wsgi.run_once']
46 self.env = wsgienv
46 self.env = wsgienv
47 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
47 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
48 self.start_response = start_response
48 self.start_response = start_response
49 self.headers = []
49 self.headers = []
50 destination.run_wsgi(self)
50 destination.run_wsgi(self)
51
51
52 def __iter__(self):
52 def __iter__(self):
53 return iter([])
53 return iter([])
54
54
55 def read(self, count=-1):
55 def read(self, count=-1):
56 return self.inp.read(count)
56 return self.inp.read(count)
57
57
58 def write(self, *things):
58 def write(self, *things):
59 for thing in things:
59 for thing in things:
60 if hasattr(thing, "__iter__"):
60 if hasattr(thing, "__iter__"):
61 for part in thing:
61 for part in thing:
62 self.write(part)
62 self.write(part)
63 else:
63 else:
64 thing = str(thing)
64 thing = str(thing)
65 if self.server_write is None:
65 if self.server_write is None:
66 if not self.headers:
66 if not self.headers:
67 raise RuntimeError("request.write called before headers sent (%s)." % thing)
67 raise RuntimeError("request.write called before headers sent (%s)." % thing)
68 self.server_write = self.start_response('200 Script output follows',
68 self.server_write = self.start_response('200 Script output follows',
69 self.headers)
69 self.headers)
70 self.start_response = None
70 self.start_response = None
71 self.headers = None
71 self.headers = None
72 try:
72 try:
73 self.server_write(thing)
73 self.server_write(thing)
74 except socket.error, inst:
74 except socket.error, inst:
75 if inst[0] != errno.ECONNRESET:
75 if inst[0] != errno.ECONNRESET:
76 raise
76 raise
77
77
78 def header(self, headers=[('Content-type','text/html')]):
78 def header(self, headers=[('Content-type','text/html')]):
79 self.headers.extend(headers)
79 self.headers.extend(headers)
80
80
81 def httphdr(self, type, filename=None, length=0, headers={}):
81 def httphdr(self, type, filename=None, length=0, headers={}):
82 headers = headers.items()
82 headers = headers.items()
83 headers.append(('Content-type', type))
83 headers.append(('Content-type', type))
84 if filename:
84 if filename:
85 headers.append(('Content-disposition', 'attachment; filename=%s' %
85 headers.append(('Content-disposition', 'attachment; filename=%s' %
86 filename))
86 filename))
87 if length:
87 if length:
88 headers.append(('Content-length', str(length)))
88 headers.append(('Content-length', str(length)))
89 self.header(headers)
89 self.header(headers)
@@ -1,67 +1,67 b''
1 # mail.py - mail sending bits for mercurial
1 # mail.py - mail sending bits for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 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 from i18n import _
8 from i18n import _
9 import os, re, smtplib, templater, util
9 import os, smtplib, templater, util
10
10
11 def _smtp(ui):
11 def _smtp(ui):
12 '''send mail using smtp.'''
12 '''send mail using smtp.'''
13
13
14 local_hostname = ui.config('smtp', 'local_hostname')
14 local_hostname = ui.config('smtp', 'local_hostname')
15 s = smtplib.SMTP(local_hostname=local_hostname)
15 s = smtplib.SMTP(local_hostname=local_hostname)
16 mailhost = ui.config('smtp', 'host')
16 mailhost = ui.config('smtp', 'host')
17 if not mailhost:
17 if not mailhost:
18 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
18 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
19 mailport = int(ui.config('smtp', 'port', 25))
19 mailport = int(ui.config('smtp', 'port', 25))
20 ui.note(_('sending mail: smtp host %s, port %s\n') %
20 ui.note(_('sending mail: smtp host %s, port %s\n') %
21 (mailhost, mailport))
21 (mailhost, mailport))
22 s.connect(host=mailhost, port=mailport)
22 s.connect(host=mailhost, port=mailport)
23 if ui.configbool('smtp', 'tls'):
23 if ui.configbool('smtp', 'tls'):
24 ui.note(_('(using tls)\n'))
24 ui.note(_('(using tls)\n'))
25 s.ehlo()
25 s.ehlo()
26 s.starttls()
26 s.starttls()
27 s.ehlo()
27 s.ehlo()
28 username = ui.config('smtp', 'username')
28 username = ui.config('smtp', 'username')
29 password = ui.config('smtp', 'password')
29 password = ui.config('smtp', 'password')
30 if username and password:
30 if username and password:
31 ui.note(_('(authenticating to mail server as %s)\n') %
31 ui.note(_('(authenticating to mail server as %s)\n') %
32 (username))
32 (username))
33 s.login(username, password)
33 s.login(username, password)
34 return s
34 return s
35
35
36 class _sendmail(object):
36 class _sendmail(object):
37 '''send mail using sendmail.'''
37 '''send mail using sendmail.'''
38
38
39 def __init__(self, ui, program):
39 def __init__(self, ui, program):
40 self.ui = ui
40 self.ui = ui
41 self.program = program
41 self.program = program
42
42
43 def sendmail(self, sender, recipients, msg):
43 def sendmail(self, sender, recipients, msg):
44 cmdline = '%s -f %s %s' % (
44 cmdline = '%s -f %s %s' % (
45 self.program, templater.email(sender),
45 self.program, templater.email(sender),
46 ' '.join(map(templater.email, recipients)))
46 ' '.join(map(templater.email, recipients)))
47 self.ui.note(_('sending mail: %s\n') % cmdline)
47 self.ui.note(_('sending mail: %s\n') % cmdline)
48 fp = os.popen(cmdline, 'w')
48 fp = os.popen(cmdline, 'w')
49 fp.write(msg)
49 fp.write(msg)
50 ret = fp.close()
50 ret = fp.close()
51 if ret:
51 if ret:
52 raise util.Abort('%s %s' % (
52 raise util.Abort('%s %s' % (
53 os.path.basename(self.program.split(None, 1)[0]),
53 os.path.basename(self.program.split(None, 1)[0]),
54 util.explain_exit(ret)[0]))
54 util.explain_exit(ret)[0]))
55
55
56 def connect(ui):
56 def connect(ui):
57 '''make a mail connection. object returned has one method, sendmail.
57 '''make a mail connection. object returned has one method, sendmail.
58 call as sendmail(sender, list-of-recipients, msg).'''
58 call as sendmail(sender, list-of-recipients, msg).'''
59
59
60 method = ui.config('email', 'method', 'smtp')
60 method = ui.config('email', 'method', 'smtp')
61 if method == 'smtp':
61 if method == 'smtp':
62 return _smtp(ui)
62 return _smtp(ui)
63
63
64 return _sendmail(ui, method)
64 return _sendmail(ui, method)
65
65
66 def sendmail(ui, sender, recipients, msg):
66 def sendmail(ui, sender, recipients, msg):
67 return connect(ui).sendmail(sender, recipients, msg)
67 return connect(ui).sendmail(sender, recipients, msg)
@@ -1,679 +1,679 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 #
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 i18n import _
8 from i18n import _
9 from node import *
9 from node import *
10 import base85, cmdutil, mdiff, util
10 import base85, cmdutil, mdiff, util
11 import cStringIO, email.Parser, errno, os, popen2, re, shutil, sha
11 import cStringIO, email.Parser, os, popen2, re, sha
12 import sys, tempfile, zlib
12 import sys, tempfile, zlib
13
13
14 # helper functions
14 # helper functions
15
15
16 def copyfile(src, dst, basedir=None):
16 def copyfile(src, dst, basedir=None):
17 if not basedir:
17 if not basedir:
18 basedir = os.getcwd()
18 basedir = os.getcwd()
19
19
20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
21 if os.path.exists(absdst):
21 if os.path.exists(absdst):
22 raise util.Abort(_("cannot create %s: destination already exists") %
22 raise util.Abort(_("cannot create %s: destination already exists") %
23 dst)
23 dst)
24
24
25 targetdir = os.path.dirname(absdst)
25 targetdir = os.path.dirname(absdst)
26 if not os.path.isdir(targetdir):
26 if not os.path.isdir(targetdir):
27 os.makedirs(targetdir)
27 os.makedirs(targetdir)
28
28
29 util.copyfile(abssrc, absdst)
29 util.copyfile(abssrc, absdst)
30
30
31 # public functions
31 # public functions
32
32
33 def extract(ui, fileobj):
33 def extract(ui, fileobj):
34 '''extract patch from data read from fileobj.
34 '''extract patch from data read from fileobj.
35
35
36 patch can be normal patch or contained in email message.
36 patch can be normal patch or contained in email message.
37
37
38 return tuple (filename, message, user, date). any item in returned
38 return tuple (filename, message, user, date). any item in returned
39 tuple can be None. if filename is None, fileobj did not contain
39 tuple can be None. if filename is None, fileobj did not contain
40 patch. caller must unlink filename when done.'''
40 patch. caller must unlink filename when done.'''
41
41
42 # attempt to detect the start of a patch
42 # attempt to detect the start of a patch
43 # (this heuristic is borrowed from quilt)
43 # (this heuristic is borrowed from quilt)
44 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
44 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
45 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
45 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
46 '(---|\*\*\*)[ \t])', re.MULTILINE)
46 '(---|\*\*\*)[ \t])', re.MULTILINE)
47
47
48 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
48 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
49 tmpfp = os.fdopen(fd, 'w')
49 tmpfp = os.fdopen(fd, 'w')
50 try:
50 try:
51 hgpatch = False
51 hgpatch = False
52
52
53 msg = email.Parser.Parser().parse(fileobj)
53 msg = email.Parser.Parser().parse(fileobj)
54
54
55 message = msg['Subject']
55 message = msg['Subject']
56 user = msg['From']
56 user = msg['From']
57 # should try to parse msg['Date']
57 # should try to parse msg['Date']
58 date = None
58 date = None
59
59
60 if message:
60 if message:
61 message = message.replace('\n\t', ' ')
61 message = message.replace('\n\t', ' ')
62 ui.debug('Subject: %s\n' % message)
62 ui.debug('Subject: %s\n' % message)
63 if user:
63 if user:
64 ui.debug('From: %s\n' % user)
64 ui.debug('From: %s\n' % user)
65 diffs_seen = 0
65 diffs_seen = 0
66 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
66 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
67
67
68 for part in msg.walk():
68 for part in msg.walk():
69 content_type = part.get_content_type()
69 content_type = part.get_content_type()
70 ui.debug('Content-Type: %s\n' % content_type)
70 ui.debug('Content-Type: %s\n' % content_type)
71 if content_type not in ok_types:
71 if content_type not in ok_types:
72 continue
72 continue
73 payload = part.get_payload(decode=True)
73 payload = part.get_payload(decode=True)
74 m = diffre.search(payload)
74 m = diffre.search(payload)
75 if m:
75 if m:
76 ui.debug(_('found patch at byte %d\n') % m.start(0))
76 ui.debug(_('found patch at byte %d\n') % m.start(0))
77 diffs_seen += 1
77 diffs_seen += 1
78 cfp = cStringIO.StringIO()
78 cfp = cStringIO.StringIO()
79 if message:
79 if message:
80 cfp.write(message)
80 cfp.write(message)
81 cfp.write('\n')
81 cfp.write('\n')
82 for line in payload[:m.start(0)].splitlines():
82 for line in payload[:m.start(0)].splitlines():
83 if line.startswith('# HG changeset patch'):
83 if line.startswith('# HG changeset patch'):
84 ui.debug(_('patch generated by hg export\n'))
84 ui.debug(_('patch generated by hg export\n'))
85 hgpatch = True
85 hgpatch = True
86 # drop earlier commit message content
86 # drop earlier commit message content
87 cfp.seek(0)
87 cfp.seek(0)
88 cfp.truncate()
88 cfp.truncate()
89 elif hgpatch:
89 elif hgpatch:
90 if line.startswith('# User '):
90 if line.startswith('# User '):
91 user = line[7:]
91 user = line[7:]
92 ui.debug('From: %s\n' % user)
92 ui.debug('From: %s\n' % user)
93 elif line.startswith("# Date "):
93 elif line.startswith("# Date "):
94 date = line[7:]
94 date = line[7:]
95 if not line.startswith('# '):
95 if not line.startswith('# '):
96 cfp.write(line)
96 cfp.write(line)
97 cfp.write('\n')
97 cfp.write('\n')
98 message = cfp.getvalue()
98 message = cfp.getvalue()
99 if tmpfp:
99 if tmpfp:
100 tmpfp.write(payload)
100 tmpfp.write(payload)
101 if not payload.endswith('\n'):
101 if not payload.endswith('\n'):
102 tmpfp.write('\n')
102 tmpfp.write('\n')
103 elif not diffs_seen and message and content_type == 'text/plain':
103 elif not diffs_seen and message and content_type == 'text/plain':
104 message += '\n' + payload
104 message += '\n' + payload
105 except:
105 except:
106 tmpfp.close()
106 tmpfp.close()
107 os.unlink(tmpname)
107 os.unlink(tmpname)
108 raise
108 raise
109
109
110 tmpfp.close()
110 tmpfp.close()
111 if not diffs_seen:
111 if not diffs_seen:
112 os.unlink(tmpname)
112 os.unlink(tmpname)
113 return None, message, user, date
113 return None, message, user, date
114 return tmpname, message, user, date
114 return tmpname, message, user, date
115
115
116 GP_PATCH = 1 << 0 # we have to run patch
116 GP_PATCH = 1 << 0 # we have to run patch
117 GP_FILTER = 1 << 1 # there's some copy/rename operation
117 GP_FILTER = 1 << 1 # there's some copy/rename operation
118 GP_BINARY = 1 << 2 # there's a binary patch
118 GP_BINARY = 1 << 2 # there's a binary patch
119
119
120 def readgitpatch(patchname):
120 def readgitpatch(patchname):
121 """extract git-style metadata about patches from <patchname>"""
121 """extract git-style metadata about patches from <patchname>"""
122 class gitpatch:
122 class gitpatch:
123 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
123 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
124 def __init__(self, path):
124 def __init__(self, path):
125 self.path = path
125 self.path = path
126 self.oldpath = None
126 self.oldpath = None
127 self.mode = None
127 self.mode = None
128 self.op = 'MODIFY'
128 self.op = 'MODIFY'
129 self.copymod = False
129 self.copymod = False
130 self.lineno = 0
130 self.lineno = 0
131 self.binary = False
131 self.binary = False
132
132
133 # Filter patch for git information
133 # Filter patch for git information
134 gitre = re.compile('diff --git a/(.*) b/(.*)')
134 gitre = re.compile('diff --git a/(.*) b/(.*)')
135 pf = file(patchname)
135 pf = file(patchname)
136 gp = None
136 gp = None
137 gitpatches = []
137 gitpatches = []
138 # Can have a git patch with only metadata, causing patch to complain
138 # Can have a git patch with only metadata, causing patch to complain
139 dopatch = 0
139 dopatch = 0
140
140
141 lineno = 0
141 lineno = 0
142 for line in pf:
142 for line in pf:
143 lineno += 1
143 lineno += 1
144 if line.startswith('diff --git'):
144 if line.startswith('diff --git'):
145 m = gitre.match(line)
145 m = gitre.match(line)
146 if m:
146 if m:
147 if gp:
147 if gp:
148 gitpatches.append(gp)
148 gitpatches.append(gp)
149 src, dst = m.group(1, 2)
149 src, dst = m.group(1, 2)
150 gp = gitpatch(dst)
150 gp = gitpatch(dst)
151 gp.lineno = lineno
151 gp.lineno = lineno
152 elif gp:
152 elif gp:
153 if line.startswith('--- '):
153 if line.startswith('--- '):
154 if gp.op in ('COPY', 'RENAME'):
154 if gp.op in ('COPY', 'RENAME'):
155 gp.copymod = True
155 gp.copymod = True
156 dopatch |= GP_FILTER
156 dopatch |= GP_FILTER
157 gitpatches.append(gp)
157 gitpatches.append(gp)
158 gp = None
158 gp = None
159 dopatch |= GP_PATCH
159 dopatch |= GP_PATCH
160 continue
160 continue
161 if line.startswith('rename from '):
161 if line.startswith('rename from '):
162 gp.op = 'RENAME'
162 gp.op = 'RENAME'
163 gp.oldpath = line[12:].rstrip()
163 gp.oldpath = line[12:].rstrip()
164 elif line.startswith('rename to '):
164 elif line.startswith('rename to '):
165 gp.path = line[10:].rstrip()
165 gp.path = line[10:].rstrip()
166 elif line.startswith('copy from '):
166 elif line.startswith('copy from '):
167 gp.op = 'COPY'
167 gp.op = 'COPY'
168 gp.oldpath = line[10:].rstrip()
168 gp.oldpath = line[10:].rstrip()
169 elif line.startswith('copy to '):
169 elif line.startswith('copy to '):
170 gp.path = line[8:].rstrip()
170 gp.path = line[8:].rstrip()
171 elif line.startswith('deleted file'):
171 elif line.startswith('deleted file'):
172 gp.op = 'DELETE'
172 gp.op = 'DELETE'
173 elif line.startswith('new file mode '):
173 elif line.startswith('new file mode '):
174 gp.op = 'ADD'
174 gp.op = 'ADD'
175 gp.mode = int(line.rstrip()[-3:], 8)
175 gp.mode = int(line.rstrip()[-3:], 8)
176 elif line.startswith('new mode '):
176 elif line.startswith('new mode '):
177 gp.mode = int(line.rstrip()[-3:], 8)
177 gp.mode = int(line.rstrip()[-3:], 8)
178 elif line.startswith('GIT binary patch'):
178 elif line.startswith('GIT binary patch'):
179 dopatch |= GP_BINARY
179 dopatch |= GP_BINARY
180 gp.binary = True
180 gp.binary = True
181 if gp:
181 if gp:
182 gitpatches.append(gp)
182 gitpatches.append(gp)
183
183
184 if not gitpatches:
184 if not gitpatches:
185 dopatch = GP_PATCH
185 dopatch = GP_PATCH
186
186
187 return (dopatch, gitpatches)
187 return (dopatch, gitpatches)
188
188
189 def dogitpatch(patchname, gitpatches, cwd=None):
189 def dogitpatch(patchname, gitpatches, cwd=None):
190 """Preprocess git patch so that vanilla patch can handle it"""
190 """Preprocess git patch so that vanilla patch can handle it"""
191 def extractbin(fp):
191 def extractbin(fp):
192 i = [0] # yuck
192 i = [0] # yuck
193 def readline():
193 def readline():
194 i[0] += 1
194 i[0] += 1
195 return fp.readline().rstrip()
195 return fp.readline().rstrip()
196 line = readline()
196 line = readline()
197 while line and not line.startswith('literal '):
197 while line and not line.startswith('literal '):
198 line = readline()
198 line = readline()
199 if not line:
199 if not line:
200 return None, i[0]
200 return None, i[0]
201 size = int(line[8:])
201 size = int(line[8:])
202 dec = []
202 dec = []
203 line = readline()
203 line = readline()
204 while line:
204 while line:
205 l = line[0]
205 l = line[0]
206 if l <= 'Z' and l >= 'A':
206 if l <= 'Z' and l >= 'A':
207 l = ord(l) - ord('A') + 1
207 l = ord(l) - ord('A') + 1
208 else:
208 else:
209 l = ord(l) - ord('a') + 27
209 l = ord(l) - ord('a') + 27
210 dec.append(base85.b85decode(line[1:])[:l])
210 dec.append(base85.b85decode(line[1:])[:l])
211 line = readline()
211 line = readline()
212 text = zlib.decompress(''.join(dec))
212 text = zlib.decompress(''.join(dec))
213 if len(text) != size:
213 if len(text) != size:
214 raise util.Abort(_('binary patch is %d bytes, not %d') %
214 raise util.Abort(_('binary patch is %d bytes, not %d') %
215 (len(text), size))
215 (len(text), size))
216 return text, i[0]
216 return text, i[0]
217
217
218 pf = file(patchname)
218 pf = file(patchname)
219 pfline = 1
219 pfline = 1
220
220
221 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
221 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
222 tmpfp = os.fdopen(fd, 'w')
222 tmpfp = os.fdopen(fd, 'w')
223
223
224 try:
224 try:
225 for i in xrange(len(gitpatches)):
225 for i in xrange(len(gitpatches)):
226 p = gitpatches[i]
226 p = gitpatches[i]
227 if not p.copymod and not p.binary:
227 if not p.copymod and not p.binary:
228 continue
228 continue
229
229
230 # rewrite patch hunk
230 # rewrite patch hunk
231 while pfline < p.lineno:
231 while pfline < p.lineno:
232 tmpfp.write(pf.readline())
232 tmpfp.write(pf.readline())
233 pfline += 1
233 pfline += 1
234
234
235 if p.binary:
235 if p.binary:
236 text, delta = extractbin(pf)
236 text, delta = extractbin(pf)
237 if not text:
237 if not text:
238 raise util.Abort(_('binary patch extraction failed'))
238 raise util.Abort(_('binary patch extraction failed'))
239 pfline += delta
239 pfline += delta
240 if not cwd:
240 if not cwd:
241 cwd = os.getcwd()
241 cwd = os.getcwd()
242 absdst = os.path.join(cwd, p.path)
242 absdst = os.path.join(cwd, p.path)
243 basedir = os.path.dirname(absdst)
243 basedir = os.path.dirname(absdst)
244 if not os.path.isdir(basedir):
244 if not os.path.isdir(basedir):
245 os.makedirs(basedir)
245 os.makedirs(basedir)
246 out = file(absdst, 'wb')
246 out = file(absdst, 'wb')
247 out.write(text)
247 out.write(text)
248 out.close()
248 out.close()
249 elif p.copymod:
249 elif p.copymod:
250 copyfile(p.oldpath, p.path, basedir=cwd)
250 copyfile(p.oldpath, p.path, basedir=cwd)
251 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
251 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
252 line = pf.readline()
252 line = pf.readline()
253 pfline += 1
253 pfline += 1
254 while not line.startswith('--- a/'):
254 while not line.startswith('--- a/'):
255 tmpfp.write(line)
255 tmpfp.write(line)
256 line = pf.readline()
256 line = pf.readline()
257 pfline += 1
257 pfline += 1
258 tmpfp.write('--- a/%s\n' % p.path)
258 tmpfp.write('--- a/%s\n' % p.path)
259
259
260 line = pf.readline()
260 line = pf.readline()
261 while line:
261 while line:
262 tmpfp.write(line)
262 tmpfp.write(line)
263 line = pf.readline()
263 line = pf.readline()
264 except:
264 except:
265 tmpfp.close()
265 tmpfp.close()
266 os.unlink(patchname)
266 os.unlink(patchname)
267 raise
267 raise
268
268
269 tmpfp.close()
269 tmpfp.close()
270 return patchname
270 return patchname
271
271
272 def patch(patchname, ui, strip=1, cwd=None, files={}):
272 def patch(patchname, ui, strip=1, cwd=None, files={}):
273 """apply the patch <patchname> to the working directory.
273 """apply the patch <patchname> to the working directory.
274 a list of patched files is returned"""
274 a list of patched files is returned"""
275
275
276 # helper function
276 # helper function
277 def __patch(patchname):
277 def __patch(patchname):
278 """patch and updates the files and fuzz variables"""
278 """patch and updates the files and fuzz variables"""
279 fuzz = False
279 fuzz = False
280
280
281 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''),
281 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''),
282 'patch')
282 'patch')
283 args = []
283 args = []
284 if cwd:
284 if cwd:
285 args.append('-d %s' % util.shellquote(cwd))
285 args.append('-d %s' % util.shellquote(cwd))
286 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
286 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
287 util.shellquote(patchname)))
287 util.shellquote(patchname)))
288
288
289 for line in fp:
289 for line in fp:
290 line = line.rstrip()
290 line = line.rstrip()
291 ui.note(line + '\n')
291 ui.note(line + '\n')
292 if line.startswith('patching file '):
292 if line.startswith('patching file '):
293 pf = util.parse_patch_output(line)
293 pf = util.parse_patch_output(line)
294 printed_file = False
294 printed_file = False
295 files.setdefault(pf, (None, None))
295 files.setdefault(pf, (None, None))
296 elif line.find('with fuzz') >= 0:
296 elif line.find('with fuzz') >= 0:
297 fuzz = True
297 fuzz = True
298 if not printed_file:
298 if not printed_file:
299 ui.warn(pf + '\n')
299 ui.warn(pf + '\n')
300 printed_file = True
300 printed_file = True
301 ui.warn(line + '\n')
301 ui.warn(line + '\n')
302 elif line.find('saving rejects to file') >= 0:
302 elif line.find('saving rejects to file') >= 0:
303 ui.warn(line + '\n')
303 ui.warn(line + '\n')
304 elif line.find('FAILED') >= 0:
304 elif line.find('FAILED') >= 0:
305 if not printed_file:
305 if not printed_file:
306 ui.warn(pf + '\n')
306 ui.warn(pf + '\n')
307 printed_file = True
307 printed_file = True
308 ui.warn(line + '\n')
308 ui.warn(line + '\n')
309 code = fp.close()
309 code = fp.close()
310 if code:
310 if code:
311 raise util.Abort(_("patch command failed: %s") %
311 raise util.Abort(_("patch command failed: %s") %
312 util.explain_exit(code)[0])
312 util.explain_exit(code)[0])
313 return fuzz
313 return fuzz
314
314
315 (dopatch, gitpatches) = readgitpatch(patchname)
315 (dopatch, gitpatches) = readgitpatch(patchname)
316 for gp in gitpatches:
316 for gp in gitpatches:
317 files[gp.path] = (gp.op, gp)
317 files[gp.path] = (gp.op, gp)
318
318
319 fuzz = False
319 fuzz = False
320 if dopatch:
320 if dopatch:
321 filterpatch = dopatch & (GP_FILTER | GP_BINARY)
321 filterpatch = dopatch & (GP_FILTER | GP_BINARY)
322 if filterpatch:
322 if filterpatch:
323 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
323 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
324 try:
324 try:
325 if dopatch & GP_PATCH:
325 if dopatch & GP_PATCH:
326 fuzz = __patch(patchname)
326 fuzz = __patch(patchname)
327 finally:
327 finally:
328 if filterpatch:
328 if filterpatch:
329 os.unlink(patchname)
329 os.unlink(patchname)
330
330
331 return fuzz
331 return fuzz
332
332
333 def diffopts(ui, opts={}, untrusted=False):
333 def diffopts(ui, opts={}, untrusted=False):
334 def get(key, name=None):
334 def get(key, name=None):
335 return (opts.get(key) or
335 return (opts.get(key) or
336 ui.configbool('diff', name or key, None, untrusted=untrusted))
336 ui.configbool('diff', name or key, None, untrusted=untrusted))
337 return mdiff.diffopts(
337 return mdiff.diffopts(
338 text=opts.get('text'),
338 text=opts.get('text'),
339 git=get('git'),
339 git=get('git'),
340 nodates=get('nodates'),
340 nodates=get('nodates'),
341 showfunc=get('show_function', 'showfunc'),
341 showfunc=get('show_function', 'showfunc'),
342 ignorews=get('ignore_all_space', 'ignorews'),
342 ignorews=get('ignore_all_space', 'ignorews'),
343 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
343 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
344 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
344 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
345
345
346 def updatedir(ui, repo, patches, wlock=None):
346 def updatedir(ui, repo, patches, wlock=None):
347 '''Update dirstate after patch application according to metadata'''
347 '''Update dirstate after patch application according to metadata'''
348 if not patches:
348 if not patches:
349 return
349 return
350 copies = []
350 copies = []
351 removes = {}
351 removes = {}
352 cfiles = patches.keys()
352 cfiles = patches.keys()
353 cwd = repo.getcwd()
353 cwd = repo.getcwd()
354 if cwd:
354 if cwd:
355 cfiles = [util.pathto(cwd, f) for f in patches.keys()]
355 cfiles = [util.pathto(cwd, f) for f in patches.keys()]
356 for f in patches:
356 for f in patches:
357 ctype, gp = patches[f]
357 ctype, gp = patches[f]
358 if ctype == 'RENAME':
358 if ctype == 'RENAME':
359 copies.append((gp.oldpath, gp.path, gp.copymod))
359 copies.append((gp.oldpath, gp.path, gp.copymod))
360 removes[gp.oldpath] = 1
360 removes[gp.oldpath] = 1
361 elif ctype == 'COPY':
361 elif ctype == 'COPY':
362 copies.append((gp.oldpath, gp.path, gp.copymod))
362 copies.append((gp.oldpath, gp.path, gp.copymod))
363 elif ctype == 'DELETE':
363 elif ctype == 'DELETE':
364 removes[gp.path] = 1
364 removes[gp.path] = 1
365 for src, dst, after in copies:
365 for src, dst, after in copies:
366 if not after:
366 if not after:
367 copyfile(src, dst, repo.root)
367 copyfile(src, dst, repo.root)
368 repo.copy(src, dst, wlock=wlock)
368 repo.copy(src, dst, wlock=wlock)
369 removes = removes.keys()
369 removes = removes.keys()
370 if removes:
370 if removes:
371 removes.sort()
371 removes.sort()
372 repo.remove(removes, True, wlock=wlock)
372 repo.remove(removes, True, wlock=wlock)
373 for f in patches:
373 for f in patches:
374 ctype, gp = patches[f]
374 ctype, gp = patches[f]
375 if gp and gp.mode:
375 if gp and gp.mode:
376 x = gp.mode & 0100 != 0
376 x = gp.mode & 0100 != 0
377 dst = os.path.join(repo.root, gp.path)
377 dst = os.path.join(repo.root, gp.path)
378 # patch won't create empty files
378 # patch won't create empty files
379 if ctype == 'ADD' and not os.path.exists(dst):
379 if ctype == 'ADD' and not os.path.exists(dst):
380 repo.wwrite(gp.path, '')
380 repo.wwrite(gp.path, '')
381 util.set_exec(dst, x)
381 util.set_exec(dst, x)
382 cmdutil.addremove(repo, cfiles, wlock=wlock)
382 cmdutil.addremove(repo, cfiles, wlock=wlock)
383 files = patches.keys()
383 files = patches.keys()
384 files.extend([r for r in removes if r not in files])
384 files.extend([r for r in removes if r not in files])
385 files.sort()
385 files.sort()
386
386
387 return files
387 return files
388
388
389 def b85diff(fp, to, tn):
389 def b85diff(fp, to, tn):
390 '''print base85-encoded binary diff'''
390 '''print base85-encoded binary diff'''
391 def gitindex(text):
391 def gitindex(text):
392 if not text:
392 if not text:
393 return '0' * 40
393 return '0' * 40
394 l = len(text)
394 l = len(text)
395 s = sha.new('blob %d\0' % l)
395 s = sha.new('blob %d\0' % l)
396 s.update(text)
396 s.update(text)
397 return s.hexdigest()
397 return s.hexdigest()
398
398
399 def fmtline(line):
399 def fmtline(line):
400 l = len(line)
400 l = len(line)
401 if l <= 26:
401 if l <= 26:
402 l = chr(ord('A') + l - 1)
402 l = chr(ord('A') + l - 1)
403 else:
403 else:
404 l = chr(l - 26 + ord('a') - 1)
404 l = chr(l - 26 + ord('a') - 1)
405 return '%c%s\n' % (l, base85.b85encode(line, True))
405 return '%c%s\n' % (l, base85.b85encode(line, True))
406
406
407 def chunk(text, csize=52):
407 def chunk(text, csize=52):
408 l = len(text)
408 l = len(text)
409 i = 0
409 i = 0
410 while i < l:
410 while i < l:
411 yield text[i:i+csize]
411 yield text[i:i+csize]
412 i += csize
412 i += csize
413
413
414 # TODO: deltas
414 # TODO: deltas
415 l = len(tn)
415 l = len(tn)
416 fp.write('index %s..%s\nGIT binary patch\nliteral %s\n' %
416 fp.write('index %s..%s\nGIT binary patch\nliteral %s\n' %
417 (gitindex(to), gitindex(tn), len(tn)))
417 (gitindex(to), gitindex(tn), len(tn)))
418
418
419 tn = ''.join([fmtline(l) for l in chunk(zlib.compress(tn))])
419 tn = ''.join([fmtline(l) for l in chunk(zlib.compress(tn))])
420 fp.write(tn)
420 fp.write(tn)
421 fp.write('\n')
421 fp.write('\n')
422
422
423 def diff(repo, node1=None, node2=None, files=None, match=util.always,
423 def diff(repo, node1=None, node2=None, files=None, match=util.always,
424 fp=None, changes=None, opts=None):
424 fp=None, changes=None, opts=None):
425 '''print diff of changes to files between two nodes, or node and
425 '''print diff of changes to files between two nodes, or node and
426 working directory.
426 working directory.
427
427
428 if node1 is None, use first dirstate parent instead.
428 if node1 is None, use first dirstate parent instead.
429 if node2 is None, compare node1 with working directory.'''
429 if node2 is None, compare node1 with working directory.'''
430
430
431 if opts is None:
431 if opts is None:
432 opts = mdiff.defaultopts
432 opts = mdiff.defaultopts
433 if fp is None:
433 if fp is None:
434 fp = repo.ui
434 fp = repo.ui
435
435
436 if not node1:
436 if not node1:
437 node1 = repo.dirstate.parents()[0]
437 node1 = repo.dirstate.parents()[0]
438
438
439 clcache = {}
439 clcache = {}
440 def getchangelog(n):
440 def getchangelog(n):
441 if n not in clcache:
441 if n not in clcache:
442 clcache[n] = repo.changelog.read(n)
442 clcache[n] = repo.changelog.read(n)
443 return clcache[n]
443 return clcache[n]
444 mcache = {}
444 mcache = {}
445 def getmanifest(n):
445 def getmanifest(n):
446 if n not in mcache:
446 if n not in mcache:
447 mcache[n] = repo.manifest.read(n)
447 mcache[n] = repo.manifest.read(n)
448 return mcache[n]
448 return mcache[n]
449 fcache = {}
449 fcache = {}
450 def getfile(f):
450 def getfile(f):
451 if f not in fcache:
451 if f not in fcache:
452 fcache[f] = repo.file(f)
452 fcache[f] = repo.file(f)
453 return fcache[f]
453 return fcache[f]
454
454
455 # reading the data for node1 early allows it to play nicely
455 # reading the data for node1 early allows it to play nicely
456 # with repo.status and the revlog cache.
456 # with repo.status and the revlog cache.
457 change = getchangelog(node1)
457 change = getchangelog(node1)
458 mmap = getmanifest(change[0])
458 mmap = getmanifest(change[0])
459 date1 = util.datestr(change[2])
459 date1 = util.datestr(change[2])
460
460
461 if not changes:
461 if not changes:
462 changes = repo.status(node1, node2, files, match=match)[:5]
462 changes = repo.status(node1, node2, files, match=match)[:5]
463 modified, added, removed, deleted, unknown = changes
463 modified, added, removed, deleted, unknown = changes
464 if files:
464 if files:
465 def filterfiles(filters):
465 def filterfiles(filters):
466 l = [x for x in filters if x in files]
466 l = [x for x in filters if x in files]
467
467
468 for t in files:
468 for t in files:
469 if not t.endswith("/"):
469 if not t.endswith("/"):
470 t += "/"
470 t += "/"
471 l += [x for x in filters if x.startswith(t)]
471 l += [x for x in filters if x.startswith(t)]
472 return l
472 return l
473
473
474 modified, added, removed = map(filterfiles, (modified, added, removed))
474 modified, added, removed = map(filterfiles, (modified, added, removed))
475
475
476 if not modified and not added and not removed:
476 if not modified and not added and not removed:
477 return
477 return
478
478
479 # returns False if there was no rename between n1 and n2
479 # returns False if there was no rename between n1 and n2
480 # returns None if the file was created between n1 and n2
480 # returns None if the file was created between n1 and n2
481 # returns the (file, node) present in n1 that was renamed to f in n2
481 # returns the (file, node) present in n1 that was renamed to f in n2
482 def renamedbetween(f, n1, n2):
482 def renamedbetween(f, n1, n2):
483 r1, r2 = map(repo.changelog.rev, (n1, n2))
483 r1, r2 = map(repo.changelog.rev, (n1, n2))
484 orig = f
484 orig = f
485 src = None
485 src = None
486 while r2 > r1:
486 while r2 > r1:
487 cl = getchangelog(n2)
487 cl = getchangelog(n2)
488 if f in cl[3]:
488 if f in cl[3]:
489 m = getmanifest(cl[0])
489 m = getmanifest(cl[0])
490 try:
490 try:
491 src = getfile(f).renamed(m[f])
491 src = getfile(f).renamed(m[f])
492 except KeyError:
492 except KeyError:
493 return None
493 return None
494 if src:
494 if src:
495 f = src[0]
495 f = src[0]
496 n2 = repo.changelog.parents(n2)[0]
496 n2 = repo.changelog.parents(n2)[0]
497 r2 = repo.changelog.rev(n2)
497 r2 = repo.changelog.rev(n2)
498 cl = getchangelog(n1)
498 cl = getchangelog(n1)
499 m = getmanifest(cl[0])
499 m = getmanifest(cl[0])
500 if f not in m:
500 if f not in m:
501 return None
501 return None
502 if f == orig:
502 if f == orig:
503 return False
503 return False
504 return f, m[f]
504 return f, m[f]
505
505
506 if node2:
506 if node2:
507 change = getchangelog(node2)
507 change = getchangelog(node2)
508 mmap2 = getmanifest(change[0])
508 mmap2 = getmanifest(change[0])
509 _date2 = util.datestr(change[2])
509 _date2 = util.datestr(change[2])
510 def date2(f):
510 def date2(f):
511 return _date2
511 return _date2
512 def read(f):
512 def read(f):
513 return getfile(f).read(mmap2[f])
513 return getfile(f).read(mmap2[f])
514 def renamed(f):
514 def renamed(f):
515 return renamedbetween(f, node1, node2)
515 return renamedbetween(f, node1, node2)
516 else:
516 else:
517 tz = util.makedate()[1]
517 tz = util.makedate()[1]
518 _date2 = util.datestr()
518 _date2 = util.datestr()
519 def date2(f):
519 def date2(f):
520 try:
520 try:
521 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
521 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
522 except OSError, err:
522 except OSError, err:
523 if err.errno != errno.ENOENT: raise
523 if err.errno != errno.ENOENT: raise
524 return _date2
524 return _date2
525 def read(f):
525 def read(f):
526 return repo.wread(f)
526 return repo.wread(f)
527 def renamed(f):
527 def renamed(f):
528 src = repo.dirstate.copied(f)
528 src = repo.dirstate.copied(f)
529 parent = repo.dirstate.parents()[0]
529 parent = repo.dirstate.parents()[0]
530 if src:
530 if src:
531 f = src
531 f = src
532 of = renamedbetween(f, node1, parent)
532 of = renamedbetween(f, node1, parent)
533 if of or of is None:
533 if of or of is None:
534 return of
534 return of
535 elif src:
535 elif src:
536 cl = getchangelog(parent)[0]
536 cl = getchangelog(parent)[0]
537 return (src, getmanifest(cl)[src])
537 return (src, getmanifest(cl)[src])
538 else:
538 else:
539 return None
539 return None
540
540
541 if repo.ui.quiet:
541 if repo.ui.quiet:
542 r = None
542 r = None
543 else:
543 else:
544 hexfunc = repo.ui.debugflag and hex or short
544 hexfunc = repo.ui.debugflag and hex or short
545 r = [hexfunc(node) for node in [node1, node2] if node]
545 r = [hexfunc(node) for node in [node1, node2] if node]
546
546
547 if opts.git:
547 if opts.git:
548 copied = {}
548 copied = {}
549 for f in added:
549 for f in added:
550 src = renamed(f)
550 src = renamed(f)
551 if src:
551 if src:
552 copied[f] = src
552 copied[f] = src
553 srcs = [x[1][0] for x in copied.items()]
553 srcs = [x[1][0] for x in copied.items()]
554
554
555 all = modified + added + removed
555 all = modified + added + removed
556 all.sort()
556 all.sort()
557 gone = {}
557 gone = {}
558 for f in all:
558 for f in all:
559 to = None
559 to = None
560 tn = None
560 tn = None
561 dodiff = True
561 dodiff = True
562 header = []
562 header = []
563 if f in mmap:
563 if f in mmap:
564 to = getfile(f).read(mmap[f])
564 to = getfile(f).read(mmap[f])
565 if f not in removed:
565 if f not in removed:
566 tn = read(f)
566 tn = read(f)
567 if opts.git:
567 if opts.git:
568 def gitmode(x):
568 def gitmode(x):
569 return x and '100755' or '100644'
569 return x and '100755' or '100644'
570 def addmodehdr(header, omode, nmode):
570 def addmodehdr(header, omode, nmode):
571 if omode != nmode:
571 if omode != nmode:
572 header.append('old mode %s\n' % omode)
572 header.append('old mode %s\n' % omode)
573 header.append('new mode %s\n' % nmode)
573 header.append('new mode %s\n' % nmode)
574
574
575 a, b = f, f
575 a, b = f, f
576 if f in added:
576 if f in added:
577 if node2:
577 if node2:
578 mode = gitmode(mmap2.execf(f))
578 mode = gitmode(mmap2.execf(f))
579 else:
579 else:
580 mode = gitmode(util.is_exec(repo.wjoin(f), None))
580 mode = gitmode(util.is_exec(repo.wjoin(f), None))
581 if f in copied:
581 if f in copied:
582 a, arev = copied[f]
582 a, arev = copied[f]
583 omode = gitmode(mmap.execf(a))
583 omode = gitmode(mmap.execf(a))
584 addmodehdr(header, omode, mode)
584 addmodehdr(header, omode, mode)
585 if a in removed and a not in gone:
585 if a in removed and a not in gone:
586 op = 'rename'
586 op = 'rename'
587 gone[a] = 1
587 gone[a] = 1
588 else:
588 else:
589 op = 'copy'
589 op = 'copy'
590 header.append('%s from %s\n' % (op, a))
590 header.append('%s from %s\n' % (op, a))
591 header.append('%s to %s\n' % (op, f))
591 header.append('%s to %s\n' % (op, f))
592 to = getfile(a).read(arev)
592 to = getfile(a).read(arev)
593 else:
593 else:
594 header.append('new file mode %s\n' % mode)
594 header.append('new file mode %s\n' % mode)
595 if util.binary(tn):
595 if util.binary(tn):
596 dodiff = 'binary'
596 dodiff = 'binary'
597 elif f in removed:
597 elif f in removed:
598 if f in srcs:
598 if f in srcs:
599 dodiff = False
599 dodiff = False
600 else:
600 else:
601 mode = gitmode(mmap.execf(f))
601 mode = gitmode(mmap.execf(f))
602 header.append('deleted file mode %s\n' % mode)
602 header.append('deleted file mode %s\n' % mode)
603 else:
603 else:
604 omode = gitmode(mmap.execf(f))
604 omode = gitmode(mmap.execf(f))
605 if node2:
605 if node2:
606 nmode = gitmode(mmap2.execf(f))
606 nmode = gitmode(mmap2.execf(f))
607 else:
607 else:
608 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
608 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
609 addmodehdr(header, omode, nmode)
609 addmodehdr(header, omode, nmode)
610 if util.binary(to) or util.binary(tn):
610 if util.binary(to) or util.binary(tn):
611 dodiff = 'binary'
611 dodiff = 'binary'
612 r = None
612 r = None
613 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
613 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
614 if dodiff == 'binary':
614 if dodiff == 'binary':
615 fp.write(''.join(header))
615 fp.write(''.join(header))
616 b85diff(fp, to, tn)
616 b85diff(fp, to, tn)
617 elif dodiff:
617 elif dodiff:
618 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
618 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
619 if text or len(header) > 1:
619 if text or len(header) > 1:
620 fp.write(''.join(header))
620 fp.write(''.join(header))
621 fp.write(text)
621 fp.write(text)
622
622
623 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
623 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
624 opts=None):
624 opts=None):
625 '''export changesets as hg patches.'''
625 '''export changesets as hg patches.'''
626
626
627 total = len(revs)
627 total = len(revs)
628 revwidth = max([len(str(rev)) for rev in revs])
628 revwidth = max([len(str(rev)) for rev in revs])
629
629
630 def single(node, seqno, fp):
630 def single(node, seqno, fp):
631 parents = [p for p in repo.changelog.parents(node) if p != nullid]
631 parents = [p for p in repo.changelog.parents(node) if p != nullid]
632 if switch_parent:
632 if switch_parent:
633 parents.reverse()
633 parents.reverse()
634 prev = (parents and parents[0]) or nullid
634 prev = (parents and parents[0]) or nullid
635 change = repo.changelog.read(node)
635 change = repo.changelog.read(node)
636
636
637 if not fp:
637 if not fp:
638 fp = cmdutil.make_file(repo, template, node, total=total,
638 fp = cmdutil.make_file(repo, template, node, total=total,
639 seqno=seqno, revwidth=revwidth)
639 seqno=seqno, revwidth=revwidth)
640 if fp not in (sys.stdout, repo.ui):
640 if fp not in (sys.stdout, repo.ui):
641 repo.ui.note("%s\n" % fp.name)
641 repo.ui.note("%s\n" % fp.name)
642
642
643 fp.write("# HG changeset patch\n")
643 fp.write("# HG changeset patch\n")
644 fp.write("# User %s\n" % change[1])
644 fp.write("# User %s\n" % change[1])
645 fp.write("# Date %d %d\n" % change[2])
645 fp.write("# Date %d %d\n" % change[2])
646 fp.write("# Node ID %s\n" % hex(node))
646 fp.write("# Node ID %s\n" % hex(node))
647 fp.write("# Parent %s\n" % hex(prev))
647 fp.write("# Parent %s\n" % hex(prev))
648 if len(parents) > 1:
648 if len(parents) > 1:
649 fp.write("# Parent %s\n" % hex(parents[1]))
649 fp.write("# Parent %s\n" % hex(parents[1]))
650 fp.write(change[4].rstrip())
650 fp.write(change[4].rstrip())
651 fp.write("\n\n")
651 fp.write("\n\n")
652
652
653 diff(repo, prev, node, fp=fp, opts=opts)
653 diff(repo, prev, node, fp=fp, opts=opts)
654 if fp not in (sys.stdout, repo.ui):
654 if fp not in (sys.stdout, repo.ui):
655 fp.close()
655 fp.close()
656
656
657 for seqno, rev in enumerate(revs):
657 for seqno, rev in enumerate(revs):
658 single(repo.lookup(rev), seqno+1, fp)
658 single(repo.lookup(rev), seqno+1, fp)
659
659
660 def diffstat(patchlines):
660 def diffstat(patchlines):
661 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
661 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
662 try:
662 try:
663 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
663 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
664 try:
664 try:
665 for line in patchlines: print >> p.tochild, line
665 for line in patchlines: print >> p.tochild, line
666 p.tochild.close()
666 p.tochild.close()
667 if p.wait(): return
667 if p.wait(): return
668 fp = os.fdopen(fd, 'r')
668 fp = os.fdopen(fd, 'r')
669 stat = []
669 stat = []
670 for line in fp: stat.append(line.lstrip())
670 for line in fp: stat.append(line.lstrip())
671 last = stat.pop()
671 last = stat.pop()
672 stat.insert(0, last)
672 stat.insert(0, last)
673 stat = ''.join(stat)
673 stat = ''.join(stat)
674 if stat.startswith('0 files'): raise ValueError
674 if stat.startswith('0 files'): raise ValueError
675 return stat
675 return stat
676 except: raise
676 except: raise
677 finally:
677 finally:
678 try: os.unlink(name)
678 try: os.unlink(name)
679 except: pass
679 except: pass
General Comments 0
You need to be logged in to leave comments. Login now