##// END OF EJS Templates
Merge with crew-stable
Alexis S. L. Carvalho -
r5336:24de0275 merge default
parent child Browse files
Show More
@@ -1,119 +1,124 b''
1 # git support for the convert extension
1 # git support for the convert extension
2
2
3 import os
3 import os
4 from mercurial import util
4 from mercurial import util
5
5
6 from common import NoRepo, commit, converter_source
6 from common import NoRepo, commit, converter_source
7
7
8 class convert_git(converter_source):
8 class convert_git(converter_source):
9 # Windows does not support GIT_DIR= construct while other systems
9 # Windows does not support GIT_DIR= construct while other systems
10 # cannot remove environment variable. Just assume none have
10 # cannot remove environment variable. Just assume none have
11 # both issues.
11 # both issues.
12 if hasattr(os, 'unsetenv'):
12 if hasattr(os, 'unsetenv'):
13 def gitcmd(self, s):
13 def gitcmd(self, s):
14 prevgitdir = os.environ.get('GIT_DIR')
14 prevgitdir = os.environ.get('GIT_DIR')
15 os.environ['GIT_DIR'] = self.path
15 os.environ['GIT_DIR'] = self.path
16 try:
16 try:
17 return os.popen(s)
17 return os.popen(s)
18 finally:
18 finally:
19 if prevgitdir is None:
19 if prevgitdir is None:
20 del os.environ['GIT_DIR']
20 del os.environ['GIT_DIR']
21 else:
21 else:
22 os.environ['GIT_DIR'] = prevgitdir
22 os.environ['GIT_DIR'] = prevgitdir
23 else:
23 else:
24 def gitcmd(self, s):
24 def gitcmd(self, s):
25 return os.popen('GIT_DIR=%s %s' % (self.path, s))
25 return os.popen('GIT_DIR=%s %s' % (self.path, s))
26
26
27 def __init__(self, ui, path, rev=None):
27 def __init__(self, ui, path, rev=None):
28 super(convert_git, self).__init__(ui, path, rev=rev)
28 super(convert_git, self).__init__(ui, path, rev=rev)
29
29
30 if os.path.isdir(path + "/.git"):
30 if os.path.isdir(path + "/.git"):
31 path += "/.git"
31 path += "/.git"
32 if not os.path.exists(path + "/objects"):
32 if not os.path.exists(path + "/objects"):
33 raise NoRepo("couldn't open GIT repo %s" % path)
33 raise NoRepo("couldn't open GIT repo %s" % path)
34 self.path = path
34 self.path = path
35
35
36 def getheads(self):
36 def getheads(self):
37 if not self.rev:
37 if not self.rev:
38 return self.gitcmd('git-rev-parse --branches').read().splitlines()
38 return self.gitcmd('git-rev-parse --branches').read().splitlines()
39 else:
39 else:
40 fh = self.gitcmd("git-rev-parse --verify %s" % self.rev)
40 fh = self.gitcmd("git-rev-parse --verify %s" % self.rev)
41 return [fh.read()[:-1]]
41 return [fh.read()[:-1]]
42
42
43 def catfile(self, rev, type):
43 def catfile(self, rev, type):
44 if rev == "0" * 40: raise IOError()
44 if rev == "0" * 40: raise IOError()
45 fh = self.gitcmd("git-cat-file %s %s 2>%s" % (type, rev,
45 fh = self.gitcmd("git-cat-file %s %s 2>%s" % (type, rev,
46 util.nulldev))
46 util.nulldev))
47 return fh.read()
47 return fh.read()
48
48
49 def getfile(self, name, rev):
49 def getfile(self, name, rev):
50 return self.catfile(rev, "blob")
50 return self.catfile(rev, "blob")
51
51
52 def getmode(self, name, rev):
52 def getmode(self, name, rev):
53 return self.modecache[(name, rev)]
53 return self.modecache[(name, rev)]
54
54
55 def getchanges(self, version):
55 def getchanges(self, version):
56 self.modecache = {}
56 self.modecache = {}
57 fh = self.gitcmd("git-diff-tree --root -m -r %s" % version)
57 fh = self.gitcmd("git-diff-tree --root -m -r %s" % version)
58 changes = []
58 changes = []
59 seen = {}
59 for l in fh:
60 for l in fh:
60 if "\t" not in l: continue
61 if "\t" not in l:
62 continue
61 m, f = l[:-1].split("\t")
63 m, f = l[:-1].split("\t")
64 if f in seen:
65 continue
66 seen[f] = 1
62 m = m.split()
67 m = m.split()
63 h = m[3]
68 h = m[3]
64 p = (m[1] == "100755")
69 p = (m[1] == "100755")
65 s = (m[1] == "120000")
70 s = (m[1] == "120000")
66 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
71 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
67 changes.append((f, h))
72 changes.append((f, h))
68 return (changes, {})
73 return (changes, {})
69
74
70 def getcommit(self, version):
75 def getcommit(self, version):
71 c = self.catfile(version, "commit") # read the commit hash
76 c = self.catfile(version, "commit") # read the commit hash
72 end = c.find("\n\n")
77 end = c.find("\n\n")
73 message = c[end+2:]
78 message = c[end+2:]
74 message = self.recode(message)
79 message = self.recode(message)
75 l = c[:end].splitlines()
80 l = c[:end].splitlines()
76 manifest = l[0].split()[1]
81 manifest = l[0].split()[1]
77 parents = []
82 parents = []
78 for e in l[1:]:
83 for e in l[1:]:
79 n, v = e.split(" ", 1)
84 n, v = e.split(" ", 1)
80 if n == "author":
85 if n == "author":
81 p = v.split()
86 p = v.split()
82 tm, tz = p[-2:]
87 tm, tz = p[-2:]
83 author = " ".join(p[:-2])
88 author = " ".join(p[:-2])
84 if author[0] == "<": author = author[1:-1]
89 if author[0] == "<": author = author[1:-1]
85 author = self.recode(author)
90 author = self.recode(author)
86 if n == "committer":
91 if n == "committer":
87 p = v.split()
92 p = v.split()
88 tm, tz = p[-2:]
93 tm, tz = p[-2:]
89 committer = " ".join(p[:-2])
94 committer = " ".join(p[:-2])
90 if committer[0] == "<": committer = committer[1:-1]
95 if committer[0] == "<": committer = committer[1:-1]
91 committer = self.recode(committer)
96 committer = self.recode(committer)
92 message += "\ncommitter: %s\n" % committer
97 message += "\ncommitter: %s\n" % committer
93 if n == "parent": parents.append(v)
98 if n == "parent": parents.append(v)
94
99
95 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
100 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
96 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
101 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
97 date = tm + " " + str(tz)
102 date = tm + " " + str(tz)
98 author = author or "unknown"
103 author = author or "unknown"
99
104
100 c = commit(parents=parents, date=date, author=author, desc=message,
105 c = commit(parents=parents, date=date, author=author, desc=message,
101 rev=version)
106 rev=version)
102 return c
107 return c
103
108
104 def gettags(self):
109 def gettags(self):
105 tags = {}
110 tags = {}
106 fh = self.gitcmd('git-ls-remote --tags "%s" 2>%s' % (self.path,
111 fh = self.gitcmd('git-ls-remote --tags "%s" 2>%s' % (self.path,
107 util.nulldev))
112 util.nulldev))
108 prefix = 'refs/tags/'
113 prefix = 'refs/tags/'
109 for line in fh:
114 for line in fh:
110 line = line.strip()
115 line = line.strip()
111 if not line.endswith("^{}"):
116 if not line.endswith("^{}"):
112 continue
117 continue
113 node, tag = line.split(None, 1)
118 node, tag = line.split(None, 1)
114 if not tag.startswith(prefix):
119 if not tag.startswith(prefix):
115 continue
120 continue
116 tag = tag[len(prefix):-3]
121 tag = tag[len(prefix):-3]
117 tags[tag] = node
122 tags[tag] = node
118
123
119 return tags
124 return tags
@@ -1,2253 +1,2256 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
33 from mercurial import commands, cmdutil, hg, patch, revlog, util
34 from mercurial import repair
34 from mercurial import repair
35 import os, sys, re, errno
35 import os, sys, re, errno
36
36
37 commands.norepo += " qclone qversion"
37 commands.norepo += " qclone qversion"
38
38
39 # Patch names looks like unix-file names.
39 # Patch names looks like unix-file names.
40 # They must be joinable with queue directory and result in the patch path.
40 # They must be joinable with queue directory and result in the patch path.
41 normname = util.normpath
41 normname = util.normpath
42
42
43 class statusentry:
43 class statusentry:
44 def __init__(self, rev, name=None):
44 def __init__(self, rev, name=None):
45 if not name:
45 if not name:
46 fields = rev.split(':', 1)
46 fields = rev.split(':', 1)
47 if len(fields) == 2:
47 if len(fields) == 2:
48 self.rev, self.name = fields
48 self.rev, self.name = fields
49 else:
49 else:
50 self.rev, self.name = None, None
50 self.rev, self.name = None, None
51 else:
51 else:
52 self.rev, self.name = rev, name
52 self.rev, self.name = rev, name
53
53
54 def __str__(self):
54 def __str__(self):
55 return self.rev + ':' + self.name
55 return self.rev + ':' + self.name
56
56
57 class queue:
57 class queue:
58 def __init__(self, ui, path, patchdir=None):
58 def __init__(self, ui, path, patchdir=None):
59 self.basepath = path
59 self.basepath = path
60 self.path = patchdir or os.path.join(path, "patches")
60 self.path = patchdir or os.path.join(path, "patches")
61 self.opener = util.opener(self.path)
61 self.opener = util.opener(self.path)
62 self.ui = ui
62 self.ui = ui
63 self.applied = []
63 self.applied = []
64 self.full_series = []
64 self.full_series = []
65 self.applied_dirty = 0
65 self.applied_dirty = 0
66 self.series_dirty = 0
66 self.series_dirty = 0
67 self.series_path = "series"
67 self.series_path = "series"
68 self.status_path = "status"
68 self.status_path = "status"
69 self.guards_path = "guards"
69 self.guards_path = "guards"
70 self.active_guards = None
70 self.active_guards = None
71 self.guards_dirty = False
71 self.guards_dirty = False
72 self._diffopts = None
72 self._diffopts = None
73
73
74 if os.path.exists(self.join(self.series_path)):
74 if os.path.exists(self.join(self.series_path)):
75 self.full_series = self.opener(self.series_path).read().splitlines()
75 self.full_series = self.opener(self.series_path).read().splitlines()
76 self.parse_series()
76 self.parse_series()
77
77
78 if os.path.exists(self.join(self.status_path)):
78 if os.path.exists(self.join(self.status_path)):
79 lines = self.opener(self.status_path).read().splitlines()
79 lines = self.opener(self.status_path).read().splitlines()
80 self.applied = [statusentry(l) for l in lines]
80 self.applied = [statusentry(l) for l in lines]
81
81
82 def diffopts(self):
82 def diffopts(self):
83 if self._diffopts is None:
83 if self._diffopts is None:
84 self._diffopts = patch.diffopts(self.ui)
84 self._diffopts = patch.diffopts(self.ui)
85 return self._diffopts
85 return self._diffopts
86
86
87 def join(self, *p):
87 def join(self, *p):
88 return os.path.join(self.path, *p)
88 return os.path.join(self.path, *p)
89
89
90 def find_series(self, patch):
90 def find_series(self, patch):
91 pre = re.compile("(\s*)([^#]+)")
91 pre = re.compile("(\s*)([^#]+)")
92 index = 0
92 index = 0
93 for l in self.full_series:
93 for l in self.full_series:
94 m = pre.match(l)
94 m = pre.match(l)
95 if m:
95 if m:
96 s = m.group(2)
96 s = m.group(2)
97 s = s.rstrip()
97 s = s.rstrip()
98 if s == patch:
98 if s == patch:
99 return index
99 return index
100 index += 1
100 index += 1
101 return None
101 return None
102
102
103 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
103 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
104
104
105 def parse_series(self):
105 def parse_series(self):
106 self.series = []
106 self.series = []
107 self.series_guards = []
107 self.series_guards = []
108 for l in self.full_series:
108 for l in self.full_series:
109 h = l.find('#')
109 h = l.find('#')
110 if h == -1:
110 if h == -1:
111 patch = l
111 patch = l
112 comment = ''
112 comment = ''
113 elif h == 0:
113 elif h == 0:
114 continue
114 continue
115 else:
115 else:
116 patch = l[:h]
116 patch = l[:h]
117 comment = l[h:]
117 comment = l[h:]
118 patch = patch.strip()
118 patch = patch.strip()
119 if patch:
119 if patch:
120 if patch in self.series:
120 if patch in self.series:
121 raise util.Abort(_('%s appears more than once in %s') %
121 raise util.Abort(_('%s appears more than once in %s') %
122 (patch, self.join(self.series_path)))
122 (patch, self.join(self.series_path)))
123 self.series.append(patch)
123 self.series.append(patch)
124 self.series_guards.append(self.guard_re.findall(comment))
124 self.series_guards.append(self.guard_re.findall(comment))
125
125
126 def check_guard(self, guard):
126 def check_guard(self, guard):
127 bad_chars = '# \t\r\n\f'
127 bad_chars = '# \t\r\n\f'
128 first = guard[0]
128 first = guard[0]
129 for c in '-+':
129 for c in '-+':
130 if first == c:
130 if first == c:
131 return (_('guard %r starts with invalid character: %r') %
131 return (_('guard %r starts with invalid character: %r') %
132 (guard, c))
132 (guard, c))
133 for c in bad_chars:
133 for c in bad_chars:
134 if c in guard:
134 if c in guard:
135 return _('invalid character in guard %r: %r') % (guard, c)
135 return _('invalid character in guard %r: %r') % (guard, c)
136
136
137 def set_active(self, guards):
137 def set_active(self, guards):
138 for guard in guards:
138 for guard in guards:
139 bad = self.check_guard(guard)
139 bad = self.check_guard(guard)
140 if bad:
140 if bad:
141 raise util.Abort(bad)
141 raise util.Abort(bad)
142 guards = dict.fromkeys(guards).keys()
142 guards = dict.fromkeys(guards).keys()
143 guards.sort()
143 guards.sort()
144 self.ui.debug('active guards: %s\n' % ' '.join(guards))
144 self.ui.debug('active guards: %s\n' % ' '.join(guards))
145 self.active_guards = guards
145 self.active_guards = guards
146 self.guards_dirty = True
146 self.guards_dirty = True
147
147
148 def active(self):
148 def active(self):
149 if self.active_guards is None:
149 if self.active_guards is None:
150 self.active_guards = []
150 self.active_guards = []
151 try:
151 try:
152 guards = self.opener(self.guards_path).read().split()
152 guards = self.opener(self.guards_path).read().split()
153 except IOError, err:
153 except IOError, err:
154 if err.errno != errno.ENOENT: raise
154 if err.errno != errno.ENOENT: raise
155 guards = []
155 guards = []
156 for i, guard in enumerate(guards):
156 for i, guard in enumerate(guards):
157 bad = self.check_guard(guard)
157 bad = self.check_guard(guard)
158 if bad:
158 if bad:
159 self.ui.warn('%s:%d: %s\n' %
159 self.ui.warn('%s:%d: %s\n' %
160 (self.join(self.guards_path), i + 1, bad))
160 (self.join(self.guards_path), i + 1, bad))
161 else:
161 else:
162 self.active_guards.append(guard)
162 self.active_guards.append(guard)
163 return self.active_guards
163 return self.active_guards
164
164
165 def set_guards(self, idx, guards):
165 def set_guards(self, idx, guards):
166 for g in guards:
166 for g in guards:
167 if len(g) < 2:
167 if len(g) < 2:
168 raise util.Abort(_('guard %r too short') % g)
168 raise util.Abort(_('guard %r too short') % g)
169 if g[0] not in '-+':
169 if g[0] not in '-+':
170 raise util.Abort(_('guard %r starts with invalid char') % g)
170 raise util.Abort(_('guard %r starts with invalid char') % g)
171 bad = self.check_guard(g[1:])
171 bad = self.check_guard(g[1:])
172 if bad:
172 if bad:
173 raise util.Abort(bad)
173 raise util.Abort(bad)
174 drop = self.guard_re.sub('', self.full_series[idx])
174 drop = self.guard_re.sub('', self.full_series[idx])
175 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
175 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
176 self.parse_series()
176 self.parse_series()
177 self.series_dirty = True
177 self.series_dirty = True
178
178
179 def pushable(self, idx):
179 def pushable(self, idx):
180 if isinstance(idx, str):
180 if isinstance(idx, str):
181 idx = self.series.index(idx)
181 idx = self.series.index(idx)
182 patchguards = self.series_guards[idx]
182 patchguards = self.series_guards[idx]
183 if not patchguards:
183 if not patchguards:
184 return True, None
184 return True, None
185 default = False
185 default = False
186 guards = self.active()
186 guards = self.active()
187 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
187 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
188 if exactneg:
188 if exactneg:
189 return False, exactneg[0]
189 return False, exactneg[0]
190 pos = [g for g in patchguards if g[0] == '+']
190 pos = [g for g in patchguards if g[0] == '+']
191 exactpos = [g for g in pos if g[1:] in guards]
191 exactpos = [g for g in pos if g[1:] in guards]
192 if pos:
192 if pos:
193 if exactpos:
193 if exactpos:
194 return True, exactpos[0]
194 return True, exactpos[0]
195 return False, pos
195 return False, pos
196 return True, ''
196 return True, ''
197
197
198 def explain_pushable(self, idx, all_patches=False):
198 def explain_pushable(self, idx, all_patches=False):
199 write = all_patches and self.ui.write or self.ui.warn
199 write = all_patches and self.ui.write or self.ui.warn
200 if all_patches or self.ui.verbose:
200 if all_patches or self.ui.verbose:
201 if isinstance(idx, str):
201 if isinstance(idx, str):
202 idx = self.series.index(idx)
202 idx = self.series.index(idx)
203 pushable, why = self.pushable(idx)
203 pushable, why = self.pushable(idx)
204 if all_patches and pushable:
204 if all_patches and pushable:
205 if why is None:
205 if why is None:
206 write(_('allowing %s - no guards in effect\n') %
206 write(_('allowing %s - no guards in effect\n') %
207 self.series[idx])
207 self.series[idx])
208 else:
208 else:
209 if not why:
209 if not why:
210 write(_('allowing %s - no matching negative guards\n') %
210 write(_('allowing %s - no matching negative guards\n') %
211 self.series[idx])
211 self.series[idx])
212 else:
212 else:
213 write(_('allowing %s - guarded by %r\n') %
213 write(_('allowing %s - guarded by %r\n') %
214 (self.series[idx], why))
214 (self.series[idx], why))
215 if not pushable:
215 if not pushable:
216 if why:
216 if why:
217 write(_('skipping %s - guarded by %r\n') %
217 write(_('skipping %s - guarded by %r\n') %
218 (self.series[idx], why))
218 (self.series[idx], why))
219 else:
219 else:
220 write(_('skipping %s - no matching guards\n') %
220 write(_('skipping %s - no matching guards\n') %
221 self.series[idx])
221 self.series[idx])
222
222
223 def save_dirty(self):
223 def save_dirty(self):
224 def write_list(items, path):
224 def write_list(items, path):
225 fp = self.opener(path, 'w')
225 fp = self.opener(path, 'w')
226 for i in items:
226 for i in items:
227 print >> fp, i
227 print >> fp, i
228 fp.close()
228 fp.close()
229 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
229 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
230 if self.series_dirty: write_list(self.full_series, self.series_path)
230 if self.series_dirty: write_list(self.full_series, self.series_path)
231 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
231 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
232
232
233 def readheaders(self, patch):
233 def readheaders(self, patch):
234 def eatdiff(lines):
234 def eatdiff(lines):
235 while lines:
235 while lines:
236 l = lines[-1]
236 l = lines[-1]
237 if (l.startswith("diff -") or
237 if (l.startswith("diff -") or
238 l.startswith("Index:") or
238 l.startswith("Index:") or
239 l.startswith("===========")):
239 l.startswith("===========")):
240 del lines[-1]
240 del lines[-1]
241 else:
241 else:
242 break
242 break
243 def eatempty(lines):
243 def eatempty(lines):
244 while lines:
244 while lines:
245 l = lines[-1]
245 l = lines[-1]
246 if re.match('\s*$', l):
246 if re.match('\s*$', l):
247 del lines[-1]
247 del lines[-1]
248 else:
248 else:
249 break
249 break
250
250
251 pf = self.join(patch)
251 pf = self.join(patch)
252 message = []
252 message = []
253 comments = []
253 comments = []
254 user = None
254 user = None
255 date = None
255 date = None
256 format = None
256 format = None
257 subject = None
257 subject = None
258 diffstart = 0
258 diffstart = 0
259
259
260 for line in file(pf):
260 for line in file(pf):
261 line = line.rstrip()
261 line = line.rstrip()
262 if line.startswith('diff --git'):
262 if line.startswith('diff --git'):
263 diffstart = 2
263 diffstart = 2
264 break
264 break
265 if diffstart:
265 if diffstart:
266 if line.startswith('+++ '):
266 if line.startswith('+++ '):
267 diffstart = 2
267 diffstart = 2
268 break
268 break
269 if line.startswith("--- "):
269 if line.startswith("--- "):
270 diffstart = 1
270 diffstart = 1
271 continue
271 continue
272 elif format == "hgpatch":
272 elif format == "hgpatch":
273 # parse values when importing the result of an hg export
273 # parse values when importing the result of an hg export
274 if line.startswith("# User "):
274 if line.startswith("# User "):
275 user = line[7:]
275 user = line[7:]
276 elif line.startswith("# Date "):
276 elif line.startswith("# Date "):
277 date = line[7:]
277 date = line[7:]
278 elif not line.startswith("# ") and line:
278 elif not line.startswith("# ") and line:
279 message.append(line)
279 message.append(line)
280 format = None
280 format = None
281 elif line == '# HG changeset patch':
281 elif line == '# HG changeset patch':
282 format = "hgpatch"
282 format = "hgpatch"
283 elif (format != "tagdone" and (line.startswith("Subject: ") or
283 elif (format != "tagdone" and (line.startswith("Subject: ") or
284 line.startswith("subject: "))):
284 line.startswith("subject: "))):
285 subject = line[9:]
285 subject = line[9:]
286 format = "tag"
286 format = "tag"
287 elif (format != "tagdone" and (line.startswith("From: ") or
287 elif (format != "tagdone" and (line.startswith("From: ") or
288 line.startswith("from: "))):
288 line.startswith("from: "))):
289 user = line[6:]
289 user = line[6:]
290 format = "tag"
290 format = "tag"
291 elif format == "tag" and line == "":
291 elif format == "tag" and line == "":
292 # when looking for tags (subject: from: etc) they
292 # when looking for tags (subject: from: etc) they
293 # end once you find a blank line in the source
293 # end once you find a blank line in the source
294 format = "tagdone"
294 format = "tagdone"
295 elif message or line:
295 elif message or line:
296 message.append(line)
296 message.append(line)
297 comments.append(line)
297 comments.append(line)
298
298
299 eatdiff(message)
299 eatdiff(message)
300 eatdiff(comments)
300 eatdiff(comments)
301 eatempty(message)
301 eatempty(message)
302 eatempty(comments)
302 eatempty(comments)
303
303
304 # make sure message isn't empty
304 # make sure message isn't empty
305 if format and format.startswith("tag") and subject:
305 if format and format.startswith("tag") and subject:
306 message.insert(0, "")
306 message.insert(0, "")
307 message.insert(0, subject)
307 message.insert(0, subject)
308 return (message, comments, user, date, diffstart > 1)
308 return (message, comments, user, date, diffstart > 1)
309
309
310 def removeundo(self, repo):
310 def removeundo(self, repo):
311 undo = repo.sjoin('undo')
311 undo = repo.sjoin('undo')
312 if not os.path.exists(undo):
312 if not os.path.exists(undo):
313 return
313 return
314 try:
314 try:
315 os.unlink(undo)
315 os.unlink(undo)
316 except OSError, inst:
316 except OSError, inst:
317 self.ui.warn('error removing undo: %s\n' % str(inst))
317 self.ui.warn('error removing undo: %s\n' % str(inst))
318
318
319 def printdiff(self, repo, node1, node2=None, files=None,
319 def printdiff(self, repo, node1, node2=None, files=None,
320 fp=None, changes=None, opts={}):
320 fp=None, changes=None, opts={}):
321 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
321 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
322
322
323 patch.diff(repo, node1, node2, fns, match=matchfn,
323 patch.diff(repo, node1, node2, fns, match=matchfn,
324 fp=fp, changes=changes, opts=self.diffopts())
324 fp=fp, changes=changes, opts=self.diffopts())
325
325
326 def mergeone(self, repo, mergeq, head, patch, rev):
326 def mergeone(self, repo, mergeq, head, patch, rev):
327 # first try just applying the patch
327 # first try just applying the patch
328 (err, n) = self.apply(repo, [ patch ], update_status=False,
328 (err, n) = self.apply(repo, [ patch ], update_status=False,
329 strict=True, merge=rev)
329 strict=True, merge=rev)
330
330
331 if err == 0:
331 if err == 0:
332 return (err, n)
332 return (err, n)
333
333
334 if n is None:
334 if n is None:
335 raise util.Abort(_("apply failed for patch %s") % patch)
335 raise util.Abort(_("apply failed for patch %s") % patch)
336
336
337 self.ui.warn("patch didn't work out, merging %s\n" % patch)
337 self.ui.warn("patch didn't work out, merging %s\n" % patch)
338
338
339 # apply failed, strip away that rev and merge.
339 # apply failed, strip away that rev and merge.
340 hg.clean(repo, head)
340 hg.clean(repo, head)
341 self.strip(repo, n, update=False, backup='strip')
341 self.strip(repo, n, update=False, backup='strip')
342
342
343 ctx = repo.changectx(rev)
343 ctx = repo.changectx(rev)
344 ret = hg.merge(repo, rev)
344 ret = hg.merge(repo, rev)
345 if ret:
345 if ret:
346 raise util.Abort(_("update returned %d") % ret)
346 raise util.Abort(_("update returned %d") % ret)
347 n = repo.commit(None, ctx.description(), ctx.user(), force=1)
347 n = repo.commit(None, ctx.description(), ctx.user(), force=1)
348 if n == None:
348 if n == None:
349 raise util.Abort(_("repo commit failed"))
349 raise util.Abort(_("repo commit failed"))
350 try:
350 try:
351 message, comments, user, date, patchfound = mergeq.readheaders(patch)
351 message, comments, user, date, patchfound = mergeq.readheaders(patch)
352 except:
352 except:
353 raise util.Abort(_("unable to read %s") % patch)
353 raise util.Abort(_("unable to read %s") % patch)
354
354
355 patchf = self.opener(patch, "w")
355 patchf = self.opener(patch, "w")
356 if comments:
356 if comments:
357 comments = "\n".join(comments) + '\n\n'
357 comments = "\n".join(comments) + '\n\n'
358 patchf.write(comments)
358 patchf.write(comments)
359 self.printdiff(repo, head, n, fp=patchf)
359 self.printdiff(repo, head, n, fp=patchf)
360 patchf.close()
360 patchf.close()
361 self.removeundo(repo)
361 self.removeundo(repo)
362 return (0, n)
362 return (0, n)
363
363
364 def qparents(self, repo, rev=None):
364 def qparents(self, repo, rev=None):
365 if rev is None:
365 if rev is None:
366 (p1, p2) = repo.dirstate.parents()
366 (p1, p2) = repo.dirstate.parents()
367 if p2 == revlog.nullid:
367 if p2 == revlog.nullid:
368 return p1
368 return p1
369 if len(self.applied) == 0:
369 if len(self.applied) == 0:
370 return None
370 return None
371 return revlog.bin(self.applied[-1].rev)
371 return revlog.bin(self.applied[-1].rev)
372 pp = repo.changelog.parents(rev)
372 pp = repo.changelog.parents(rev)
373 if pp[1] != revlog.nullid:
373 if pp[1] != revlog.nullid:
374 arevs = [ x.rev for x in self.applied ]
374 arevs = [ x.rev for x in self.applied ]
375 p0 = revlog.hex(pp[0])
375 p0 = revlog.hex(pp[0])
376 p1 = revlog.hex(pp[1])
376 p1 = revlog.hex(pp[1])
377 if p0 in arevs:
377 if p0 in arevs:
378 return pp[0]
378 return pp[0]
379 if p1 in arevs:
379 if p1 in arevs:
380 return pp[1]
380 return pp[1]
381 return pp[0]
381 return pp[0]
382
382
383 def mergepatch(self, repo, mergeq, series):
383 def mergepatch(self, repo, mergeq, series):
384 if len(self.applied) == 0:
384 if len(self.applied) == 0:
385 # each of the patches merged in will have two parents. This
385 # each of the patches merged in will have two parents. This
386 # can confuse the qrefresh, qdiff, and strip code because it
386 # can confuse the qrefresh, qdiff, and strip code because it
387 # needs to know which parent is actually in the patch queue.
387 # needs to know which parent is actually in the patch queue.
388 # so, we insert a merge marker with only one parent. This way
388 # so, we insert a merge marker with only one parent. This way
389 # the first patch in the queue is never a merge patch
389 # the first patch in the queue is never a merge patch
390 #
390 #
391 pname = ".hg.patches.merge.marker"
391 pname = ".hg.patches.merge.marker"
392 n = repo.commit(None, '[mq]: merge marker', user=None, force=1)
392 n = repo.commit(None, '[mq]: merge marker', user=None, force=1)
393 self.removeundo(repo)
393 self.removeundo(repo)
394 self.applied.append(statusentry(revlog.hex(n), pname))
394 self.applied.append(statusentry(revlog.hex(n), pname))
395 self.applied_dirty = 1
395 self.applied_dirty = 1
396
396
397 head = self.qparents(repo)
397 head = self.qparents(repo)
398
398
399 for patch in series:
399 for patch in series:
400 patch = mergeq.lookup(patch, strict=True)
400 patch = mergeq.lookup(patch, strict=True)
401 if not patch:
401 if not patch:
402 self.ui.warn("patch %s does not exist\n" % patch)
402 self.ui.warn("patch %s does not exist\n" % patch)
403 return (1, None)
403 return (1, None)
404 pushable, reason = self.pushable(patch)
404 pushable, reason = self.pushable(patch)
405 if not pushable:
405 if not pushable:
406 self.explain_pushable(patch, all_patches=True)
406 self.explain_pushable(patch, all_patches=True)
407 continue
407 continue
408 info = mergeq.isapplied(patch)
408 info = mergeq.isapplied(patch)
409 if not info:
409 if not info:
410 self.ui.warn("patch %s is not applied\n" % patch)
410 self.ui.warn("patch %s is not applied\n" % patch)
411 return (1, None)
411 return (1, None)
412 rev = revlog.bin(info[1])
412 rev = revlog.bin(info[1])
413 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
413 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
414 if head:
414 if head:
415 self.applied.append(statusentry(revlog.hex(head), patch))
415 self.applied.append(statusentry(revlog.hex(head), patch))
416 self.applied_dirty = 1
416 self.applied_dirty = 1
417 if err:
417 if err:
418 return (err, head)
418 return (err, head)
419 self.save_dirty()
419 self.save_dirty()
420 return (0, head)
420 return (0, head)
421
421
422 def patch(self, repo, patchfile):
422 def patch(self, repo, patchfile):
423 '''Apply patchfile to the working directory.
423 '''Apply patchfile to the working directory.
424 patchfile: file name of patch'''
424 patchfile: file name of patch'''
425 files = {}
425 files = {}
426 try:
426 try:
427 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
427 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
428 files=files)
428 files=files)
429 except Exception, inst:
429 except Exception, inst:
430 self.ui.note(str(inst) + '\n')
430 self.ui.note(str(inst) + '\n')
431 if not self.ui.verbose:
431 if not self.ui.verbose:
432 self.ui.warn("patch failed, unable to continue (try -v)\n")
432 self.ui.warn("patch failed, unable to continue (try -v)\n")
433 return (False, files, False)
433 return (False, files, False)
434
434
435 return (True, files, fuzz)
435 return (True, files, fuzz)
436
436
437 def apply(self, repo, series, list=False, update_status=True,
437 def apply(self, repo, series, list=False, update_status=True,
438 strict=False, patchdir=None, merge=None, all_files={}):
438 strict=False, patchdir=None, merge=None, all_files={}):
439 wlock = lock = tr = None
439 wlock = lock = tr = None
440 try:
440 try:
441 wlock = repo.wlock()
441 wlock = repo.wlock()
442 lock = repo.lock()
442 lock = repo.lock()
443 tr = repo.transaction()
443 tr = repo.transaction()
444 try:
444 try:
445 ret = self._apply(repo, series, list, update_status,
445 ret = self._apply(repo, series, list, update_status,
446 strict, patchdir, merge, all_files=all_files)
446 strict, patchdir, merge, all_files=all_files)
447 tr.close()
447 tr.close()
448 self.save_dirty()
448 self.save_dirty()
449 return ret
449 return ret
450 except:
450 except:
451 try:
451 try:
452 tr.abort()
452 tr.abort()
453 finally:
453 finally:
454 repo.invalidate()
454 repo.invalidate()
455 repo.dirstate.invalidate()
455 repo.dirstate.invalidate()
456 raise
456 raise
457 finally:
457 finally:
458 del tr, lock, wlock
458 del tr, lock, wlock
459
459
460 def _apply(self, repo, series, list=False, update_status=True,
460 def _apply(self, repo, series, list=False, update_status=True,
461 strict=False, patchdir=None, merge=None, all_files={}):
461 strict=False, patchdir=None, merge=None, all_files={}):
462 # TODO unify with commands.py
462 # TODO unify with commands.py
463 if not patchdir:
463 if not patchdir:
464 patchdir = self.path
464 patchdir = self.path
465 err = 0
465 err = 0
466 n = None
466 n = None
467 for patchname in series:
467 for patchname in series:
468 pushable, reason = self.pushable(patchname)
468 pushable, reason = self.pushable(patchname)
469 if not pushable:
469 if not pushable:
470 self.explain_pushable(patchname, all_patches=True)
470 self.explain_pushable(patchname, all_patches=True)
471 continue
471 continue
472 self.ui.warn("applying %s\n" % patchname)
472 self.ui.warn("applying %s\n" % patchname)
473 pf = os.path.join(patchdir, patchname)
473 pf = os.path.join(patchdir, patchname)
474
474
475 try:
475 try:
476 message, comments, user, date, patchfound = self.readheaders(patchname)
476 message, comments, user, date, patchfound = self.readheaders(patchname)
477 except:
477 except:
478 self.ui.warn("Unable to read %s\n" % patchname)
478 self.ui.warn("Unable to read %s\n" % patchname)
479 err = 1
479 err = 1
480 break
480 break
481
481
482 if not message:
482 if not message:
483 message = "imported patch %s\n" % patchname
483 message = "imported patch %s\n" % patchname
484 else:
484 else:
485 if list:
485 if list:
486 message.append("\nimported patch %s" % patchname)
486 message.append("\nimported patch %s" % patchname)
487 message = '\n'.join(message)
487 message = '\n'.join(message)
488
488
489 (patcherr, files, fuzz) = self.patch(repo, pf)
489 (patcherr, files, fuzz) = self.patch(repo, pf)
490 all_files.update(files)
490 all_files.update(files)
491 patcherr = not patcherr
491 patcherr = not patcherr
492
492
493 if merge and files:
493 if merge and files:
494 # Mark as removed/merged and update dirstate parent info
494 # Mark as removed/merged and update dirstate parent info
495 removed = []
495 removed = []
496 merged = []
496 merged = []
497 for f in files:
497 for f in files:
498 if os.path.exists(repo.wjoin(f)):
498 if os.path.exists(repo.wjoin(f)):
499 merged.append(f)
499 merged.append(f)
500 else:
500 else:
501 removed.append(f)
501 removed.append(f)
502 for f in removed:
502 for f in removed:
503 repo.dirstate.remove(f)
503 repo.dirstate.remove(f)
504 for f in merged:
504 for f in merged:
505 repo.dirstate.merge(f)
505 repo.dirstate.merge(f)
506 p1, p2 = repo.dirstate.parents()
506 p1, p2 = repo.dirstate.parents()
507 repo.dirstate.setparents(p1, merge)
507 repo.dirstate.setparents(p1, merge)
508 files = patch.updatedir(self.ui, repo, files)
508 files = patch.updatedir(self.ui, repo, files)
509 n = repo.commit(files, message, user, date, force=1)
509 n = repo.commit(files, message, user, date, force=1)
510
510
511 if n == None:
511 if n == None:
512 raise util.Abort(_("repo commit failed"))
512 raise util.Abort(_("repo commit failed"))
513
513
514 if update_status:
514 if update_status:
515 self.applied.append(statusentry(revlog.hex(n), patchname))
515 self.applied.append(statusentry(revlog.hex(n), patchname))
516
516
517 if patcherr:
517 if patcherr:
518 if not patchfound:
518 if not patchfound:
519 self.ui.warn("patch %s is empty\n" % patchname)
519 self.ui.warn("patch %s is empty\n" % patchname)
520 err = 0
520 err = 0
521 else:
521 else:
522 self.ui.warn("patch failed, rejects left in working dir\n")
522 self.ui.warn("patch failed, rejects left in working dir\n")
523 err = 1
523 err = 1
524 break
524 break
525
525
526 if fuzz and strict:
526 if fuzz and strict:
527 self.ui.warn("fuzz found when applying patch, stopping\n")
527 self.ui.warn("fuzz found when applying patch, stopping\n")
528 err = 1
528 err = 1
529 break
529 break
530 self.removeundo(repo)
530 self.removeundo(repo)
531 return (err, n)
531 return (err, n)
532
532
533 def delete(self, repo, patches, opts):
533 def delete(self, repo, patches, opts):
534 if not patches and not opts.get('rev'):
534 if not patches and not opts.get('rev'):
535 raise util.Abort(_('qdelete requires at least one revision or '
535 raise util.Abort(_('qdelete requires at least one revision or '
536 'patch name'))
536 'patch name'))
537
537
538 realpatches = []
538 realpatches = []
539 for patch in patches:
539 for patch in patches:
540 patch = self.lookup(patch, strict=True)
540 patch = self.lookup(patch, strict=True)
541 info = self.isapplied(patch)
541 info = self.isapplied(patch)
542 if info:
542 if info:
543 raise util.Abort(_("cannot delete applied patch %s") % patch)
543 raise util.Abort(_("cannot delete applied patch %s") % patch)
544 if patch not in self.series:
544 if patch not in self.series:
545 raise util.Abort(_("patch %s not in series file") % patch)
545 raise util.Abort(_("patch %s not in series file") % patch)
546 realpatches.append(patch)
546 realpatches.append(patch)
547
547
548 appliedbase = 0
548 appliedbase = 0
549 if opts.get('rev'):
549 if opts.get('rev'):
550 if not self.applied:
550 if not self.applied:
551 raise util.Abort(_('no patches applied'))
551 raise util.Abort(_('no patches applied'))
552 revs = cmdutil.revrange(repo, opts['rev'])
552 revs = cmdutil.revrange(repo, opts['rev'])
553 if len(revs) > 1 and revs[0] > revs[1]:
553 if len(revs) > 1 and revs[0] > revs[1]:
554 revs.reverse()
554 revs.reverse()
555 for rev in revs:
555 for rev in revs:
556 if appliedbase >= len(self.applied):
556 if appliedbase >= len(self.applied):
557 raise util.Abort(_("revision %d is not managed") % rev)
557 raise util.Abort(_("revision %d is not managed") % rev)
558
558
559 base = revlog.bin(self.applied[appliedbase].rev)
559 base = revlog.bin(self.applied[appliedbase].rev)
560 node = repo.changelog.node(rev)
560 node = repo.changelog.node(rev)
561 if node != base:
561 if node != base:
562 raise util.Abort(_("cannot delete revision %d above "
562 raise util.Abort(_("cannot delete revision %d above "
563 "applied patches") % rev)
563 "applied patches") % rev)
564 realpatches.append(self.applied[appliedbase].name)
564 realpatches.append(self.applied[appliedbase].name)
565 appliedbase += 1
565 appliedbase += 1
566
566
567 if not opts.get('keep'):
567 if not opts.get('keep'):
568 r = self.qrepo()
568 r = self.qrepo()
569 if r:
569 if r:
570 r.remove(realpatches, True)
570 r.remove(realpatches, True)
571 else:
571 else:
572 for p in realpatches:
572 for p in realpatches:
573 os.unlink(self.join(p))
573 os.unlink(self.join(p))
574
574
575 if appliedbase:
575 if appliedbase:
576 del self.applied[:appliedbase]
576 del self.applied[:appliedbase]
577 self.applied_dirty = 1
577 self.applied_dirty = 1
578 indices = [self.find_series(p) for p in realpatches]
578 indices = [self.find_series(p) for p in realpatches]
579 indices.sort()
579 indices.sort()
580 for i in indices[-1::-1]:
580 for i in indices[-1::-1]:
581 del self.full_series[i]
581 del self.full_series[i]
582 self.parse_series()
582 self.parse_series()
583 self.series_dirty = 1
583 self.series_dirty = 1
584
584
585 def check_toppatch(self, repo):
585 def check_toppatch(self, repo):
586 if len(self.applied) > 0:
586 if len(self.applied) > 0:
587 top = revlog.bin(self.applied[-1].rev)
587 top = revlog.bin(self.applied[-1].rev)
588 pp = repo.dirstate.parents()
588 pp = repo.dirstate.parents()
589 if top not in pp:
589 if top not in pp:
590 raise util.Abort(_("queue top not at same revision as working directory"))
590 raise util.Abort(_("queue top not at same revision as working directory"))
591 return top
591 return top
592 return None
592 return None
593 def check_localchanges(self, repo, force=False, refresh=True):
593 def check_localchanges(self, repo, force=False, refresh=True):
594 m, a, r, d = repo.status()[:4]
594 m, a, r, d = repo.status()[:4]
595 if m or a or r or d:
595 if m or a or r or d:
596 if not force:
596 if not force:
597 if refresh:
597 if refresh:
598 raise util.Abort(_("local changes found, refresh first"))
598 raise util.Abort(_("local changes found, refresh first"))
599 else:
599 else:
600 raise util.Abort(_("local changes found"))
600 raise util.Abort(_("local changes found"))
601 return m, a, r, d
601 return m, a, r, d
602
602
603 def new(self, repo, patch, *pats, **opts):
603 def new(self, repo, patch, *pats, **opts):
604 msg = opts.get('msg')
604 msg = opts.get('msg')
605 force = opts.get('force')
605 force = opts.get('force')
606 if os.path.exists(self.join(patch)):
606 if os.path.exists(self.join(patch)):
607 raise util.Abort(_('patch "%s" already exists') % patch)
607 raise util.Abort(_('patch "%s" already exists') % patch)
608 if opts.get('include') or opts.get('exclude') or pats:
608 if opts.get('include') or opts.get('exclude') or pats:
609 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
609 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
610 m, a, r, d = repo.status(files=fns, match=match)[:4]
610 m, a, r, d = repo.status(files=fns, match=match)[:4]
611 else:
611 else:
612 m, a, r, d = self.check_localchanges(repo, force)
612 m, a, r, d = self.check_localchanges(repo, force)
613 commitfiles = m + a + r
613 commitfiles = m + a + r
614 self.check_toppatch(repo)
614 self.check_toppatch(repo)
615 wlock = repo.wlock()
615 wlock = repo.wlock()
616 try:
616 try:
617 insert = self.full_series_end()
617 insert = self.full_series_end()
618 if msg:
618 if msg:
619 n = repo.commit(commitfiles, msg, force=True)
619 n = repo.commit(commitfiles, msg, force=True)
620 else:
620 else:
621 n = repo.commit(commitfiles, "[mq]: %s" % patch, force=True)
621 n = repo.commit(commitfiles, "[mq]: %s" % patch, force=True)
622 if n == None:
622 if n == None:
623 raise util.Abort(_("repo commit failed"))
623 raise util.Abort(_("repo commit failed"))
624 self.full_series[insert:insert] = [patch]
624 self.full_series[insert:insert] = [patch]
625 self.applied.append(statusentry(revlog.hex(n), patch))
625 self.applied.append(statusentry(revlog.hex(n), patch))
626 self.parse_series()
626 self.parse_series()
627 self.series_dirty = 1
627 self.series_dirty = 1
628 self.applied_dirty = 1
628 self.applied_dirty = 1
629 p = self.opener(patch, "w")
629 p = self.opener(patch, "w")
630 if msg:
630 if msg:
631 msg = msg + "\n"
631 msg = msg + "\n"
632 p.write(msg)
632 p.write(msg)
633 p.close()
633 p.close()
634 wlock = None
634 wlock = None
635 r = self.qrepo()
635 r = self.qrepo()
636 if r: r.add([patch])
636 if r: r.add([patch])
637 if commitfiles:
637 if commitfiles:
638 self.refresh(repo, short=True, git=opts.get('git'))
638 self.refresh(repo, short=True, git=opts.get('git'))
639 self.removeundo(repo)
639 self.removeundo(repo)
640 finally:
640 finally:
641 del wlock
641 del wlock
642
642
643 def strip(self, repo, rev, update=True, backup="all"):
643 def strip(self, repo, rev, update=True, backup="all"):
644 wlock = lock = None
644 wlock = lock = None
645 try:
645 try:
646 wlock = repo.wlock()
646 wlock = repo.wlock()
647 lock = repo.lock()
647 lock = repo.lock()
648
648
649 if update:
649 if update:
650 self.check_localchanges(repo, refresh=False)
650 self.check_localchanges(repo, refresh=False)
651 urev = self.qparents(repo, rev)
651 urev = self.qparents(repo, rev)
652 hg.clean(repo, urev)
652 hg.clean(repo, urev)
653 repo.dirstate.write()
653 repo.dirstate.write()
654
654
655 self.removeundo(repo)
655 self.removeundo(repo)
656 repair.strip(self.ui, repo, rev, backup)
656 repair.strip(self.ui, repo, rev, backup)
657 finally:
657 finally:
658 del lock, wlock
658 del lock, wlock
659
659
660 def isapplied(self, patch):
660 def isapplied(self, patch):
661 """returns (index, rev, patch)"""
661 """returns (index, rev, patch)"""
662 for i in xrange(len(self.applied)):
662 for i in xrange(len(self.applied)):
663 a = self.applied[i]
663 a = self.applied[i]
664 if a.name == patch:
664 if a.name == patch:
665 return (i, a.rev, a.name)
665 return (i, a.rev, a.name)
666 return None
666 return None
667
667
668 # if the exact patch name does not exist, we try a few
668 # if the exact patch name does not exist, we try a few
669 # variations. If strict is passed, we try only #1
669 # variations. If strict is passed, we try only #1
670 #
670 #
671 # 1) a number to indicate an offset in the series file
671 # 1) a number to indicate an offset in the series file
672 # 2) a unique substring of the patch name was given
672 # 2) a unique substring of the patch name was given
673 # 3) patchname[-+]num to indicate an offset in the series file
673 # 3) patchname[-+]num to indicate an offset in the series file
674 def lookup(self, patch, strict=False):
674 def lookup(self, patch, strict=False):
675 patch = patch and str(patch)
675 patch = patch and str(patch)
676
676
677 def partial_name(s):
677 def partial_name(s):
678 if s in self.series:
678 if s in self.series:
679 return s
679 return s
680 matches = [x for x in self.series if s in x]
680 matches = [x for x in self.series if s in x]
681 if len(matches) > 1:
681 if len(matches) > 1:
682 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
682 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
683 for m in matches:
683 for m in matches:
684 self.ui.warn(' %s\n' % m)
684 self.ui.warn(' %s\n' % m)
685 return None
685 return None
686 if matches:
686 if matches:
687 return matches[0]
687 return matches[0]
688 if len(self.series) > 0 and len(self.applied) > 0:
688 if len(self.series) > 0 and len(self.applied) > 0:
689 if s == 'qtip':
689 if s == 'qtip':
690 return self.series[self.series_end(True)-1]
690 return self.series[self.series_end(True)-1]
691 if s == 'qbase':
691 if s == 'qbase':
692 return self.series[0]
692 return self.series[0]
693 return None
693 return None
694 if patch == None:
694 if patch == None:
695 return None
695 return None
696
696
697 # we don't want to return a partial match until we make
697 # we don't want to return a partial match until we make
698 # sure the file name passed in does not exist (checked below)
698 # sure the file name passed in does not exist (checked below)
699 res = partial_name(patch)
699 res = partial_name(patch)
700 if res and res == patch:
700 if res and res == patch:
701 return res
701 return res
702
702
703 if not os.path.isfile(self.join(patch)):
703 if not os.path.isfile(self.join(patch)):
704 try:
704 try:
705 sno = int(patch)
705 sno = int(patch)
706 except(ValueError, OverflowError):
706 except(ValueError, OverflowError):
707 pass
707 pass
708 else:
708 else:
709 if sno < len(self.series):
709 if sno < len(self.series):
710 return self.series[sno]
710 return self.series[sno]
711 if not strict:
711 if not strict:
712 # return any partial match made above
712 # return any partial match made above
713 if res:
713 if res:
714 return res
714 return res
715 minus = patch.rfind('-')
715 minus = patch.rfind('-')
716 if minus >= 0:
716 if minus >= 0:
717 res = partial_name(patch[:minus])
717 res = partial_name(patch[:minus])
718 if res:
718 if res:
719 i = self.series.index(res)
719 i = self.series.index(res)
720 try:
720 try:
721 off = int(patch[minus+1:] or 1)
721 off = int(patch[minus+1:] or 1)
722 except(ValueError, OverflowError):
722 except(ValueError, OverflowError):
723 pass
723 pass
724 else:
724 else:
725 if i - off >= 0:
725 if i - off >= 0:
726 return self.series[i - off]
726 return self.series[i - off]
727 plus = patch.rfind('+')
727 plus = patch.rfind('+')
728 if plus >= 0:
728 if plus >= 0:
729 res = partial_name(patch[:plus])
729 res = partial_name(patch[:plus])
730 if res:
730 if res:
731 i = self.series.index(res)
731 i = self.series.index(res)
732 try:
732 try:
733 off = int(patch[plus+1:] or 1)
733 off = int(patch[plus+1:] or 1)
734 except(ValueError, OverflowError):
734 except(ValueError, OverflowError):
735 pass
735 pass
736 else:
736 else:
737 if i + off < len(self.series):
737 if i + off < len(self.series):
738 return self.series[i + off]
738 return self.series[i + off]
739 raise util.Abort(_("patch %s not in series") % patch)
739 raise util.Abort(_("patch %s not in series") % patch)
740
740
741 def push(self, repo, patch=None, force=False, list=False,
741 def push(self, repo, patch=None, force=False, list=False,
742 mergeq=None):
742 mergeq=None):
743 wlock = repo.wlock()
743 wlock = repo.wlock()
744 try:
744 try:
745 patch = self.lookup(patch)
745 patch = self.lookup(patch)
746 # Suppose our series file is: A B C and the current 'top'
746 # Suppose our series file is: A B C and the current 'top'
747 # patch is B. qpush C should be performed (moving forward)
747 # patch is B. qpush C should be performed (moving forward)
748 # qpush B is a NOP (no change) qpush A is an error (can't
748 # qpush B is a NOP (no change) qpush A is an error (can't
749 # go backwards with qpush)
749 # go backwards with qpush)
750 if patch:
750 if patch:
751 info = self.isapplied(patch)
751 info = self.isapplied(patch)
752 if info:
752 if info:
753 if info[0] < len(self.applied) - 1:
753 if info[0] < len(self.applied) - 1:
754 raise util.Abort(
754 raise util.Abort(
755 _("cannot push to a previous patch: %s") % patch)
755 _("cannot push to a previous patch: %s") % patch)
756 if info[0] < len(self.series) - 1:
756 if info[0] < len(self.series) - 1:
757 self.ui.warn(
757 self.ui.warn(
758 _('qpush: %s is already at the top\n') % patch)
758 _('qpush: %s is already at the top\n') % patch)
759 else:
759 else:
760 self.ui.warn(_('all patches are currently applied\n'))
760 self.ui.warn(_('all patches are currently applied\n'))
761 return
761 return
762
762
763 # Following the above example, starting at 'top' of B:
763 # Following the above example, starting at 'top' of B:
764 # qpush should be performed (pushes C), but a subsequent
764 # qpush should be performed (pushes C), but a subsequent
765 # qpush without an argument is an error (nothing to
765 # qpush without an argument is an error (nothing to
766 # apply). This allows a loop of "...while hg qpush..." to
766 # apply). This allows a loop of "...while hg qpush..." to
767 # work as it detects an error when done
767 # work as it detects an error when done
768 if self.series_end() == len(self.series):
768 if self.series_end() == len(self.series):
769 self.ui.warn(_('patch series already fully applied\n'))
769 self.ui.warn(_('patch series already fully applied\n'))
770 return 1
770 return 1
771 if not force:
771 if not force:
772 self.check_localchanges(repo)
772 self.check_localchanges(repo)
773
773
774 self.applied_dirty = 1;
774 self.applied_dirty = 1;
775 start = self.series_end()
775 start = self.series_end()
776 if start > 0:
776 if start > 0:
777 self.check_toppatch(repo)
777 self.check_toppatch(repo)
778 if not patch:
778 if not patch:
779 patch = self.series[start]
779 patch = self.series[start]
780 end = start + 1
780 end = start + 1
781 else:
781 else:
782 end = self.series.index(patch, start) + 1
782 end = self.series.index(patch, start) + 1
783 s = self.series[start:end]
783 s = self.series[start:end]
784 all_files = {}
784 all_files = {}
785 try:
785 try:
786 if mergeq:
786 if mergeq:
787 ret = self.mergepatch(repo, mergeq, s)
787 ret = self.mergepatch(repo, mergeq, s)
788 else:
788 else:
789 ret = self.apply(repo, s, list, all_files=all_files)
789 ret = self.apply(repo, s, list, all_files=all_files)
790 except:
790 except:
791 self.ui.warn(_('cleaning up working directory...'))
791 self.ui.warn(_('cleaning up working directory...'))
792 node = repo.dirstate.parents()[0]
792 node = repo.dirstate.parents()[0]
793 hg.revert(repo, node, None)
793 hg.revert(repo, node, None)
794 unknown = repo.status()[4]
794 unknown = repo.status()[4]
795 # only remove unknown files that we know we touched or
795 # only remove unknown files that we know we touched or
796 # created while patching
796 # created while patching
797 for f in unknown:
797 for f in unknown:
798 if f in all_files:
798 if f in all_files:
799 util.unlink(repo.wjoin(f))
799 util.unlink(repo.wjoin(f))
800 self.ui.warn(_('done\n'))
800 self.ui.warn(_('done\n'))
801 raise
801 raise
802 top = self.applied[-1].name
802 top = self.applied[-1].name
803 if ret[0]:
803 if ret[0]:
804 self.ui.write(
804 self.ui.write(
805 "Errors during apply, please fix and refresh %s\n" % top)
805 "Errors during apply, please fix and refresh %s\n" % top)
806 else:
806 else:
807 self.ui.write("Now at: %s\n" % top)
807 self.ui.write("Now at: %s\n" % top)
808 return ret[0]
808 return ret[0]
809 finally:
809 finally:
810 del wlock
810 del wlock
811
811
812 def pop(self, repo, patch=None, force=False, update=True, all=False):
812 def pop(self, repo, patch=None, force=False, update=True, all=False):
813 def getfile(f, rev, flags):
813 def getfile(f, rev, flags):
814 t = repo.file(f).read(rev)
814 t = repo.file(f).read(rev)
815 repo.wwrite(f, t, flags)
815 repo.wwrite(f, t, flags)
816
816
817 wlock = repo.wlock()
817 wlock = repo.wlock()
818 try:
818 try:
819 if patch:
819 if patch:
820 # index, rev, patch
820 # index, rev, patch
821 info = self.isapplied(patch)
821 info = self.isapplied(patch)
822 if not info:
822 if not info:
823 patch = self.lookup(patch)
823 patch = self.lookup(patch)
824 info = self.isapplied(patch)
824 info = self.isapplied(patch)
825 if not info:
825 if not info:
826 raise util.Abort(_("patch %s is not applied") % patch)
826 raise util.Abort(_("patch %s is not applied") % patch)
827
827
828 if len(self.applied) == 0:
828 if len(self.applied) == 0:
829 # Allow qpop -a to work repeatedly,
829 # Allow qpop -a to work repeatedly,
830 # but not qpop without an argument
830 # but not qpop without an argument
831 self.ui.warn(_("no patches applied\n"))
831 self.ui.warn(_("no patches applied\n"))
832 return not all
832 return not all
833
833
834 if not update:
834 if not update:
835 parents = repo.dirstate.parents()
835 parents = repo.dirstate.parents()
836 rr = [ revlog.bin(x.rev) for x in self.applied ]
836 rr = [ revlog.bin(x.rev) for x in self.applied ]
837 for p in parents:
837 for p in parents:
838 if p in rr:
838 if p in rr:
839 self.ui.warn("qpop: forcing dirstate update\n")
839 self.ui.warn("qpop: forcing dirstate update\n")
840 update = True
840 update = True
841
841
842 if not force and update:
842 if not force and update:
843 self.check_localchanges(repo)
843 self.check_localchanges(repo)
844
844
845 self.applied_dirty = 1;
845 self.applied_dirty = 1;
846 end = len(self.applied)
846 end = len(self.applied)
847 if not patch:
847 if not patch:
848 if all:
848 if all:
849 popi = 0
849 popi = 0
850 else:
850 else:
851 popi = len(self.applied) - 1
851 popi = len(self.applied) - 1
852 else:
852 else:
853 popi = info[0] + 1
853 popi = info[0] + 1
854 if popi >= end:
854 if popi >= end:
855 self.ui.warn("qpop: %s is already at the top\n" % patch)
855 self.ui.warn("qpop: %s is already at the top\n" % patch)
856 return
856 return
857 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
857 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
858
858
859 start = info[0]
859 start = info[0]
860 rev = revlog.bin(info[1])
860 rev = revlog.bin(info[1])
861
861
862 # we know there are no local changes, so we can make a simplified
862 # we know there are no local changes, so we can make a simplified
863 # form of hg.update.
863 # form of hg.update.
864 if update:
864 if update:
865 top = self.check_toppatch(repo)
865 top = self.check_toppatch(repo)
866 qp = self.qparents(repo, rev)
866 qp = self.qparents(repo, rev)
867 changes = repo.changelog.read(qp)
867 changes = repo.changelog.read(qp)
868 mmap = repo.manifest.read(changes[0])
868 mmap = repo.manifest.read(changes[0])
869 m, a, r, d, u = repo.status(qp, top)[:5]
869 m, a, r, d, u = repo.status(qp, top)[:5]
870 if d:
870 if d:
871 raise util.Abort("deletions found between repo revs")
871 raise util.Abort("deletions found between repo revs")
872 for f in m:
872 for f in m:
873 getfile(f, mmap[f], mmap.flags(f))
873 getfile(f, mmap[f], mmap.flags(f))
874 for f in r:
874 for f in r:
875 getfile(f, mmap[f], mmap.flags(f))
875 getfile(f, mmap[f], mmap.flags(f))
876 for f in m + r:
876 for f in m + r:
877 repo.dirstate.normal(f)
877 repo.dirstate.normal(f)
878 for f in a:
878 for f in a:
879 try:
879 try:
880 os.unlink(repo.wjoin(f))
880 os.unlink(repo.wjoin(f))
881 except OSError, e:
881 except OSError, e:
882 if e.errno != errno.ENOENT:
882 if e.errno != errno.ENOENT:
883 raise
883 raise
884 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
884 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
885 except: pass
885 except: pass
886 repo.dirstate.forget(f)
886 repo.dirstate.forget(f)
887 repo.dirstate.setparents(qp, revlog.nullid)
887 repo.dirstate.setparents(qp, revlog.nullid)
888 self.strip(repo, rev, update=False, backup='strip')
888 self.strip(repo, rev, update=False, backup='strip')
889 del self.applied[start:end]
889 del self.applied[start:end]
890 if len(self.applied):
890 if len(self.applied):
891 self.ui.write("Now at: %s\n" % self.applied[-1].name)
891 self.ui.write("Now at: %s\n" % self.applied[-1].name)
892 else:
892 else:
893 self.ui.write("Patch queue now empty\n")
893 self.ui.write("Patch queue now empty\n")
894 finally:
894 finally:
895 del wlock
895 del wlock
896
896
897 def diff(self, repo, pats, opts):
897 def diff(self, repo, pats, opts):
898 top = self.check_toppatch(repo)
898 top = self.check_toppatch(repo)
899 if not top:
899 if not top:
900 self.ui.write("No patches applied\n")
900 self.ui.write("No patches applied\n")
901 return
901 return
902 qp = self.qparents(repo, top)
902 qp = self.qparents(repo, top)
903 if opts.get('git'):
903 if opts.get('git'):
904 self.diffopts().git = True
904 self.diffopts().git = True
905 self.printdiff(repo, qp, files=pats, opts=opts)
905 self.printdiff(repo, qp, files=pats, opts=opts)
906
906
907 def refresh(self, repo, pats=None, **opts):
907 def refresh(self, repo, pats=None, **opts):
908 if len(self.applied) == 0:
908 if len(self.applied) == 0:
909 self.ui.write("No patches applied\n")
909 self.ui.write("No patches applied\n")
910 return 1
910 return 1
911 wlock = repo.wlock()
911 wlock = repo.wlock()
912 try:
912 try:
913 self.check_toppatch(repo)
913 self.check_toppatch(repo)
914 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
914 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
915 top = revlog.bin(top)
915 top = revlog.bin(top)
916 cparents = repo.changelog.parents(top)
916 cparents = repo.changelog.parents(top)
917 patchparent = self.qparents(repo, top)
917 patchparent = self.qparents(repo, top)
918 message, comments, user, date, patchfound = self.readheaders(patchfn)
918 message, comments, user, date, patchfound = self.readheaders(patchfn)
919
919
920 patchf = self.opener(patchfn, 'r+')
920 patchf = self.opener(patchfn, 'r+')
921
921
922 # if the patch was a git patch, refresh it as a git patch
922 # if the patch was a git patch, refresh it as a git patch
923 for line in patchf:
923 for line in patchf:
924 if line.startswith('diff --git'):
924 if line.startswith('diff --git'):
925 self.diffopts().git = True
925 self.diffopts().git = True
926 break
926 break
927
927
928 msg = opts.get('msg', '').rstrip()
928 msg = opts.get('msg', '').rstrip()
929 if msg and comments:
929 if msg and comments:
930 # Remove existing message, keeping the rest of the comments
930 # Remove existing message, keeping the rest of the comments
931 # fields.
931 # fields.
932 # If comments contains 'subject: ', message will prepend
932 # If comments contains 'subject: ', message will prepend
933 # the field and a blank line.
933 # the field and a blank line.
934 if message:
934 if message:
935 subj = 'subject: ' + message[0].lower()
935 subj = 'subject: ' + message[0].lower()
936 for i in xrange(len(comments)):
936 for i in xrange(len(comments)):
937 if subj == comments[i].lower():
937 if subj == comments[i].lower():
938 del comments[i]
938 del comments[i]
939 message = message[2:]
939 message = message[2:]
940 break
940 break
941 ci = 0
941 ci = 0
942 for mi in xrange(len(message)):
942 for mi in xrange(len(message)):
943 while message[mi] != comments[ci]:
943 while message[mi] != comments[ci]:
944 ci += 1
944 ci += 1
945 del comments[ci]
945 del comments[ci]
946 if msg:
946 if msg:
947 comments.append(msg)
947 comments.append(msg)
948
948
949 patchf.seek(0)
949 patchf.seek(0)
950 patchf.truncate()
950 patchf.truncate()
951
951
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 match = dict.fromkeys(filelist).__contains__
981 match = dict.fromkeys(filelist).__contains__
982 else:
982 else:
983 filelist = None
983 filelist = None
984 match = util.always
984 match = util.always
985 m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
985 m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
986
986
987 # we might end up with files that were added between
987 # we might end up with files that were added between
988 # tip and the dirstate parent, but then changed in the
988 # tip and the dirstate parent, but then changed in the
989 # local dirstate. in this case, we want them to only
989 # local dirstate. in this case, we want them to only
990 # show up in the added section
990 # show up in the added section
991 for x in m:
991 for x in m:
992 if x not in aa:
992 if x not in aa:
993 mm.append(x)
993 mm.append(x)
994 # we might end up with files added by the local dirstate that
994 # we might end up with files added by the local dirstate that
995 # were deleted by the patch. In this case, they should only
995 # were deleted by the patch. In this case, they should only
996 # show up in the changed section.
996 # show up in the changed section.
997 for x in a:
997 for x in a:
998 if x in dd:
998 if x in dd:
999 del dd[dd.index(x)]
999 del dd[dd.index(x)]
1000 mm.append(x)
1000 mm.append(x)
1001 else:
1001 else:
1002 aa.append(x)
1002 aa.append(x)
1003 # make sure any files deleted in the local dirstate
1003 # make sure any files deleted in the local dirstate
1004 # are not in the add or change column of the patch
1004 # are not in the add or change column of the patch
1005 forget = []
1005 forget = []
1006 for x in d + r:
1006 for x in d + r:
1007 if x in aa:
1007 if x in aa:
1008 del aa[aa.index(x)]
1008 del aa[aa.index(x)]
1009 forget.append(x)
1009 forget.append(x)
1010 continue
1010 continue
1011 elif x in mm:
1011 elif x in mm:
1012 del mm[mm.index(x)]
1012 del mm[mm.index(x)]
1013 dd.append(x)
1013 dd.append(x)
1014
1014
1015 m = util.unique(mm)
1015 m = util.unique(mm)
1016 r = util.unique(dd)
1016 r = util.unique(dd)
1017 a = util.unique(aa)
1017 a = util.unique(aa)
1018 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1018 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1019 filelist = util.unique(c[0] + c[1] + c[2])
1019 filelist = util.unique(c[0] + c[1] + c[2])
1020 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1020 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1021 fp=patchf, changes=c, opts=self.diffopts())
1021 fp=patchf, changes=c, opts=self.diffopts())
1022 patchf.close()
1022 patchf.close()
1023
1023
1024 repo.dirstate.setparents(*cparents)
1024 repo.dirstate.setparents(*cparents)
1025 copies = {}
1025 copies = {}
1026 for dst in a:
1026 for dst in a:
1027 src = repo.dirstate.copied(dst)
1027 src = repo.dirstate.copied(dst)
1028 if src is not None:
1028 if src is not None:
1029 copies.setdefault(src, []).append(dst)
1029 copies.setdefault(src, []).append(dst)
1030 repo.dirstate.add(dst)
1030 repo.dirstate.add(dst)
1031 # remember the copies between patchparent and tip
1031 # remember the copies between patchparent and tip
1032 # this may be slow, so don't do it if we're not tracking copies
1032 # this may be slow, so don't do it if we're not tracking copies
1033 if self.diffopts().git:
1033 if self.diffopts().git:
1034 for dst in aaa:
1034 for dst in aaa:
1035 f = repo.file(dst)
1035 f = repo.file(dst)
1036 src = f.renamed(man[dst])
1036 src = f.renamed(man[dst])
1037 if src:
1037 if src:
1038 copies[src[0]] = copies.get(dst, [])
1038 copies[src[0]] = copies.get(dst, [])
1039 if dst in a:
1039 if dst in a:
1040 copies[src[0]].append(dst)
1040 copies[src[0]].append(dst)
1041 # we can't copy a file created by the patch itself
1041 # we can't copy a file created by the patch itself
1042 if dst in copies:
1042 if dst in copies:
1043 del copies[dst]
1043 del copies[dst]
1044 for src, dsts in copies.iteritems():
1044 for src, dsts in copies.iteritems():
1045 for dst in dsts:
1045 for dst in dsts:
1046 repo.dirstate.copy(src, dst)
1046 repo.dirstate.copy(src, dst)
1047 for f in r:
1047 for f in r:
1048 repo.dirstate.remove(f)
1048 repo.dirstate.remove(f)
1049 # if the patch excludes a modified file, mark that
1049 # if the patch excludes a modified file, mark that
1050 # file with mtime=0 so status can see it.
1050 # file with mtime=0 so status can see it.
1051 mm = []
1051 mm = []
1052 for i in xrange(len(m)-1, -1, -1):
1052 for i in xrange(len(m)-1, -1, -1):
1053 if not matchfn(m[i]):
1053 if not matchfn(m[i]):
1054 mm.append(m[i])
1054 mm.append(m[i])
1055 del m[i]
1055 del m[i]
1056 for f in m:
1056 for f in m:
1057 repo.dirstate.normal(f)
1057 repo.dirstate.normal(f)
1058 for f in mm:
1058 for f in mm:
1059 repo.dirstate.normallookup(f)
1059 repo.dirstate.normallookup(f)
1060 for f in forget:
1060 for f in forget:
1061 repo.dirstate.forget(f)
1061 repo.dirstate.forget(f)
1062
1062
1063 if not msg:
1063 if not msg:
1064 if not message:
1064 if not message:
1065 message = "[mq]: %s\n" % patchfn
1065 message = "[mq]: %s\n" % patchfn
1066 else:
1066 else:
1067 message = "\n".join(message)
1067 message = "\n".join(message)
1068 else:
1068 else:
1069 message = msg
1069 message = msg
1070
1070
1071 self.strip(repo, top, update=False,
1071 self.strip(repo, top, update=False,
1072 backup='strip')
1072 backup='strip')
1073 n = repo.commit(filelist, message, changes[1], match=matchfn,
1073 n = repo.commit(filelist, message, changes[1], match=matchfn,
1074 force=1)
1074 force=1)
1075 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1075 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1076 self.applied_dirty = 1
1076 self.applied_dirty = 1
1077 self.removeundo(repo)
1077 self.removeundo(repo)
1078 else:
1078 else:
1079 self.printdiff(repo, patchparent, fp=patchf)
1079 self.printdiff(repo, patchparent, fp=patchf)
1080 patchf.close()
1080 patchf.close()
1081 added = repo.status()[1]
1081 added = repo.status()[1]
1082 for a in added:
1082 for a in added:
1083 f = repo.wjoin(a)
1083 f = repo.wjoin(a)
1084 try:
1084 try:
1085 os.unlink(f)
1085 os.unlink(f)
1086 except OSError, e:
1086 except OSError, e:
1087 if e.errno != errno.ENOENT:
1087 if e.errno != errno.ENOENT:
1088 raise
1088 raise
1089 try: os.removedirs(os.path.dirname(f))
1089 try: os.removedirs(os.path.dirname(f))
1090 except: pass
1090 except: pass
1091 # forget the file copies in the dirstate
1091 # forget the file copies in the dirstate
1092 # push should readd the files later on
1092 # push should readd the files later on
1093 repo.dirstate.forget(a)
1093 repo.dirstate.forget(a)
1094 self.pop(repo, force=True)
1094 self.pop(repo, force=True)
1095 self.push(repo, force=True)
1095 self.push(repo, force=True)
1096 finally:
1096 finally:
1097 del wlock
1097 del wlock
1098
1098
1099 def init(self, repo, create=False):
1099 def init(self, repo, create=False):
1100 if not create and os.path.isdir(self.path):
1100 if not create and os.path.isdir(self.path):
1101 raise util.Abort(_("patch queue directory already exists"))
1101 raise util.Abort(_("patch queue directory already exists"))
1102 try:
1102 try:
1103 os.mkdir(self.path)
1103 os.mkdir(self.path)
1104 except OSError, inst:
1104 except OSError, inst:
1105 if inst.errno != errno.EEXIST or not create:
1105 if inst.errno != errno.EEXIST or not create:
1106 raise
1106 raise
1107 if create:
1107 if create:
1108 return self.qrepo(create=True)
1108 return self.qrepo(create=True)
1109
1109
1110 def unapplied(self, repo, patch=None):
1110 def unapplied(self, repo, patch=None):
1111 if patch and patch not in self.series:
1111 if patch and patch not in self.series:
1112 raise util.Abort(_("patch %s is not in series file") % patch)
1112 raise util.Abort(_("patch %s is not in series file") % patch)
1113 if not patch:
1113 if not patch:
1114 start = self.series_end()
1114 start = self.series_end()
1115 else:
1115 else:
1116 start = self.series.index(patch) + 1
1116 start = self.series.index(patch) + 1
1117 unapplied = []
1117 unapplied = []
1118 for i in xrange(start, len(self.series)):
1118 for i in xrange(start, len(self.series)):
1119 pushable, reason = self.pushable(i)
1119 pushable, reason = self.pushable(i)
1120 if pushable:
1120 if pushable:
1121 unapplied.append((i, self.series[i]))
1121 unapplied.append((i, self.series[i]))
1122 self.explain_pushable(i)
1122 self.explain_pushable(i)
1123 return unapplied
1123 return unapplied
1124
1124
1125 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1125 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1126 summary=False):
1126 summary=False):
1127 def displayname(patchname):
1127 def displayname(patchname):
1128 if summary:
1128 if summary:
1129 msg = self.readheaders(patchname)[0]
1129 msg = self.readheaders(patchname)[0]
1130 msg = msg and ': ' + msg[0] or ': '
1130 msg = msg and ': ' + msg[0] or ': '
1131 else:
1131 else:
1132 msg = ''
1132 msg = ''
1133 return '%s%s' % (patchname, msg)
1133 return '%s%s' % (patchname, msg)
1134
1134
1135 applied = dict.fromkeys([p.name for p in self.applied])
1135 applied = dict.fromkeys([p.name for p in self.applied])
1136 if length is None:
1136 if length is None:
1137 length = len(self.series) - start
1137 length = len(self.series) - start
1138 if not missing:
1138 if not missing:
1139 for i in xrange(start, start+length):
1139 for i in xrange(start, start+length):
1140 patch = self.series[i]
1140 patch = self.series[i]
1141 if patch in applied:
1141 if patch in applied:
1142 stat = 'A'
1142 stat = 'A'
1143 elif self.pushable(i)[0]:
1143 elif self.pushable(i)[0]:
1144 stat = 'U'
1144 stat = 'U'
1145 else:
1145 else:
1146 stat = 'G'
1146 stat = 'G'
1147 pfx = ''
1147 pfx = ''
1148 if self.ui.verbose:
1148 if self.ui.verbose:
1149 pfx = '%d %s ' % (i, stat)
1149 pfx = '%d %s ' % (i, stat)
1150 elif status and status != stat:
1150 elif status and status != stat:
1151 continue
1151 continue
1152 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1152 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1153 else:
1153 else:
1154 msng_list = []
1154 msng_list = []
1155 for root, dirs, files in os.walk(self.path):
1155 for root, dirs, files in os.walk(self.path):
1156 d = root[len(self.path) + 1:]
1156 d = root[len(self.path) + 1:]
1157 for f in files:
1157 for f in files:
1158 fl = os.path.join(d, f)
1158 fl = os.path.join(d, f)
1159 if (fl not in self.series and
1159 if (fl not in self.series and
1160 fl not in (self.status_path, self.series_path,
1160 fl not in (self.status_path, self.series_path,
1161 self.guards_path)
1161 self.guards_path)
1162 and not fl.startswith('.')):
1162 and not fl.startswith('.')):
1163 msng_list.append(fl)
1163 msng_list.append(fl)
1164 msng_list.sort()
1164 msng_list.sort()
1165 for x in msng_list:
1165 for x in msng_list:
1166 pfx = self.ui.verbose and ('D ') or ''
1166 pfx = self.ui.verbose and ('D ') or ''
1167 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1167 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1168
1168
1169 def issaveline(self, l):
1169 def issaveline(self, l):
1170 if l.name == '.hg.patches.save.line':
1170 if l.name == '.hg.patches.save.line':
1171 return True
1171 return True
1172
1172
1173 def qrepo(self, create=False):
1173 def qrepo(self, create=False):
1174 if create or os.path.isdir(self.join(".hg")):
1174 if create or os.path.isdir(self.join(".hg")):
1175 return hg.repository(self.ui, path=self.path, create=create)
1175 return hg.repository(self.ui, path=self.path, create=create)
1176
1176
1177 def restore(self, repo, rev, delete=None, qupdate=None):
1177 def restore(self, repo, rev, delete=None, qupdate=None):
1178 c = repo.changelog.read(rev)
1178 c = repo.changelog.read(rev)
1179 desc = c[4].strip()
1179 desc = c[4].strip()
1180 lines = desc.splitlines()
1180 lines = desc.splitlines()
1181 i = 0
1181 i = 0
1182 datastart = None
1182 datastart = None
1183 series = []
1183 series = []
1184 applied = []
1184 applied = []
1185 qpp = None
1185 qpp = None
1186 for i in xrange(0, len(lines)):
1186 for i in xrange(0, len(lines)):
1187 if lines[i] == 'Patch Data:':
1187 if lines[i] == 'Patch Data:':
1188 datastart = i + 1
1188 datastart = i + 1
1189 elif lines[i].startswith('Dirstate:'):
1189 elif lines[i].startswith('Dirstate:'):
1190 l = lines[i].rstrip()
1190 l = lines[i].rstrip()
1191 l = l[10:].split(' ')
1191 l = l[10:].split(' ')
1192 qpp = [ hg.bin(x) for x in l ]
1192 qpp = [ hg.bin(x) for x in l ]
1193 elif datastart != None:
1193 elif datastart != None:
1194 l = lines[i].rstrip()
1194 l = lines[i].rstrip()
1195 se = statusentry(l)
1195 se = statusentry(l)
1196 file_ = se.name
1196 file_ = se.name
1197 if se.rev:
1197 if se.rev:
1198 applied.append(se)
1198 applied.append(se)
1199 else:
1199 else:
1200 series.append(file_)
1200 series.append(file_)
1201 if datastart == None:
1201 if datastart == None:
1202 self.ui.warn("No saved patch data found\n")
1202 self.ui.warn("No saved patch data found\n")
1203 return 1
1203 return 1
1204 self.ui.warn("restoring status: %s\n" % lines[0])
1204 self.ui.warn("restoring status: %s\n" % lines[0])
1205 self.full_series = series
1205 self.full_series = series
1206 self.applied = applied
1206 self.applied = applied
1207 self.parse_series()
1207 self.parse_series()
1208 self.series_dirty = 1
1208 self.series_dirty = 1
1209 self.applied_dirty = 1
1209 self.applied_dirty = 1
1210 heads = repo.changelog.heads()
1210 heads = repo.changelog.heads()
1211 if delete:
1211 if delete:
1212 if rev not in heads:
1212 if rev not in heads:
1213 self.ui.warn("save entry has children, leaving it alone\n")
1213 self.ui.warn("save entry has children, leaving it alone\n")
1214 else:
1214 else:
1215 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1215 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1216 pp = repo.dirstate.parents()
1216 pp = repo.dirstate.parents()
1217 if rev in pp:
1217 if rev in pp:
1218 update = True
1218 update = True
1219 else:
1219 else:
1220 update = False
1220 update = False
1221 self.strip(repo, rev, update=update, backup='strip')
1221 self.strip(repo, rev, update=update, backup='strip')
1222 if qpp:
1222 if qpp:
1223 self.ui.warn("saved queue repository parents: %s %s\n" %
1223 self.ui.warn("saved queue repository parents: %s %s\n" %
1224 (hg.short(qpp[0]), hg.short(qpp[1])))
1224 (hg.short(qpp[0]), hg.short(qpp[1])))
1225 if qupdate:
1225 if qupdate:
1226 print "queue directory updating"
1226 print "queue directory updating"
1227 r = self.qrepo()
1227 r = self.qrepo()
1228 if not r:
1228 if not r:
1229 self.ui.warn("Unable to load queue repository\n")
1229 self.ui.warn("Unable to load queue repository\n")
1230 return 1
1230 return 1
1231 hg.clean(r, qpp[0])
1231 hg.clean(r, qpp[0])
1232
1232
1233 def save(self, repo, msg=None):
1233 def save(self, repo, msg=None):
1234 if len(self.applied) == 0:
1234 if len(self.applied) == 0:
1235 self.ui.warn("save: no patches applied, exiting\n")
1235 self.ui.warn("save: no patches applied, exiting\n")
1236 return 1
1236 return 1
1237 if self.issaveline(self.applied[-1]):
1237 if self.issaveline(self.applied[-1]):
1238 self.ui.warn("status is already saved\n")
1238 self.ui.warn("status is already saved\n")
1239 return 1
1239 return 1
1240
1240
1241 ar = [ ':' + x for x in self.full_series ]
1241 ar = [ ':' + x for x in self.full_series ]
1242 if not msg:
1242 if not msg:
1243 msg = "hg patches saved state"
1243 msg = "hg patches saved state"
1244 else:
1244 else:
1245 msg = "hg patches: " + msg.rstrip('\r\n')
1245 msg = "hg patches: " + msg.rstrip('\r\n')
1246 r = self.qrepo()
1246 r = self.qrepo()
1247 if r:
1247 if r:
1248 pp = r.dirstate.parents()
1248 pp = r.dirstate.parents()
1249 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1249 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1250 msg += "\n\nPatch Data:\n"
1250 msg += "\n\nPatch Data:\n"
1251 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1251 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1252 "\n".join(ar) + '\n' or "")
1252 "\n".join(ar) + '\n' or "")
1253 n = repo.commit(None, text, user=None, force=1)
1253 n = repo.commit(None, text, user=None, force=1)
1254 if not n:
1254 if not n:
1255 self.ui.warn("repo commit failed\n")
1255 self.ui.warn("repo commit failed\n")
1256 return 1
1256 return 1
1257 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1257 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1258 self.applied_dirty = 1
1258 self.applied_dirty = 1
1259 self.removeundo(repo)
1259 self.removeundo(repo)
1260
1260
1261 def full_series_end(self):
1261 def full_series_end(self):
1262 if len(self.applied) > 0:
1262 if len(self.applied) > 0:
1263 p = self.applied[-1].name
1263 p = self.applied[-1].name
1264 end = self.find_series(p)
1264 end = self.find_series(p)
1265 if end == None:
1265 if end == None:
1266 return len(self.full_series)
1266 return len(self.full_series)
1267 return end + 1
1267 return end + 1
1268 return 0
1268 return 0
1269
1269
1270 def series_end(self, all_patches=False):
1270 def series_end(self, all_patches=False):
1271 """If all_patches is False, return the index of the next pushable patch
1271 """If all_patches is False, return the index of the next pushable patch
1272 in the series, or the series length. If all_patches is True, return the
1272 in the series, or the series length. If all_patches is True, return the
1273 index of the first patch past the last applied one.
1273 index of the first patch past the last applied one.
1274 """
1274 """
1275 end = 0
1275 end = 0
1276 def next(start):
1276 def next(start):
1277 if all_patches:
1277 if all_patches:
1278 return start
1278 return start
1279 i = start
1279 i = start
1280 while i < len(self.series):
1280 while i < len(self.series):
1281 p, reason = self.pushable(i)
1281 p, reason = self.pushable(i)
1282 if p:
1282 if p:
1283 break
1283 break
1284 self.explain_pushable(i)
1284 self.explain_pushable(i)
1285 i += 1
1285 i += 1
1286 return i
1286 return i
1287 if len(self.applied) > 0:
1287 if len(self.applied) > 0:
1288 p = self.applied[-1].name
1288 p = self.applied[-1].name
1289 try:
1289 try:
1290 end = self.series.index(p)
1290 end = self.series.index(p)
1291 except ValueError:
1291 except ValueError:
1292 return 0
1292 return 0
1293 return next(end + 1)
1293 return next(end + 1)
1294 return next(end)
1294 return next(end)
1295
1295
1296 def appliedname(self, index):
1296 def appliedname(self, index):
1297 pname = self.applied[index].name
1297 pname = self.applied[index].name
1298 if not self.ui.verbose:
1298 if not self.ui.verbose:
1299 p = pname
1299 p = pname
1300 else:
1300 else:
1301 p = str(self.series.index(pname)) + " " + pname
1301 p = str(self.series.index(pname)) + " " + pname
1302 return p
1302 return p
1303
1303
1304 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1304 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1305 force=None, git=False):
1305 force=None, git=False):
1306 def checkseries(patchname):
1306 def checkseries(patchname):
1307 if patchname in self.series:
1307 if patchname in self.series:
1308 raise util.Abort(_('patch %s is already in the series file')
1308 raise util.Abort(_('patch %s is already in the series file')
1309 % patchname)
1309 % patchname)
1310 def checkfile(patchname):
1310 def checkfile(patchname):
1311 if not force and os.path.exists(self.join(patchname)):
1311 if not force and os.path.exists(self.join(patchname)):
1312 raise util.Abort(_('patch "%s" already exists')
1312 raise util.Abort(_('patch "%s" already exists')
1313 % patchname)
1313 % patchname)
1314
1314
1315 if rev:
1315 if rev:
1316 if files:
1316 if files:
1317 raise util.Abort(_('option "-r" not valid when importing '
1317 raise util.Abort(_('option "-r" not valid when importing '
1318 'files'))
1318 'files'))
1319 rev = cmdutil.revrange(repo, rev)
1319 rev = cmdutil.revrange(repo, rev)
1320 rev.sort(lambda x, y: cmp(y, x))
1320 rev.sort(lambda x, y: cmp(y, x))
1321 if (len(files) > 1 or len(rev) > 1) and patchname:
1321 if (len(files) > 1 or len(rev) > 1) and patchname:
1322 raise util.Abort(_('option "-n" not valid when importing multiple '
1322 raise util.Abort(_('option "-n" not valid when importing multiple '
1323 'patches'))
1323 'patches'))
1324 i = 0
1324 i = 0
1325 added = []
1325 added = []
1326 if rev:
1326 if rev:
1327 # If mq patches are applied, we can only import revisions
1327 # If mq patches are applied, we can only import revisions
1328 # that form a linear path to qbase.
1328 # that form a linear path to qbase.
1329 # Otherwise, they should form a linear path to a head.
1329 # Otherwise, they should form a linear path to a head.
1330 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1330 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1331 if len(heads) > 1:
1331 if len(heads) > 1:
1332 raise util.Abort(_('revision %d is the root of more than one '
1332 raise util.Abort(_('revision %d is the root of more than one '
1333 'branch') % rev[-1])
1333 'branch') % rev[-1])
1334 if self.applied:
1334 if self.applied:
1335 base = revlog.hex(repo.changelog.node(rev[0]))
1335 base = revlog.hex(repo.changelog.node(rev[0]))
1336 if base in [n.rev for n in self.applied]:
1336 if base in [n.rev for n in self.applied]:
1337 raise util.Abort(_('revision %d is already managed')
1337 raise util.Abort(_('revision %d is already managed')
1338 % rev[0])
1338 % rev[0])
1339 if heads != [revlog.bin(self.applied[-1].rev)]:
1339 if heads != [revlog.bin(self.applied[-1].rev)]:
1340 raise util.Abort(_('revision %d is not the parent of '
1340 raise util.Abort(_('revision %d is not the parent of '
1341 'the queue') % rev[0])
1341 'the queue') % rev[0])
1342 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1342 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1343 lastparent = repo.changelog.parentrevs(base)[0]
1343 lastparent = repo.changelog.parentrevs(base)[0]
1344 else:
1344 else:
1345 if heads != [repo.changelog.node(rev[0])]:
1345 if heads != [repo.changelog.node(rev[0])]:
1346 raise util.Abort(_('revision %d has unmanaged children')
1346 raise util.Abort(_('revision %d has unmanaged children')
1347 % rev[0])
1347 % rev[0])
1348 lastparent = None
1348 lastparent = None
1349
1349
1350 if git:
1350 if git:
1351 self.diffopts().git = True
1351 self.diffopts().git = True
1352
1352
1353 for r in rev:
1353 for r in rev:
1354 p1, p2 = repo.changelog.parentrevs(r)
1354 p1, p2 = repo.changelog.parentrevs(r)
1355 n = repo.changelog.node(r)
1355 n = repo.changelog.node(r)
1356 if p2 != revlog.nullrev:
1356 if p2 != revlog.nullrev:
1357 raise util.Abort(_('cannot import merge revision %d') % r)
1357 raise util.Abort(_('cannot import merge revision %d') % r)
1358 if lastparent and lastparent != r:
1358 if lastparent and lastparent != r:
1359 raise util.Abort(_('revision %d is not the parent of %d')
1359 raise util.Abort(_('revision %d is not the parent of %d')
1360 % (r, lastparent))
1360 % (r, lastparent))
1361 lastparent = p1
1361 lastparent = p1
1362
1362
1363 if not patchname:
1363 if not patchname:
1364 patchname = normname('%d.diff' % r)
1364 patchname = normname('%d.diff' % r)
1365 checkseries(patchname)
1365 checkseries(patchname)
1366 checkfile(patchname)
1366 checkfile(patchname)
1367 self.full_series.insert(0, patchname)
1367 self.full_series.insert(0, patchname)
1368
1368
1369 patchf = self.opener(patchname, "w")
1369 patchf = self.opener(patchname, "w")
1370 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1370 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1371 patchf.close()
1371 patchf.close()
1372
1372
1373 se = statusentry(revlog.hex(n), patchname)
1373 se = statusentry(revlog.hex(n), patchname)
1374 self.applied.insert(0, se)
1374 self.applied.insert(0, se)
1375
1375
1376 added.append(patchname)
1376 added.append(patchname)
1377 patchname = None
1377 patchname = None
1378 self.parse_series()
1378 self.parse_series()
1379 self.applied_dirty = 1
1379 self.applied_dirty = 1
1380
1380
1381 for filename in files:
1381 for filename in files:
1382 if existing:
1382 if existing:
1383 if filename == '-':
1383 if filename == '-':
1384 raise util.Abort(_('-e is incompatible with import from -'))
1384 raise util.Abort(_('-e is incompatible with import from -'))
1385 if not patchname:
1385 if not patchname:
1386 patchname = normname(filename)
1386 patchname = normname(filename)
1387 if not os.path.isfile(self.join(patchname)):
1387 if not os.path.isfile(self.join(patchname)):
1388 raise util.Abort(_("patch %s does not exist") % patchname)
1388 raise util.Abort(_("patch %s does not exist") % patchname)
1389 else:
1389 else:
1390 try:
1390 try:
1391 if filename == '-':
1391 if filename == '-':
1392 if not patchname:
1392 if not patchname:
1393 raise util.Abort(_('need --name to import a patch from -'))
1393 raise util.Abort(_('need --name to import a patch from -'))
1394 text = sys.stdin.read()
1394 text = sys.stdin.read()
1395 else:
1395 else:
1396 text = file(filename).read()
1396 text = file(filename).read()
1397 except IOError:
1397 except IOError:
1398 raise util.Abort(_("unable to read %s") % patchname)
1398 raise util.Abort(_("unable to read %s") % patchname)
1399 if not patchname:
1399 if not patchname:
1400 patchname = normname(os.path.basename(filename))
1400 patchname = normname(os.path.basename(filename))
1401 checkfile(patchname)
1401 checkfile(patchname)
1402 patchf = self.opener(patchname, "w")
1402 patchf = self.opener(patchname, "w")
1403 patchf.write(text)
1403 patchf.write(text)
1404 checkseries(patchname)
1404 checkseries(patchname)
1405 index = self.full_series_end() + i
1405 index = self.full_series_end() + i
1406 self.full_series[index:index] = [patchname]
1406 self.full_series[index:index] = [patchname]
1407 self.parse_series()
1407 self.parse_series()
1408 self.ui.warn("adding %s to series file\n" % patchname)
1408 self.ui.warn("adding %s to series file\n" % patchname)
1409 i += 1
1409 i += 1
1410 added.append(patchname)
1410 added.append(patchname)
1411 patchname = None
1411 patchname = None
1412 self.series_dirty = 1
1412 self.series_dirty = 1
1413 qrepo = self.qrepo()
1413 qrepo = self.qrepo()
1414 if qrepo:
1414 if qrepo:
1415 qrepo.add(added)
1415 qrepo.add(added)
1416
1416
1417 def delete(ui, repo, *patches, **opts):
1417 def delete(ui, repo, *patches, **opts):
1418 """remove patches from queue
1418 """remove patches from queue
1419
1419
1420 The patches must not be applied, unless they are arguments to
1420 The patches must not be applied, unless they are arguments to
1421 the --rev parameter. At least one patch or revision is required.
1421 the --rev parameter. At least one patch or revision is required.
1422
1422
1423 With --rev, mq will stop managing the named revisions (converting
1423 With --rev, mq will stop managing the named revisions (converting
1424 them to regular mercurial changesets). The patches must be applied
1424 them to regular mercurial changesets). The patches must be applied
1425 and at the base of the stack. This option is useful when the patches
1425 and at the base of the stack. This option is useful when the patches
1426 have been applied upstream.
1426 have been applied upstream.
1427
1427
1428 With --keep, the patch files are preserved in the patch directory."""
1428 With --keep, the patch files are preserved in the patch directory."""
1429 q = repo.mq
1429 q = repo.mq
1430 q.delete(repo, patches, opts)
1430 q.delete(repo, patches, opts)
1431 q.save_dirty()
1431 q.save_dirty()
1432 return 0
1432 return 0
1433
1433
1434 def applied(ui, repo, patch=None, **opts):
1434 def applied(ui, repo, patch=None, **opts):
1435 """print the patches already applied"""
1435 """print the patches already applied"""
1436 q = repo.mq
1436 q = repo.mq
1437 if patch:
1437 if patch:
1438 if patch not in q.series:
1438 if patch not in q.series:
1439 raise util.Abort(_("patch %s is not in series file") % patch)
1439 raise util.Abort(_("patch %s is not in series file") % patch)
1440 end = q.series.index(patch) + 1
1440 end = q.series.index(patch) + 1
1441 else:
1441 else:
1442 end = q.series_end(True)
1442 end = q.series_end(True)
1443 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1443 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1444
1444
1445 def unapplied(ui, repo, patch=None, **opts):
1445 def unapplied(ui, repo, patch=None, **opts):
1446 """print the patches not yet applied"""
1446 """print the patches not yet applied"""
1447 q = repo.mq
1447 q = repo.mq
1448 if patch:
1448 if patch:
1449 if patch not in q.series:
1449 if patch not in q.series:
1450 raise util.Abort(_("patch %s is not in series file") % patch)
1450 raise util.Abort(_("patch %s is not in series file") % patch)
1451 start = q.series.index(patch) + 1
1451 start = q.series.index(patch) + 1
1452 else:
1452 else:
1453 start = q.series_end(True)
1453 start = q.series_end(True)
1454 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1454 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1455
1455
1456 def qimport(ui, repo, *filename, **opts):
1456 def qimport(ui, repo, *filename, **opts):
1457 """import a patch
1457 """import a patch
1458
1458
1459 The patch will have the same name as its source file unless you
1459 The patch will have the same name as its source file unless you
1460 give it a new one with --name.
1460 give it a new one with --name.
1461
1461
1462 You can register an existing patch inside the patch directory
1462 You can register an existing patch inside the patch directory
1463 with the --existing flag.
1463 with the --existing flag.
1464
1464
1465 With --force, an existing patch of the same name will be overwritten.
1465 With --force, an existing patch of the same name will be overwritten.
1466
1466
1467 An existing changeset may be placed under mq control with --rev
1467 An existing changeset may be placed under mq control with --rev
1468 (e.g. qimport --rev tip -n patch will place tip under mq control).
1468 (e.g. qimport --rev tip -n patch will place tip under mq control).
1469 With --git, patches imported with --rev will use the git diff
1469 With --git, patches imported with --rev will use the git diff
1470 format.
1470 format.
1471 """
1471 """
1472 q = repo.mq
1472 q = repo.mq
1473 q.qimport(repo, filename, patchname=opts['name'],
1473 q.qimport(repo, filename, patchname=opts['name'],
1474 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1474 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1475 git=opts['git'])
1475 git=opts['git'])
1476 q.save_dirty()
1476 q.save_dirty()
1477 return 0
1477 return 0
1478
1478
1479 def init(ui, repo, **opts):
1479 def init(ui, repo, **opts):
1480 """init a new queue repository
1480 """init a new queue repository
1481
1481
1482 The queue repository is unversioned by default. If -c is
1482 The queue repository is unversioned by default. If -c is
1483 specified, qinit will create a separate nested repository
1483 specified, qinit will create a separate nested repository
1484 for patches (qinit -c may also be run later to convert
1484 for patches (qinit -c may also be run later to convert
1485 an unversioned patch repository into a versioned one).
1485 an unversioned patch repository into a versioned one).
1486 You can use qcommit to commit changes to this queue repository."""
1486 You can use qcommit to commit changes to this queue repository."""
1487 q = repo.mq
1487 q = repo.mq
1488 r = q.init(repo, create=opts['create_repo'])
1488 r = q.init(repo, create=opts['create_repo'])
1489 q.save_dirty()
1489 q.save_dirty()
1490 if r:
1490 if r:
1491 if not os.path.exists(r.wjoin('.hgignore')):
1491 if not os.path.exists(r.wjoin('.hgignore')):
1492 fp = r.wopener('.hgignore', 'w')
1492 fp = r.wopener('.hgignore', 'w')
1493 fp.write('syntax: glob\n')
1493 fp.write('syntax: glob\n')
1494 fp.write('status\n')
1494 fp.write('status\n')
1495 fp.write('guards\n')
1495 fp.write('guards\n')
1496 fp.close()
1496 fp.close()
1497 if not os.path.exists(r.wjoin('series')):
1497 if not os.path.exists(r.wjoin('series')):
1498 r.wopener('series', 'w').close()
1498 r.wopener('series', 'w').close()
1499 r.add(['.hgignore', 'series'])
1499 r.add(['.hgignore', 'series'])
1500 commands.add(ui, r)
1500 commands.add(ui, r)
1501 return 0
1501 return 0
1502
1502
1503 def clone(ui, source, dest=None, **opts):
1503 def clone(ui, source, dest=None, **opts):
1504 '''clone main and patch repository at same time
1504 '''clone main and patch repository at same time
1505
1505
1506 If source is local, destination will have no patches applied. If
1506 If source is local, destination will have no patches applied. If
1507 source is remote, this command can not check if patches are
1507 source is remote, this command can not check if patches are
1508 applied in source, so cannot guarantee that patches are not
1508 applied in source, so cannot guarantee that patches are not
1509 applied in destination. If you clone remote repository, be sure
1509 applied in destination. If you clone remote repository, be sure
1510 before that it has no patches applied.
1510 before that it has no patches applied.
1511
1511
1512 Source patch repository is looked for in <src>/.hg/patches by
1512 Source patch repository is looked for in <src>/.hg/patches by
1513 default. Use -p <url> to change.
1513 default. Use -p <url> to change.
1514
1514
1515 The patch directory must be a nested mercurial repository, as
1515 The patch directory must be a nested mercurial repository, as
1516 would be created by qinit -c.
1516 would be created by qinit -c.
1517 '''
1517 '''
1518 def patchdir(repo):
1518 def patchdir(repo):
1519 url = repo.url()
1519 url = repo.url()
1520 if url.endswith('/'):
1520 if url.endswith('/'):
1521 url = url[:-1]
1521 url = url[:-1]
1522 return url + '/.hg/patches'
1522 return url + '/.hg/patches'
1523 cmdutil.setremoteconfig(ui, opts)
1523 cmdutil.setremoteconfig(ui, opts)
1524 if dest is None:
1524 if dest is None:
1525 dest = hg.defaultdest(source)
1525 dest = hg.defaultdest(source)
1526 sr = hg.repository(ui, ui.expandpath(source))
1526 sr = hg.repository(ui, ui.expandpath(source))
1527 patchespath = opts['patches'] or patchdir(sr)
1527 patchespath = opts['patches'] or patchdir(sr)
1528 try:
1528 try:
1529 pr = hg.repository(ui, patchespath)
1529 pr = hg.repository(ui, patchespath)
1530 except hg.RepoError:
1530 except hg.RepoError:
1531 raise util.Abort(_('versioned patch repository not found'
1531 raise util.Abort(_('versioned patch repository not found'
1532 ' (see qinit -c)'))
1532 ' (see qinit -c)'))
1533 qbase, destrev = None, None
1533 qbase, destrev = None, None
1534 if sr.local():
1534 if sr.local():
1535 if sr.mq.applied:
1535 if sr.mq.applied:
1536 qbase = revlog.bin(sr.mq.applied[0].rev)
1536 qbase = revlog.bin(sr.mq.applied[0].rev)
1537 if not hg.islocal(dest):
1537 if not hg.islocal(dest):
1538 heads = dict.fromkeys(sr.heads())
1538 heads = dict.fromkeys(sr.heads())
1539 for h in sr.heads(qbase):
1539 for h in sr.heads(qbase):
1540 del heads[h]
1540 del heads[h]
1541 destrev = heads.keys()
1541 destrev = heads.keys()
1542 destrev.append(sr.changelog.parents(qbase)[0])
1542 destrev.append(sr.changelog.parents(qbase)[0])
1543 ui.note(_('cloning main repo\n'))
1543 ui.note(_('cloning main repo\n'))
1544 sr, dr = hg.clone(ui, sr.url(), dest,
1544 sr, dr = hg.clone(ui, sr.url(), dest,
1545 pull=opts['pull'],
1545 pull=opts['pull'],
1546 rev=destrev,
1546 rev=destrev,
1547 update=False,
1547 update=False,
1548 stream=opts['uncompressed'])
1548 stream=opts['uncompressed'])
1549 ui.note(_('cloning patch repo\n'))
1549 ui.note(_('cloning patch repo\n'))
1550 spr, dpr = hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1550 spr, dpr = hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1551 pull=opts['pull'], update=not opts['noupdate'],
1551 pull=opts['pull'], update=not opts['noupdate'],
1552 stream=opts['uncompressed'])
1552 stream=opts['uncompressed'])
1553 if dr.local():
1553 if dr.local():
1554 if qbase:
1554 if qbase:
1555 ui.note(_('stripping applied patches from destination repo\n'))
1555 ui.note(_('stripping applied patches from destination repo\n'))
1556 dr.mq.strip(dr, qbase, update=False, backup=None)
1556 dr.mq.strip(dr, qbase, update=False, backup=None)
1557 if not opts['noupdate']:
1557 if not opts['noupdate']:
1558 ui.note(_('updating destination repo\n'))
1558 ui.note(_('updating destination repo\n'))
1559 hg.update(dr, dr.changelog.tip())
1559 hg.update(dr, dr.changelog.tip())
1560
1560
1561 def commit(ui, repo, *pats, **opts):
1561 def commit(ui, repo, *pats, **opts):
1562 """commit changes in the queue repository"""
1562 """commit changes in the queue repository"""
1563 q = repo.mq
1563 q = repo.mq
1564 r = q.qrepo()
1564 r = q.qrepo()
1565 if not r: raise util.Abort('no queue repository')
1565 if not r: raise util.Abort('no queue repository')
1566 commands.commit(r.ui, r, *pats, **opts)
1566 commands.commit(r.ui, r, *pats, **opts)
1567
1567
1568 def series(ui, repo, **opts):
1568 def series(ui, repo, **opts):
1569 """print the entire series file"""
1569 """print the entire series file"""
1570 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1570 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1571 return 0
1571 return 0
1572
1572
1573 def top(ui, repo, **opts):
1573 def top(ui, repo, **opts):
1574 """print the name of the current patch"""
1574 """print the name of the current patch"""
1575 q = repo.mq
1575 q = repo.mq
1576 t = q.applied and q.series_end(True) or 0
1576 t = q.applied and q.series_end(True) or 0
1577 if t:
1577 if t:
1578 return q.qseries(repo, start=t-1, length=1, status='A',
1578 return q.qseries(repo, start=t-1, length=1, status='A',
1579 summary=opts.get('summary'))
1579 summary=opts.get('summary'))
1580 else:
1580 else:
1581 ui.write("No patches applied\n")
1581 ui.write("No patches applied\n")
1582 return 1
1582 return 1
1583
1583
1584 def next(ui, repo, **opts):
1584 def next(ui, repo, **opts):
1585 """print the name of the next patch"""
1585 """print the name of the next patch"""
1586 q = repo.mq
1586 q = repo.mq
1587 end = q.series_end()
1587 end = q.series_end()
1588 if end == len(q.series):
1588 if end == len(q.series):
1589 ui.write("All patches applied\n")
1589 ui.write("All patches applied\n")
1590 return 1
1590 return 1
1591 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1591 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1592
1592
1593 def prev(ui, repo, **opts):
1593 def prev(ui, repo, **opts):
1594 """print the name of the previous patch"""
1594 """print the name of the previous patch"""
1595 q = repo.mq
1595 q = repo.mq
1596 l = len(q.applied)
1596 l = len(q.applied)
1597 if l == 1:
1597 if l == 1:
1598 ui.write("Only one patch applied\n")
1598 ui.write("Only one patch applied\n")
1599 return 1
1599 return 1
1600 if not l:
1600 if not l:
1601 ui.write("No patches applied\n")
1601 ui.write("No patches applied\n")
1602 return 1
1602 return 1
1603 return q.qseries(repo, start=l-2, length=1, status='A',
1603 return q.qseries(repo, start=l-2, length=1, status='A',
1604 summary=opts.get('summary'))
1604 summary=opts.get('summary'))
1605
1605
1606 def new(ui, repo, patch, *args, **opts):
1606 def new(ui, repo, patch, *args, **opts):
1607 """create a new patch
1607 """create a new patch
1608
1608
1609 qnew creates a new patch on top of the currently-applied patch
1609 qnew creates a new patch on top of the currently-applied patch
1610 (if any). It will refuse to run if there are any outstanding
1610 (if any). It will refuse to run if there are any outstanding
1611 changes unless -f is specified, in which case the patch will
1611 changes unless -f is specified, in which case the patch will
1612 be initialised with them. You may also use -I, -X, and/or a list of
1612 be initialised with them. You may also use -I, -X, and/or a list of
1613 files after the patch name to add only changes to matching files
1613 files after the patch name to add only changes to matching files
1614 to the new patch, leaving the rest as uncommitted modifications.
1614 to the new patch, leaving the rest as uncommitted modifications.
1615
1615
1616 -e, -m or -l set the patch header as well as the commit message.
1616 -e, -m or -l set the patch header as well as the commit message.
1617 If none is specified, the patch header is empty and the
1617 If none is specified, the patch header is empty and the
1618 commit message is '[mq]: PATCH'"""
1618 commit message is '[mq]: PATCH'"""
1619 q = repo.mq
1619 q = repo.mq
1620 message = cmdutil.logmessage(opts)
1620 message = cmdutil.logmessage(opts)
1621 if opts['edit']:
1621 if opts['edit']:
1622 message = ui.edit(message, ui.username())
1622 message = ui.edit(message, ui.username())
1623 opts['msg'] = message
1623 opts['msg'] = message
1624 q.new(repo, patch, *args, **opts)
1624 q.new(repo, patch, *args, **opts)
1625 q.save_dirty()
1625 q.save_dirty()
1626 return 0
1626 return 0
1627
1627
1628 def refresh(ui, repo, *pats, **opts):
1628 def refresh(ui, repo, *pats, **opts):
1629 """update the current patch
1629 """update the current patch
1630
1630
1631 If any file patterns are provided, the refreshed patch will contain only
1631 If any file patterns are provided, the refreshed patch will contain only
1632 the modifications that match those patterns; the remaining modifications
1632 the modifications that match those patterns; the remaining modifications
1633 will remain in the working directory.
1633 will remain in the working directory.
1634
1634
1635 hg add/remove/copy/rename work as usual, though you might want to use
1635 hg add/remove/copy/rename work as usual, though you might want to use
1636 git-style patches (--git or [diff] git=1) to track copies and renames.
1636 git-style patches (--git or [diff] git=1) to track copies and renames.
1637 """
1637 """
1638 q = repo.mq
1638 q = repo.mq
1639 message = cmdutil.logmessage(opts)
1639 message = cmdutil.logmessage(opts)
1640 if opts['edit']:
1640 if opts['edit']:
1641 if not q.applied:
1642 ui.write(_("No patches applied\n"))
1643 return 1
1641 if message:
1644 if message:
1642 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1645 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1643 patch = q.applied[-1].name
1646 patch = q.applied[-1].name
1644 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1647 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1645 message = ui.edit('\n'.join(message), user or ui.username())
1648 message = ui.edit('\n'.join(message), user or ui.username())
1646 ret = q.refresh(repo, pats, msg=message, **opts)
1649 ret = q.refresh(repo, pats, msg=message, **opts)
1647 q.save_dirty()
1650 q.save_dirty()
1648 return ret
1651 return ret
1649
1652
1650 def diff(ui, repo, *pats, **opts):
1653 def diff(ui, repo, *pats, **opts):
1651 """diff of the current patch"""
1654 """diff of the current patch"""
1652 repo.mq.diff(repo, pats, opts)
1655 repo.mq.diff(repo, pats, opts)
1653 return 0
1656 return 0
1654
1657
1655 def fold(ui, repo, *files, **opts):
1658 def fold(ui, repo, *files, **opts):
1656 """fold the named patches into the current patch
1659 """fold the named patches into the current patch
1657
1660
1658 Patches must not yet be applied. Each patch will be successively
1661 Patches must not yet be applied. Each patch will be successively
1659 applied to the current patch in the order given. If all the
1662 applied to the current patch in the order given. If all the
1660 patches apply successfully, the current patch will be refreshed
1663 patches apply successfully, the current patch will be refreshed
1661 with the new cumulative patch, and the folded patches will
1664 with the new cumulative patch, and the folded patches will
1662 be deleted. With -k/--keep, the folded patch files will not
1665 be deleted. With -k/--keep, the folded patch files will not
1663 be removed afterwards.
1666 be removed afterwards.
1664
1667
1665 The header for each folded patch will be concatenated with
1668 The header for each folded patch will be concatenated with
1666 the current patch header, separated by a line of '* * *'."""
1669 the current patch header, separated by a line of '* * *'."""
1667
1670
1668 q = repo.mq
1671 q = repo.mq
1669
1672
1670 if not files:
1673 if not files:
1671 raise util.Abort(_('qfold requires at least one patch name'))
1674 raise util.Abort(_('qfold requires at least one patch name'))
1672 if not q.check_toppatch(repo):
1675 if not q.check_toppatch(repo):
1673 raise util.Abort(_('No patches applied'))
1676 raise util.Abort(_('No patches applied'))
1674
1677
1675 message = cmdutil.logmessage(opts)
1678 message = cmdutil.logmessage(opts)
1676 if opts['edit']:
1679 if opts['edit']:
1677 if message:
1680 if message:
1678 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1681 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1679
1682
1680 parent = q.lookup('qtip')
1683 parent = q.lookup('qtip')
1681 patches = []
1684 patches = []
1682 messages = []
1685 messages = []
1683 for f in files:
1686 for f in files:
1684 p = q.lookup(f)
1687 p = q.lookup(f)
1685 if p in patches or p == parent:
1688 if p in patches or p == parent:
1686 ui.warn(_('Skipping already folded patch %s') % p)
1689 ui.warn(_('Skipping already folded patch %s') % p)
1687 if q.isapplied(p):
1690 if q.isapplied(p):
1688 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1691 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1689 patches.append(p)
1692 patches.append(p)
1690
1693
1691 for p in patches:
1694 for p in patches:
1692 if not message:
1695 if not message:
1693 messages.append(q.readheaders(p)[0])
1696 messages.append(q.readheaders(p)[0])
1694 pf = q.join(p)
1697 pf = q.join(p)
1695 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1698 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1696 if not patchsuccess:
1699 if not patchsuccess:
1697 raise util.Abort(_('Error folding patch %s') % p)
1700 raise util.Abort(_('Error folding patch %s') % p)
1698 patch.updatedir(ui, repo, files)
1701 patch.updatedir(ui, repo, files)
1699
1702
1700 if not message:
1703 if not message:
1701 message, comments, user = q.readheaders(parent)[0:3]
1704 message, comments, user = q.readheaders(parent)[0:3]
1702 for msg in messages:
1705 for msg in messages:
1703 message.append('* * *')
1706 message.append('* * *')
1704 message.extend(msg)
1707 message.extend(msg)
1705 message = '\n'.join(message)
1708 message = '\n'.join(message)
1706
1709
1707 if opts['edit']:
1710 if opts['edit']:
1708 message = ui.edit(message, user or ui.username())
1711 message = ui.edit(message, user or ui.username())
1709
1712
1710 q.refresh(repo, msg=message)
1713 q.refresh(repo, msg=message)
1711 q.delete(repo, patches, opts)
1714 q.delete(repo, patches, opts)
1712 q.save_dirty()
1715 q.save_dirty()
1713
1716
1714 def goto(ui, repo, patch, **opts):
1717 def goto(ui, repo, patch, **opts):
1715 '''push or pop patches until named patch is at top of stack'''
1718 '''push or pop patches until named patch is at top of stack'''
1716 q = repo.mq
1719 q = repo.mq
1717 patch = q.lookup(patch)
1720 patch = q.lookup(patch)
1718 if q.isapplied(patch):
1721 if q.isapplied(patch):
1719 ret = q.pop(repo, patch, force=opts['force'])
1722 ret = q.pop(repo, patch, force=opts['force'])
1720 else:
1723 else:
1721 ret = q.push(repo, patch, force=opts['force'])
1724 ret = q.push(repo, patch, force=opts['force'])
1722 q.save_dirty()
1725 q.save_dirty()
1723 return ret
1726 return ret
1724
1727
1725 def guard(ui, repo, *args, **opts):
1728 def guard(ui, repo, *args, **opts):
1726 '''set or print guards for a patch
1729 '''set or print guards for a patch
1727
1730
1728 Guards control whether a patch can be pushed. A patch with no
1731 Guards control whether a patch can be pushed. A patch with no
1729 guards is always pushed. A patch with a positive guard ("+foo") is
1732 guards is always pushed. A patch with a positive guard ("+foo") is
1730 pushed only if the qselect command has activated it. A patch with
1733 pushed only if the qselect command has activated it. A patch with
1731 a negative guard ("-foo") is never pushed if the qselect command
1734 a negative guard ("-foo") is never pushed if the qselect command
1732 has activated it.
1735 has activated it.
1733
1736
1734 With no arguments, print the currently active guards.
1737 With no arguments, print the currently active guards.
1735 With arguments, set guards for the named patch.
1738 With arguments, set guards for the named patch.
1736
1739
1737 To set a negative guard "-foo" on topmost patch ("--" is needed so
1740 To set a negative guard "-foo" on topmost patch ("--" is needed so
1738 hg will not interpret "-foo" as an option):
1741 hg will not interpret "-foo" as an option):
1739 hg qguard -- -foo
1742 hg qguard -- -foo
1740
1743
1741 To set guards on another patch:
1744 To set guards on another patch:
1742 hg qguard other.patch +2.6.17 -stable
1745 hg qguard other.patch +2.6.17 -stable
1743 '''
1746 '''
1744 def status(idx):
1747 def status(idx):
1745 guards = q.series_guards[idx] or ['unguarded']
1748 guards = q.series_guards[idx] or ['unguarded']
1746 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1749 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1747 q = repo.mq
1750 q = repo.mq
1748 patch = None
1751 patch = None
1749 args = list(args)
1752 args = list(args)
1750 if opts['list']:
1753 if opts['list']:
1751 if args or opts['none']:
1754 if args or opts['none']:
1752 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1755 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1753 for i in xrange(len(q.series)):
1756 for i in xrange(len(q.series)):
1754 status(i)
1757 status(i)
1755 return
1758 return
1756 if not args or args[0][0:1] in '-+':
1759 if not args or args[0][0:1] in '-+':
1757 if not q.applied:
1760 if not q.applied:
1758 raise util.Abort(_('no patches applied'))
1761 raise util.Abort(_('no patches applied'))
1759 patch = q.applied[-1].name
1762 patch = q.applied[-1].name
1760 if patch is None and args[0][0:1] not in '-+':
1763 if patch is None and args[0][0:1] not in '-+':
1761 patch = args.pop(0)
1764 patch = args.pop(0)
1762 if patch is None:
1765 if patch is None:
1763 raise util.Abort(_('no patch to work with'))
1766 raise util.Abort(_('no patch to work with'))
1764 if args or opts['none']:
1767 if args or opts['none']:
1765 idx = q.find_series(patch)
1768 idx = q.find_series(patch)
1766 if idx is None:
1769 if idx is None:
1767 raise util.Abort(_('no patch named %s') % patch)
1770 raise util.Abort(_('no patch named %s') % patch)
1768 q.set_guards(idx, args)
1771 q.set_guards(idx, args)
1769 q.save_dirty()
1772 q.save_dirty()
1770 else:
1773 else:
1771 status(q.series.index(q.lookup(patch)))
1774 status(q.series.index(q.lookup(patch)))
1772
1775
1773 def header(ui, repo, patch=None):
1776 def header(ui, repo, patch=None):
1774 """Print the header of the topmost or specified patch"""
1777 """Print the header of the topmost or specified patch"""
1775 q = repo.mq
1778 q = repo.mq
1776
1779
1777 if patch:
1780 if patch:
1778 patch = q.lookup(patch)
1781 patch = q.lookup(patch)
1779 else:
1782 else:
1780 if not q.applied:
1783 if not q.applied:
1781 ui.write('No patches applied\n')
1784 ui.write('No patches applied\n')
1782 return 1
1785 return 1
1783 patch = q.lookup('qtip')
1786 patch = q.lookup('qtip')
1784 message = repo.mq.readheaders(patch)[0]
1787 message = repo.mq.readheaders(patch)[0]
1785
1788
1786 ui.write('\n'.join(message) + '\n')
1789 ui.write('\n'.join(message) + '\n')
1787
1790
1788 def lastsavename(path):
1791 def lastsavename(path):
1789 (directory, base) = os.path.split(path)
1792 (directory, base) = os.path.split(path)
1790 names = os.listdir(directory)
1793 names = os.listdir(directory)
1791 namere = re.compile("%s.([0-9]+)" % base)
1794 namere = re.compile("%s.([0-9]+)" % base)
1792 maxindex = None
1795 maxindex = None
1793 maxname = None
1796 maxname = None
1794 for f in names:
1797 for f in names:
1795 m = namere.match(f)
1798 m = namere.match(f)
1796 if m:
1799 if m:
1797 index = int(m.group(1))
1800 index = int(m.group(1))
1798 if maxindex == None or index > maxindex:
1801 if maxindex == None or index > maxindex:
1799 maxindex = index
1802 maxindex = index
1800 maxname = f
1803 maxname = f
1801 if maxname:
1804 if maxname:
1802 return (os.path.join(directory, maxname), maxindex)
1805 return (os.path.join(directory, maxname), maxindex)
1803 return (None, None)
1806 return (None, None)
1804
1807
1805 def savename(path):
1808 def savename(path):
1806 (last, index) = lastsavename(path)
1809 (last, index) = lastsavename(path)
1807 if last is None:
1810 if last is None:
1808 index = 0
1811 index = 0
1809 newpath = path + ".%d" % (index + 1)
1812 newpath = path + ".%d" % (index + 1)
1810 return newpath
1813 return newpath
1811
1814
1812 def push(ui, repo, patch=None, **opts):
1815 def push(ui, repo, patch=None, **opts):
1813 """push the next patch onto the stack"""
1816 """push the next patch onto the stack"""
1814 q = repo.mq
1817 q = repo.mq
1815 mergeq = None
1818 mergeq = None
1816
1819
1817 if opts['all']:
1820 if opts['all']:
1818 if not q.series:
1821 if not q.series:
1819 ui.warn(_('no patches in series\n'))
1822 ui.warn(_('no patches in series\n'))
1820 return 0
1823 return 0
1821 patch = q.series[-1]
1824 patch = q.series[-1]
1822 if opts['merge']:
1825 if opts['merge']:
1823 if opts['name']:
1826 if opts['name']:
1824 newpath = opts['name']
1827 newpath = opts['name']
1825 else:
1828 else:
1826 newpath, i = lastsavename(q.path)
1829 newpath, i = lastsavename(q.path)
1827 if not newpath:
1830 if not newpath:
1828 ui.warn("no saved queues found, please use -n\n")
1831 ui.warn("no saved queues found, please use -n\n")
1829 return 1
1832 return 1
1830 mergeq = queue(ui, repo.join(""), newpath)
1833 mergeq = queue(ui, repo.join(""), newpath)
1831 ui.warn("merging with queue at: %s\n" % mergeq.path)
1834 ui.warn("merging with queue at: %s\n" % mergeq.path)
1832 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1835 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1833 mergeq=mergeq)
1836 mergeq=mergeq)
1834 return ret
1837 return ret
1835
1838
1836 def pop(ui, repo, patch=None, **opts):
1839 def pop(ui, repo, patch=None, **opts):
1837 """pop the current patch off the stack"""
1840 """pop the current patch off the stack"""
1838 localupdate = True
1841 localupdate = True
1839 if opts['name']:
1842 if opts['name']:
1840 q = queue(ui, repo.join(""), repo.join(opts['name']))
1843 q = queue(ui, repo.join(""), repo.join(opts['name']))
1841 ui.warn('using patch queue: %s\n' % q.path)
1844 ui.warn('using patch queue: %s\n' % q.path)
1842 localupdate = False
1845 localupdate = False
1843 else:
1846 else:
1844 q = repo.mq
1847 q = repo.mq
1845 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1848 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1846 all=opts['all'])
1849 all=opts['all'])
1847 q.save_dirty()
1850 q.save_dirty()
1848 return ret
1851 return ret
1849
1852
1850 def rename(ui, repo, patch, name=None, **opts):
1853 def rename(ui, repo, patch, name=None, **opts):
1851 """rename a patch
1854 """rename a patch
1852
1855
1853 With one argument, renames the current patch to PATCH1.
1856 With one argument, renames the current patch to PATCH1.
1854 With two arguments, renames PATCH1 to PATCH2."""
1857 With two arguments, renames PATCH1 to PATCH2."""
1855
1858
1856 q = repo.mq
1859 q = repo.mq
1857
1860
1858 if not name:
1861 if not name:
1859 name = patch
1862 name = patch
1860 patch = None
1863 patch = None
1861
1864
1862 if patch:
1865 if patch:
1863 patch = q.lookup(patch)
1866 patch = q.lookup(patch)
1864 else:
1867 else:
1865 if not q.applied:
1868 if not q.applied:
1866 ui.write(_('No patches applied\n'))
1869 ui.write(_('No patches applied\n'))
1867 return
1870 return
1868 patch = q.lookup('qtip')
1871 patch = q.lookup('qtip')
1869 absdest = q.join(name)
1872 absdest = q.join(name)
1870 if os.path.isdir(absdest):
1873 if os.path.isdir(absdest):
1871 name = normname(os.path.join(name, os.path.basename(patch)))
1874 name = normname(os.path.join(name, os.path.basename(patch)))
1872 absdest = q.join(name)
1875 absdest = q.join(name)
1873 if os.path.exists(absdest):
1876 if os.path.exists(absdest):
1874 raise util.Abort(_('%s already exists') % absdest)
1877 raise util.Abort(_('%s already exists') % absdest)
1875
1878
1876 if name in q.series:
1879 if name in q.series:
1877 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1880 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1878
1881
1879 if ui.verbose:
1882 if ui.verbose:
1880 ui.write('Renaming %s to %s\n' % (patch, name))
1883 ui.write('Renaming %s to %s\n' % (patch, name))
1881 i = q.find_series(patch)
1884 i = q.find_series(patch)
1882 guards = q.guard_re.findall(q.full_series[i])
1885 guards = q.guard_re.findall(q.full_series[i])
1883 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1886 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1884 q.parse_series()
1887 q.parse_series()
1885 q.series_dirty = 1
1888 q.series_dirty = 1
1886
1889
1887 info = q.isapplied(patch)
1890 info = q.isapplied(patch)
1888 if info:
1891 if info:
1889 q.applied[info[0]] = statusentry(info[1], name)
1892 q.applied[info[0]] = statusentry(info[1], name)
1890 q.applied_dirty = 1
1893 q.applied_dirty = 1
1891
1894
1892 util.rename(q.join(patch), absdest)
1895 util.rename(q.join(patch), absdest)
1893 r = q.qrepo()
1896 r = q.qrepo()
1894 if r:
1897 if r:
1895 wlock = r.wlock()
1898 wlock = r.wlock()
1896 try:
1899 try:
1897 if r.dirstate[name] == 'r':
1900 if r.dirstate[name] == 'r':
1898 r.undelete([name])
1901 r.undelete([name])
1899 r.copy(patch, name)
1902 r.copy(patch, name)
1900 r.remove([patch], False)
1903 r.remove([patch], False)
1901 finally:
1904 finally:
1902 del wlock
1905 del wlock
1903
1906
1904 q.save_dirty()
1907 q.save_dirty()
1905
1908
1906 def restore(ui, repo, rev, **opts):
1909 def restore(ui, repo, rev, **opts):
1907 """restore the queue state saved by a rev"""
1910 """restore the queue state saved by a rev"""
1908 rev = repo.lookup(rev)
1911 rev = repo.lookup(rev)
1909 q = repo.mq
1912 q = repo.mq
1910 q.restore(repo, rev, delete=opts['delete'],
1913 q.restore(repo, rev, delete=opts['delete'],
1911 qupdate=opts['update'])
1914 qupdate=opts['update'])
1912 q.save_dirty()
1915 q.save_dirty()
1913 return 0
1916 return 0
1914
1917
1915 def save(ui, repo, **opts):
1918 def save(ui, repo, **opts):
1916 """save current queue state"""
1919 """save current queue state"""
1917 q = repo.mq
1920 q = repo.mq
1918 message = cmdutil.logmessage(opts)
1921 message = cmdutil.logmessage(opts)
1919 ret = q.save(repo, msg=message)
1922 ret = q.save(repo, msg=message)
1920 if ret:
1923 if ret:
1921 return ret
1924 return ret
1922 q.save_dirty()
1925 q.save_dirty()
1923 if opts['copy']:
1926 if opts['copy']:
1924 path = q.path
1927 path = q.path
1925 if opts['name']:
1928 if opts['name']:
1926 newpath = os.path.join(q.basepath, opts['name'])
1929 newpath = os.path.join(q.basepath, opts['name'])
1927 if os.path.exists(newpath):
1930 if os.path.exists(newpath):
1928 if not os.path.isdir(newpath):
1931 if not os.path.isdir(newpath):
1929 raise util.Abort(_('destination %s exists and is not '
1932 raise util.Abort(_('destination %s exists and is not '
1930 'a directory') % newpath)
1933 'a directory') % newpath)
1931 if not opts['force']:
1934 if not opts['force']:
1932 raise util.Abort(_('destination %s exists, '
1935 raise util.Abort(_('destination %s exists, '
1933 'use -f to force') % newpath)
1936 'use -f to force') % newpath)
1934 else:
1937 else:
1935 newpath = savename(path)
1938 newpath = savename(path)
1936 ui.warn("copy %s to %s\n" % (path, newpath))
1939 ui.warn("copy %s to %s\n" % (path, newpath))
1937 util.copyfiles(path, newpath)
1940 util.copyfiles(path, newpath)
1938 if opts['empty']:
1941 if opts['empty']:
1939 try:
1942 try:
1940 os.unlink(q.join(q.status_path))
1943 os.unlink(q.join(q.status_path))
1941 except:
1944 except:
1942 pass
1945 pass
1943 return 0
1946 return 0
1944
1947
1945 def strip(ui, repo, rev, **opts):
1948 def strip(ui, repo, rev, **opts):
1946 """strip a revision and all later revs on the same branch"""
1949 """strip a revision and all later revs on the same branch"""
1947 rev = repo.lookup(rev)
1950 rev = repo.lookup(rev)
1948 backup = 'all'
1951 backup = 'all'
1949 if opts['backup']:
1952 if opts['backup']:
1950 backup = 'strip'
1953 backup = 'strip'
1951 elif opts['nobackup']:
1954 elif opts['nobackup']:
1952 backup = 'none'
1955 backup = 'none'
1953 update = repo.dirstate.parents()[0] != revlog.nullid
1956 update = repo.dirstate.parents()[0] != revlog.nullid
1954 repo.mq.strip(repo, rev, backup=backup, update=update)
1957 repo.mq.strip(repo, rev, backup=backup, update=update)
1955 return 0
1958 return 0
1956
1959
1957 def select(ui, repo, *args, **opts):
1960 def select(ui, repo, *args, **opts):
1958 '''set or print guarded patches to push
1961 '''set or print guarded patches to push
1959
1962
1960 Use the qguard command to set or print guards on patch, then use
1963 Use the qguard command to set or print guards on patch, then use
1961 qselect to tell mq which guards to use. A patch will be pushed if it
1964 qselect to tell mq which guards to use. A patch will be pushed if it
1962 has no guards or any positive guards match the currently selected guard,
1965 has no guards or any positive guards match the currently selected guard,
1963 but will not be pushed if any negative guards match the current guard.
1966 but will not be pushed if any negative guards match the current guard.
1964 For example:
1967 For example:
1965
1968
1966 qguard foo.patch -stable (negative guard)
1969 qguard foo.patch -stable (negative guard)
1967 qguard bar.patch +stable (positive guard)
1970 qguard bar.patch +stable (positive guard)
1968 qselect stable
1971 qselect stable
1969
1972
1970 This activates the "stable" guard. mq will skip foo.patch (because
1973 This activates the "stable" guard. mq will skip foo.patch (because
1971 it has a negative match) but push bar.patch (because it
1974 it has a negative match) but push bar.patch (because it
1972 has a positive match).
1975 has a positive match).
1973
1976
1974 With no arguments, prints the currently active guards.
1977 With no arguments, prints the currently active guards.
1975 With one argument, sets the active guard.
1978 With one argument, sets the active guard.
1976
1979
1977 Use -n/--none to deactivate guards (no other arguments needed).
1980 Use -n/--none to deactivate guards (no other arguments needed).
1978 When no guards are active, patches with positive guards are skipped
1981 When no guards are active, patches with positive guards are skipped
1979 and patches with negative guards are pushed.
1982 and patches with negative guards are pushed.
1980
1983
1981 qselect can change the guards on applied patches. It does not pop
1984 qselect can change the guards on applied patches. It does not pop
1982 guarded patches by default. Use --pop to pop back to the last applied
1985 guarded patches by default. Use --pop to pop back to the last applied
1983 patch that is not guarded. Use --reapply (which implies --pop) to push
1986 patch that is not guarded. Use --reapply (which implies --pop) to push
1984 back to the current patch afterwards, but skip guarded patches.
1987 back to the current patch afterwards, but skip guarded patches.
1985
1988
1986 Use -s/--series to print a list of all guards in the series file (no
1989 Use -s/--series to print a list of all guards in the series file (no
1987 other arguments needed). Use -v for more information.'''
1990 other arguments needed). Use -v for more information.'''
1988
1991
1989 q = repo.mq
1992 q = repo.mq
1990 guards = q.active()
1993 guards = q.active()
1991 if args or opts['none']:
1994 if args or opts['none']:
1992 old_unapplied = q.unapplied(repo)
1995 old_unapplied = q.unapplied(repo)
1993 old_guarded = [i for i in xrange(len(q.applied)) if
1996 old_guarded = [i for i in xrange(len(q.applied)) if
1994 not q.pushable(i)[0]]
1997 not q.pushable(i)[0]]
1995 q.set_active(args)
1998 q.set_active(args)
1996 q.save_dirty()
1999 q.save_dirty()
1997 if not args:
2000 if not args:
1998 ui.status(_('guards deactivated\n'))
2001 ui.status(_('guards deactivated\n'))
1999 if not opts['pop'] and not opts['reapply']:
2002 if not opts['pop'] and not opts['reapply']:
2000 unapplied = q.unapplied(repo)
2003 unapplied = q.unapplied(repo)
2001 guarded = [i for i in xrange(len(q.applied))
2004 guarded = [i for i in xrange(len(q.applied))
2002 if not q.pushable(i)[0]]
2005 if not q.pushable(i)[0]]
2003 if len(unapplied) != len(old_unapplied):
2006 if len(unapplied) != len(old_unapplied):
2004 ui.status(_('number of unguarded, unapplied patches has '
2007 ui.status(_('number of unguarded, unapplied patches has '
2005 'changed from %d to %d\n') %
2008 'changed from %d to %d\n') %
2006 (len(old_unapplied), len(unapplied)))
2009 (len(old_unapplied), len(unapplied)))
2007 if len(guarded) != len(old_guarded):
2010 if len(guarded) != len(old_guarded):
2008 ui.status(_('number of guarded, applied patches has changed '
2011 ui.status(_('number of guarded, applied patches has changed '
2009 'from %d to %d\n') %
2012 'from %d to %d\n') %
2010 (len(old_guarded), len(guarded)))
2013 (len(old_guarded), len(guarded)))
2011 elif opts['series']:
2014 elif opts['series']:
2012 guards = {}
2015 guards = {}
2013 noguards = 0
2016 noguards = 0
2014 for gs in q.series_guards:
2017 for gs in q.series_guards:
2015 if not gs:
2018 if not gs:
2016 noguards += 1
2019 noguards += 1
2017 for g in gs:
2020 for g in gs:
2018 guards.setdefault(g, 0)
2021 guards.setdefault(g, 0)
2019 guards[g] += 1
2022 guards[g] += 1
2020 if ui.verbose:
2023 if ui.verbose:
2021 guards['NONE'] = noguards
2024 guards['NONE'] = noguards
2022 guards = guards.items()
2025 guards = guards.items()
2023 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2026 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2024 if guards:
2027 if guards:
2025 ui.note(_('guards in series file:\n'))
2028 ui.note(_('guards in series file:\n'))
2026 for guard, count in guards:
2029 for guard, count in guards:
2027 ui.note('%2d ' % count)
2030 ui.note('%2d ' % count)
2028 ui.write(guard, '\n')
2031 ui.write(guard, '\n')
2029 else:
2032 else:
2030 ui.note(_('no guards in series file\n'))
2033 ui.note(_('no guards in series file\n'))
2031 else:
2034 else:
2032 if guards:
2035 if guards:
2033 ui.note(_('active guards:\n'))
2036 ui.note(_('active guards:\n'))
2034 for g in guards:
2037 for g in guards:
2035 ui.write(g, '\n')
2038 ui.write(g, '\n')
2036 else:
2039 else:
2037 ui.write(_('no active guards\n'))
2040 ui.write(_('no active guards\n'))
2038 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2041 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2039 popped = False
2042 popped = False
2040 if opts['pop'] or opts['reapply']:
2043 if opts['pop'] or opts['reapply']:
2041 for i in xrange(len(q.applied)):
2044 for i in xrange(len(q.applied)):
2042 pushable, reason = q.pushable(i)
2045 pushable, reason = q.pushable(i)
2043 if not pushable:
2046 if not pushable:
2044 ui.status(_('popping guarded patches\n'))
2047 ui.status(_('popping guarded patches\n'))
2045 popped = True
2048 popped = True
2046 if i == 0:
2049 if i == 0:
2047 q.pop(repo, all=True)
2050 q.pop(repo, all=True)
2048 else:
2051 else:
2049 q.pop(repo, i-1)
2052 q.pop(repo, i-1)
2050 break
2053 break
2051 if popped:
2054 if popped:
2052 try:
2055 try:
2053 if reapply:
2056 if reapply:
2054 ui.status(_('reapplying unguarded patches\n'))
2057 ui.status(_('reapplying unguarded patches\n'))
2055 q.push(repo, reapply)
2058 q.push(repo, reapply)
2056 finally:
2059 finally:
2057 q.save_dirty()
2060 q.save_dirty()
2058
2061
2059 def reposetup(ui, repo):
2062 def reposetup(ui, repo):
2060 class mqrepo(repo.__class__):
2063 class mqrepo(repo.__class__):
2061 def abort_if_wdir_patched(self, errmsg, force=False):
2064 def abort_if_wdir_patched(self, errmsg, force=False):
2062 if self.mq.applied and not force:
2065 if self.mq.applied and not force:
2063 parent = revlog.hex(self.dirstate.parents()[0])
2066 parent = revlog.hex(self.dirstate.parents()[0])
2064 if parent in [s.rev for s in self.mq.applied]:
2067 if parent in [s.rev for s in self.mq.applied]:
2065 raise util.Abort(errmsg)
2068 raise util.Abort(errmsg)
2066
2069
2067 def commit(self, *args, **opts):
2070 def commit(self, *args, **opts):
2068 if len(args) >= 6:
2071 if len(args) >= 6:
2069 force = args[5]
2072 force = args[5]
2070 else:
2073 else:
2071 force = opts.get('force')
2074 force = opts.get('force')
2072 self.abort_if_wdir_patched(
2075 self.abort_if_wdir_patched(
2073 _('cannot commit over an applied mq patch'),
2076 _('cannot commit over an applied mq patch'),
2074 force)
2077 force)
2075
2078
2076 return super(mqrepo, self).commit(*args, **opts)
2079 return super(mqrepo, self).commit(*args, **opts)
2077
2080
2078 def push(self, remote, force=False, revs=None):
2081 def push(self, remote, force=False, revs=None):
2079 if self.mq.applied and not force and not revs:
2082 if self.mq.applied and not force and not revs:
2080 raise util.Abort(_('source has mq patches applied'))
2083 raise util.Abort(_('source has mq patches applied'))
2081 return super(mqrepo, self).push(remote, force, revs)
2084 return super(mqrepo, self).push(remote, force, revs)
2082
2085
2083 def tags(self):
2086 def tags(self):
2084 if self.tagscache:
2087 if self.tagscache:
2085 return self.tagscache
2088 return self.tagscache
2086
2089
2087 tagscache = super(mqrepo, self).tags()
2090 tagscache = super(mqrepo, self).tags()
2088
2091
2089 q = self.mq
2092 q = self.mq
2090 if not q.applied:
2093 if not q.applied:
2091 return tagscache
2094 return tagscache
2092
2095
2093 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2096 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2094 mqtags.append((mqtags[-1][0], 'qtip'))
2097 mqtags.append((mqtags[-1][0], 'qtip'))
2095 mqtags.append((mqtags[0][0], 'qbase'))
2098 mqtags.append((mqtags[0][0], 'qbase'))
2096 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2099 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2097 for patch in mqtags:
2100 for patch in mqtags:
2098 if patch[1] in tagscache:
2101 if patch[1] in tagscache:
2099 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2102 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2100 else:
2103 else:
2101 tagscache[patch[1]] = patch[0]
2104 tagscache[patch[1]] = patch[0]
2102
2105
2103 return tagscache
2106 return tagscache
2104
2107
2105 def _branchtags(self):
2108 def _branchtags(self):
2106 q = self.mq
2109 q = self.mq
2107 if not q.applied:
2110 if not q.applied:
2108 return super(mqrepo, self)._branchtags()
2111 return super(mqrepo, self)._branchtags()
2109
2112
2110 self.branchcache = {} # avoid recursion in changectx
2113 self.branchcache = {} # avoid recursion in changectx
2111 cl = self.changelog
2114 cl = self.changelog
2112 partial, last, lrev = self._readbranchcache()
2115 partial, last, lrev = self._readbranchcache()
2113
2116
2114 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2117 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2115 start = lrev + 1
2118 start = lrev + 1
2116 if start < qbase:
2119 if start < qbase:
2117 # update the cache (excluding the patches) and save it
2120 # update the cache (excluding the patches) and save it
2118 self._updatebranchcache(partial, lrev+1, qbase)
2121 self._updatebranchcache(partial, lrev+1, qbase)
2119 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2122 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2120 start = qbase
2123 start = qbase
2121 # if start = qbase, the cache is as updated as it should be.
2124 # if start = qbase, the cache is as updated as it should be.
2122 # if start > qbase, the cache includes (part of) the patches.
2125 # if start > qbase, the cache includes (part of) the patches.
2123 # we might as well use it, but we won't save it.
2126 # we might as well use it, but we won't save it.
2124
2127
2125 # update the cache up to the tip
2128 # update the cache up to the tip
2126 self._updatebranchcache(partial, start, cl.count())
2129 self._updatebranchcache(partial, start, cl.count())
2127
2130
2128 return partial
2131 return partial
2129
2132
2130 if repo.local():
2133 if repo.local():
2131 repo.__class__ = mqrepo
2134 repo.__class__ = mqrepo
2132 repo.mq = queue(ui, repo.join(""))
2135 repo.mq = queue(ui, repo.join(""))
2133
2136
2134 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2137 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2135
2138
2136 cmdtable = {
2139 cmdtable = {
2137 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2140 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2138 "qclone":
2141 "qclone":
2139 (clone,
2142 (clone,
2140 [('', 'pull', None, _('use pull protocol to copy metadata')),
2143 [('', 'pull', None, _('use pull protocol to copy metadata')),
2141 ('U', 'noupdate', None, _('do not update the new working directories')),
2144 ('U', 'noupdate', None, _('do not update the new working directories')),
2142 ('', 'uncompressed', None,
2145 ('', 'uncompressed', None,
2143 _('use uncompressed transfer (fast over LAN)')),
2146 _('use uncompressed transfer (fast over LAN)')),
2144 ('p', 'patches', '', _('location of source patch repo')),
2147 ('p', 'patches', '', _('location of source patch repo')),
2145 ] + commands.remoteopts,
2148 ] + commands.remoteopts,
2146 _('hg qclone [OPTION]... SOURCE [DEST]')),
2149 _('hg qclone [OPTION]... SOURCE [DEST]')),
2147 "qcommit|qci":
2150 "qcommit|qci":
2148 (commit,
2151 (commit,
2149 commands.table["^commit|ci"][1],
2152 commands.table["^commit|ci"][1],
2150 _('hg qcommit [OPTION]... [FILE]...')),
2153 _('hg qcommit [OPTION]... [FILE]...')),
2151 "^qdiff":
2154 "^qdiff":
2152 (diff,
2155 (diff,
2153 [('g', 'git', None, _('use git extended diff format')),
2156 [('g', 'git', None, _('use git extended diff format')),
2154 ] + commands.walkopts,
2157 ] + commands.walkopts,
2155 _('hg qdiff [-I] [-X] [-g] [FILE]...')),
2158 _('hg qdiff [-I] [-X] [-g] [FILE]...')),
2156 "qdelete|qremove|qrm":
2159 "qdelete|qremove|qrm":
2157 (delete,
2160 (delete,
2158 [('k', 'keep', None, _('keep patch file')),
2161 [('k', 'keep', None, _('keep patch file')),
2159 ('r', 'rev', [], _('stop managing a revision'))],
2162 ('r', 'rev', [], _('stop managing a revision'))],
2160 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2163 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2161 'qfold':
2164 'qfold':
2162 (fold,
2165 (fold,
2163 [('e', 'edit', None, _('edit patch header')),
2166 [('e', 'edit', None, _('edit patch header')),
2164 ('k', 'keep', None, _('keep folded patch files')),
2167 ('k', 'keep', None, _('keep folded patch files')),
2165 ] + commands.commitopts,
2168 ] + commands.commitopts,
2166 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2169 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2167 'qgoto':
2170 'qgoto':
2168 (goto,
2171 (goto,
2169 [('f', 'force', None, _('overwrite any local changes'))],
2172 [('f', 'force', None, _('overwrite any local changes'))],
2170 _('hg qgoto [OPTION]... PATCH')),
2173 _('hg qgoto [OPTION]... PATCH')),
2171 'qguard':
2174 'qguard':
2172 (guard,
2175 (guard,
2173 [('l', 'list', None, _('list all patches and guards')),
2176 [('l', 'list', None, _('list all patches and guards')),
2174 ('n', 'none', None, _('drop all guards'))],
2177 ('n', 'none', None, _('drop all guards'))],
2175 _('hg qguard [-l] [-n] [PATCH] [+GUARD]... [-GUARD]...')),
2178 _('hg qguard [-l] [-n] [PATCH] [+GUARD]... [-GUARD]...')),
2176 'qheader': (header, [], _('hg qheader [PATCH]')),
2179 'qheader': (header, [], _('hg qheader [PATCH]')),
2177 "^qimport":
2180 "^qimport":
2178 (qimport,
2181 (qimport,
2179 [('e', 'existing', None, 'import file in patch dir'),
2182 [('e', 'existing', None, 'import file in patch dir'),
2180 ('n', 'name', '', 'patch file name'),
2183 ('n', 'name', '', 'patch file name'),
2181 ('f', 'force', None, 'overwrite existing files'),
2184 ('f', 'force', None, 'overwrite existing files'),
2182 ('r', 'rev', [], 'place existing revisions under mq control'),
2185 ('r', 'rev', [], 'place existing revisions under mq control'),
2183 ('g', 'git', None, _('use git extended diff format'))],
2186 ('g', 'git', None, _('use git extended diff format'))],
2184 _('hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...')),
2187 _('hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...')),
2185 "^qinit":
2188 "^qinit":
2186 (init,
2189 (init,
2187 [('c', 'create-repo', None, 'create queue repository')],
2190 [('c', 'create-repo', None, 'create queue repository')],
2188 _('hg qinit [-c]')),
2191 _('hg qinit [-c]')),
2189 "qnew":
2192 "qnew":
2190 (new,
2193 (new,
2191 [('e', 'edit', None, _('edit commit message')),
2194 [('e', 'edit', None, _('edit commit message')),
2192 ('f', 'force', None, _('import uncommitted changes into patch')),
2195 ('f', 'force', None, _('import uncommitted changes into patch')),
2193 ('g', 'git', None, _('use git extended diff format')),
2196 ('g', 'git', None, _('use git extended diff format')),
2194 ] + commands.walkopts + commands.commitopts,
2197 ] + commands.walkopts + commands.commitopts,
2195 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2198 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2196 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2199 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2197 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2200 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2198 "^qpop":
2201 "^qpop":
2199 (pop,
2202 (pop,
2200 [('a', 'all', None, _('pop all patches')),
2203 [('a', 'all', None, _('pop all patches')),
2201 ('n', 'name', '', _('queue name to pop')),
2204 ('n', 'name', '', _('queue name to pop')),
2202 ('f', 'force', None, _('forget any local changes'))],
2205 ('f', 'force', None, _('forget any local changes'))],
2203 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2206 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2204 "^qpush":
2207 "^qpush":
2205 (push,
2208 (push,
2206 [('f', 'force', None, _('apply if the patch has rejects')),
2209 [('f', 'force', None, _('apply if the patch has rejects')),
2207 ('l', 'list', None, _('list patch name in commit text')),
2210 ('l', 'list', None, _('list patch name in commit text')),
2208 ('a', 'all', None, _('apply all patches')),
2211 ('a', 'all', None, _('apply all patches')),
2209 ('m', 'merge', None, _('merge from another queue')),
2212 ('m', 'merge', None, _('merge from another queue')),
2210 ('n', 'name', '', _('merge queue name'))],
2213 ('n', 'name', '', _('merge queue name'))],
2211 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2214 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2212 "^qrefresh":
2215 "^qrefresh":
2213 (refresh,
2216 (refresh,
2214 [('e', 'edit', None, _('edit commit message')),
2217 [('e', 'edit', None, _('edit commit message')),
2215 ('g', 'git', None, _('use git extended diff format')),
2218 ('g', 'git', None, _('use git extended diff format')),
2216 ('s', 'short', None, _('refresh only files already in the patch')),
2219 ('s', 'short', None, _('refresh only files already in the patch')),
2217 ] + commands.walkopts + commands.commitopts,
2220 ] + commands.walkopts + commands.commitopts,
2218 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2221 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2219 'qrename|qmv':
2222 'qrename|qmv':
2220 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2223 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2221 "qrestore":
2224 "qrestore":
2222 (restore,
2225 (restore,
2223 [('d', 'delete', None, _('delete save entry')),
2226 [('d', 'delete', None, _('delete save entry')),
2224 ('u', 'update', None, _('update queue working dir'))],
2227 ('u', 'update', None, _('update queue working dir'))],
2225 _('hg qrestore [-d] [-u] REV')),
2228 _('hg qrestore [-d] [-u] REV')),
2226 "qsave":
2229 "qsave":
2227 (save,
2230 (save,
2228 [('c', 'copy', None, _('copy patch directory')),
2231 [('c', 'copy', None, _('copy patch directory')),
2229 ('n', 'name', '', _('copy directory name')),
2232 ('n', 'name', '', _('copy directory name')),
2230 ('e', 'empty', None, _('clear queue status file')),
2233 ('e', 'empty', None, _('clear queue status file')),
2231 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2234 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2232 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2235 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2233 "qselect":
2236 "qselect":
2234 (select,
2237 (select,
2235 [('n', 'none', None, _('disable all guards')),
2238 [('n', 'none', None, _('disable all guards')),
2236 ('s', 'series', None, _('list all guards in series file')),
2239 ('s', 'series', None, _('list all guards in series file')),
2237 ('', 'pop', None, _('pop to before first guarded applied patch')),
2240 ('', 'pop', None, _('pop to before first guarded applied patch')),
2238 ('', 'reapply', None, _('pop, then reapply patches'))],
2241 ('', 'reapply', None, _('pop, then reapply patches'))],
2239 _('hg qselect [OPTION]... [GUARD]...')),
2242 _('hg qselect [OPTION]... [GUARD]...')),
2240 "qseries":
2243 "qseries":
2241 (series,
2244 (series,
2242 [('m', 'missing', None, _('print patches not in series')),
2245 [('m', 'missing', None, _('print patches not in series')),
2243 ] + seriesopts,
2246 ] + seriesopts,
2244 _('hg qseries [-ms]')),
2247 _('hg qseries [-ms]')),
2245 "^strip":
2248 "^strip":
2246 (strip,
2249 (strip,
2247 [('f', 'force', None, _('force multi-head removal')),
2250 [('f', 'force', None, _('force multi-head removal')),
2248 ('b', 'backup', None, _('bundle unrelated changesets')),
2251 ('b', 'backup', None, _('bundle unrelated changesets')),
2249 ('n', 'nobackup', None, _('no backups'))],
2252 ('n', 'nobackup', None, _('no backups'))],
2250 _('hg strip [-f] [-b] [-n] REV')),
2253 _('hg strip [-f] [-b] [-n] REV')),
2251 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2254 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2252 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2255 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2253 }
2256 }
@@ -1,3156 +1,3163 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from i18n import _
9 from i18n import _
10 import os, re, sys, urllib
10 import os, re, sys, urllib
11 import hg, util, revlog, bundlerepo, extensions
11 import hg, util, revlog, bundlerepo, extensions
12 import difflib, patch, time, help, mdiff, tempfile
12 import difflib, patch, time, help, mdiff, tempfile
13 import errno, version, socket
13 import errno, version, socket
14 import archival, changegroup, cmdutil, hgweb.server, sshserver
14 import archival, changegroup, cmdutil, hgweb.server, sshserver
15
15
16 # Commands start here, listed alphabetically
16 # Commands start here, listed alphabetically
17
17
18 def add(ui, repo, *pats, **opts):
18 def add(ui, repo, *pats, **opts):
19 """add the specified files on the next commit
19 """add the specified files on the next commit
20
20
21 Schedule files to be version controlled and added to the repository.
21 Schedule files to be version controlled and added to the repository.
22
22
23 The files will be added to the repository at the next commit. To
23 The files will be added to the repository at the next commit. To
24 undo an add before that, see hg revert.
24 undo an add before that, see hg revert.
25
25
26 If no names are given, add all files in the repository.
26 If no names are given, add all files in the repository.
27 """
27 """
28
28
29 names = []
29 names = []
30 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
30 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
31 if exact:
31 if exact:
32 if ui.verbose:
32 if ui.verbose:
33 ui.status(_('adding %s\n') % rel)
33 ui.status(_('adding %s\n') % rel)
34 names.append(abs)
34 names.append(abs)
35 elif abs not in repo.dirstate:
35 elif abs not in repo.dirstate:
36 ui.status(_('adding %s\n') % rel)
36 ui.status(_('adding %s\n') % rel)
37 names.append(abs)
37 names.append(abs)
38 if not opts.get('dry_run'):
38 if not opts.get('dry_run'):
39 repo.add(names)
39 repo.add(names)
40
40
41 def addremove(ui, repo, *pats, **opts):
41 def addremove(ui, repo, *pats, **opts):
42 """add all new files, delete all missing files
42 """add all new files, delete all missing files
43
43
44 Add all new files and remove all missing files from the repository.
44 Add all new files and remove all missing files from the repository.
45
45
46 New files are ignored if they match any of the patterns in .hgignore. As
46 New files are ignored if they match any of the patterns in .hgignore. As
47 with add, these changes take effect at the next commit.
47 with add, these changes take effect at the next commit.
48
48
49 Use the -s option to detect renamed files. With a parameter > 0,
49 Use the -s option to detect renamed files. With a parameter > 0,
50 this compares every removed file with every added file and records
50 this compares every removed file with every added file and records
51 those similar enough as renames. This option takes a percentage
51 those similar enough as renames. This option takes a percentage
52 between 0 (disabled) and 100 (files must be identical) as its
52 between 0 (disabled) and 100 (files must be identical) as its
53 parameter. Detecting renamed files this way can be expensive.
53 parameter. Detecting renamed files this way can be expensive.
54 """
54 """
55 try:
55 try:
56 sim = float(opts.get('similarity') or 0)
56 sim = float(opts.get('similarity') or 0)
57 except ValueError:
57 except ValueError:
58 raise util.Abort(_('similarity must be a number'))
58 raise util.Abort(_('similarity must be a number'))
59 if sim < 0 or sim > 100:
59 if sim < 0 or sim > 100:
60 raise util.Abort(_('similarity must be between 0 and 100'))
60 raise util.Abort(_('similarity must be between 0 and 100'))
61 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
61 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
62
62
63 def annotate(ui, repo, *pats, **opts):
63 def annotate(ui, repo, *pats, **opts):
64 """show changeset information per file line
64 """show changeset information per file line
65
65
66 List changes in files, showing the revision id responsible for each line
66 List changes in files, showing the revision id responsible for each line
67
67
68 This command is useful to discover who did a change or when a change took
68 This command is useful to discover who did a change or when a change took
69 place.
69 place.
70
70
71 Without the -a option, annotate will avoid processing files it
71 Without the -a option, annotate will avoid processing files it
72 detects as binary. With -a, annotate will generate an annotation
72 detects as binary. With -a, annotate will generate an annotation
73 anyway, probably with undesirable results.
73 anyway, probably with undesirable results.
74 """
74 """
75 getdate = util.cachefunc(lambda x: util.datestr(x[0].date()))
75 getdate = util.cachefunc(lambda x: util.datestr(x[0].date()))
76
76
77 if not pats:
77 if not pats:
78 raise util.Abort(_('at least one file name or pattern required'))
78 raise util.Abort(_('at least one file name or pattern required'))
79
79
80 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
80 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
81 ('number', lambda x: str(x[0].rev())),
81 ('number', lambda x: str(x[0].rev())),
82 ('changeset', lambda x: short(x[0].node())),
82 ('changeset', lambda x: short(x[0].node())),
83 ('date', getdate),
83 ('date', getdate),
84 ('follow', lambda x: x[0].path()),
84 ('follow', lambda x: x[0].path()),
85 ]
85 ]
86
86
87 if (not opts['user'] and not opts['changeset'] and not opts['date']
87 if (not opts['user'] and not opts['changeset'] and not opts['date']
88 and not opts['follow']):
88 and not opts['follow']):
89 opts['number'] = 1
89 opts['number'] = 1
90
90
91 linenumber = opts.get('line_number') is not None
91 linenumber = opts.get('line_number') is not None
92 if (linenumber and (not opts['changeset']) and (not opts['number'])):
92 if (linenumber and (not opts['changeset']) and (not opts['number'])):
93 raise util.Abort(_('at least one of -n/-c is required for -l'))
93 raise util.Abort(_('at least one of -n/-c is required for -l'))
94
94
95 funcmap = [func for op, func in opmap if opts.get(op)]
95 funcmap = [func for op, func in opmap if opts.get(op)]
96 if linenumber:
96 if linenumber:
97 lastfunc = funcmap[-1]
97 lastfunc = funcmap[-1]
98 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
98 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
99
99
100 ctx = repo.changectx(opts['rev'])
100 ctx = repo.changectx(opts['rev'])
101
101
102 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
102 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
103 node=ctx.node()):
103 node=ctx.node()):
104 fctx = ctx.filectx(abs)
104 fctx = ctx.filectx(abs)
105 if not opts['text'] and util.binary(fctx.data()):
105 if not opts['text'] and util.binary(fctx.data()):
106 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
106 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
107 continue
107 continue
108
108
109 lines = fctx.annotate(follow=opts.get('follow'),
109 lines = fctx.annotate(follow=opts.get('follow'),
110 linenumber=linenumber)
110 linenumber=linenumber)
111 pieces = []
111 pieces = []
112
112
113 for f in funcmap:
113 for f in funcmap:
114 l = [f(n) for n, dummy in lines]
114 l = [f(n) for n, dummy in lines]
115 if l:
115 if l:
116 m = max(map(len, l))
116 m = max(map(len, l))
117 pieces.append(["%*s" % (m, x) for x in l])
117 pieces.append(["%*s" % (m, x) for x in l])
118
118
119 if pieces:
119 if pieces:
120 for p, l in zip(zip(*pieces), lines):
120 for p, l in zip(zip(*pieces), lines):
121 ui.write("%s: %s" % (" ".join(p), l[1]))
121 ui.write("%s: %s" % (" ".join(p), l[1]))
122
122
123 def archive(ui, repo, dest, **opts):
123 def archive(ui, repo, dest, **opts):
124 '''create unversioned archive of a repository revision
124 '''create unversioned archive of a repository revision
125
125
126 By default, the revision used is the parent of the working
126 By default, the revision used is the parent of the working
127 directory; use "-r" to specify a different revision.
127 directory; use "-r" to specify a different revision.
128
128
129 To specify the type of archive to create, use "-t". Valid
129 To specify the type of archive to create, use "-t". Valid
130 types are:
130 types are:
131
131
132 "files" (default): a directory full of files
132 "files" (default): a directory full of files
133 "tar": tar archive, uncompressed
133 "tar": tar archive, uncompressed
134 "tbz2": tar archive, compressed using bzip2
134 "tbz2": tar archive, compressed using bzip2
135 "tgz": tar archive, compressed using gzip
135 "tgz": tar archive, compressed using gzip
136 "uzip": zip archive, uncompressed
136 "uzip": zip archive, uncompressed
137 "zip": zip archive, compressed using deflate
137 "zip": zip archive, compressed using deflate
138
138
139 The exact name of the destination archive or directory is given
139 The exact name of the destination archive or directory is given
140 using a format string; see "hg help export" for details.
140 using a format string; see "hg help export" for details.
141
141
142 Each member added to an archive file has a directory prefix
142 Each member added to an archive file has a directory prefix
143 prepended. Use "-p" to specify a format string for the prefix.
143 prepended. Use "-p" to specify a format string for the prefix.
144 The default is the basename of the archive, with suffixes removed.
144 The default is the basename of the archive, with suffixes removed.
145 '''
145 '''
146
146
147 ctx = repo.changectx(opts['rev'])
147 ctx = repo.changectx(opts['rev'])
148 if not ctx:
148 if not ctx:
149 raise util.Abort(_('repository has no revisions'))
149 raise util.Abort(_('repository has no revisions'))
150 node = ctx.node()
150 node = ctx.node()
151 dest = cmdutil.make_filename(repo, dest, node)
151 dest = cmdutil.make_filename(repo, dest, node)
152 if os.path.realpath(dest) == repo.root:
152 if os.path.realpath(dest) == repo.root:
153 raise util.Abort(_('repository root cannot be destination'))
153 raise util.Abort(_('repository root cannot be destination'))
154 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
154 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
155 kind = opts.get('type') or 'files'
155 kind = opts.get('type') or 'files'
156 prefix = opts['prefix']
156 prefix = opts['prefix']
157 if dest == '-':
157 if dest == '-':
158 if kind == 'files':
158 if kind == 'files':
159 raise util.Abort(_('cannot archive plain files to stdout'))
159 raise util.Abort(_('cannot archive plain files to stdout'))
160 dest = sys.stdout
160 dest = sys.stdout
161 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
161 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
162 prefix = cmdutil.make_filename(repo, prefix, node)
162 prefix = cmdutil.make_filename(repo, prefix, node)
163 archival.archive(repo, dest, node, kind, not opts['no_decode'],
163 archival.archive(repo, dest, node, kind, not opts['no_decode'],
164 matchfn, prefix)
164 matchfn, prefix)
165
165
166 def backout(ui, repo, node=None, rev=None, **opts):
166 def backout(ui, repo, node=None, rev=None, **opts):
167 '''reverse effect of earlier changeset
167 '''reverse effect of earlier changeset
168
168
169 Commit the backed out changes as a new changeset. The new
169 Commit the backed out changes as a new changeset. The new
170 changeset is a child of the backed out changeset.
170 changeset is a child of the backed out changeset.
171
171
172 If you back out a changeset other than the tip, a new head is
172 If you back out a changeset other than the tip, a new head is
173 created. This head is the parent of the working directory. If
173 created. This head is the parent of the working directory. If
174 you back out an old changeset, your working directory will appear
174 you back out an old changeset, your working directory will appear
175 old after the backout. You should merge the backout changeset
175 old after the backout. You should merge the backout changeset
176 with another head.
176 with another head.
177
177
178 The --merge option remembers the parent of the working directory
178 The --merge option remembers the parent of the working directory
179 before starting the backout, then merges the new head with that
179 before starting the backout, then merges the new head with that
180 changeset afterwards. This saves you from doing the merge by
180 changeset afterwards. This saves you from doing the merge by
181 hand. The result of this merge is not committed, as for a normal
181 hand. The result of this merge is not committed, as for a normal
182 merge.'''
182 merge.'''
183 if rev and node:
183 if rev and node:
184 raise util.Abort(_("please specify just one revision"))
184 raise util.Abort(_("please specify just one revision"))
185
185
186 if not rev:
186 if not rev:
187 rev = node
187 rev = node
188
188
189 if not rev:
189 if not rev:
190 raise util.Abort(_("please specify a revision to backout"))
190 raise util.Abort(_("please specify a revision to backout"))
191
191
192 cmdutil.bail_if_changed(repo)
192 cmdutil.bail_if_changed(repo)
193 op1, op2 = repo.dirstate.parents()
193 op1, op2 = repo.dirstate.parents()
194 if op2 != nullid:
194 if op2 != nullid:
195 raise util.Abort(_('outstanding uncommitted merge'))
195 raise util.Abort(_('outstanding uncommitted merge'))
196 node = repo.lookup(rev)
196 node = repo.lookup(rev)
197 p1, p2 = repo.changelog.parents(node)
197 p1, p2 = repo.changelog.parents(node)
198 if p1 == nullid:
198 if p1 == nullid:
199 raise util.Abort(_('cannot back out a change with no parents'))
199 raise util.Abort(_('cannot back out a change with no parents'))
200 if p2 != nullid:
200 if p2 != nullid:
201 if not opts['parent']:
201 if not opts['parent']:
202 raise util.Abort(_('cannot back out a merge changeset without '
202 raise util.Abort(_('cannot back out a merge changeset without '
203 '--parent'))
203 '--parent'))
204 p = repo.lookup(opts['parent'])
204 p = repo.lookup(opts['parent'])
205 if p not in (p1, p2):
205 if p not in (p1, p2):
206 raise util.Abort(_('%s is not a parent of %s') %
206 raise util.Abort(_('%s is not a parent of %s') %
207 (short(p), short(node)))
207 (short(p), short(node)))
208 parent = p
208 parent = p
209 else:
209 else:
210 if opts['parent']:
210 if opts['parent']:
211 raise util.Abort(_('cannot use --parent on non-merge changeset'))
211 raise util.Abort(_('cannot use --parent on non-merge changeset'))
212 parent = p1
212 parent = p1
213 hg.clean(repo, node, show_stats=False)
213 hg.clean(repo, node, show_stats=False)
214 revert_opts = opts.copy()
214 revert_opts = opts.copy()
215 revert_opts['date'] = None
215 revert_opts['date'] = None
216 revert_opts['all'] = True
216 revert_opts['all'] = True
217 revert_opts['rev'] = hex(parent)
217 revert_opts['rev'] = hex(parent)
218 revert(ui, repo, **revert_opts)
218 revert(ui, repo, **revert_opts)
219 commit_opts = opts.copy()
219 commit_opts = opts.copy()
220 commit_opts['addremove'] = False
220 commit_opts['addremove'] = False
221 if not commit_opts['message'] and not commit_opts['logfile']:
221 if not commit_opts['message'] and not commit_opts['logfile']:
222 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
222 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
223 commit_opts['force_editor'] = True
223 commit_opts['force_editor'] = True
224 commit(ui, repo, **commit_opts)
224 commit(ui, repo, **commit_opts)
225 def nice(node):
225 def nice(node):
226 return '%d:%s' % (repo.changelog.rev(node), short(node))
226 return '%d:%s' % (repo.changelog.rev(node), short(node))
227 ui.status(_('changeset %s backs out changeset %s\n') %
227 ui.status(_('changeset %s backs out changeset %s\n') %
228 (nice(repo.changelog.tip()), nice(node)))
228 (nice(repo.changelog.tip()), nice(node)))
229 if op1 != node:
229 if op1 != node:
230 if opts['merge']:
230 if opts['merge']:
231 ui.status(_('merging with changeset %s\n') % nice(op1))
231 ui.status(_('merging with changeset %s\n') % nice(op1))
232 hg.merge(repo, hex(op1))
232 hg.merge(repo, hex(op1))
233 else:
233 else:
234 ui.status(_('the backout changeset is a new head - '
234 ui.status(_('the backout changeset is a new head - '
235 'do not forget to merge\n'))
235 'do not forget to merge\n'))
236 ui.status(_('(use "backout --merge" '
236 ui.status(_('(use "backout --merge" '
237 'if you want to auto-merge)\n'))
237 'if you want to auto-merge)\n'))
238
238
239 def branch(ui, repo, label=None, **opts):
239 def branch(ui, repo, label=None, **opts):
240 """set or show the current branch name
240 """set or show the current branch name
241
241
242 With no argument, show the current branch name. With one argument,
242 With no argument, show the current branch name. With one argument,
243 set the working directory branch name (the branch does not exist in
243 set the working directory branch name (the branch does not exist in
244 the repository until the next commit).
244 the repository until the next commit).
245
245
246 Unless --force is specified, branch will not let you set a
246 Unless --force is specified, branch will not let you set a
247 branch name that shadows an existing branch.
247 branch name that shadows an existing branch.
248 """
248 """
249
249
250 if label:
250 if label:
251 if not opts.get('force') and label in repo.branchtags():
251 if not opts.get('force') and label in repo.branchtags():
252 if label not in [p.branch() for p in repo.workingctx().parents()]:
252 if label not in [p.branch() for p in repo.workingctx().parents()]:
253 raise util.Abort(_('a branch of the same name already exists'
253 raise util.Abort(_('a branch of the same name already exists'
254 ' (use --force to override)'))
254 ' (use --force to override)'))
255 repo.dirstate.setbranch(util.fromlocal(label))
255 repo.dirstate.setbranch(util.fromlocal(label))
256 ui.status(_('marked working directory as branch %s\n') % label)
256 ui.status(_('marked working directory as branch %s\n') % label)
257 else:
257 else:
258 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
258 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
259
259
260 def branches(ui, repo, active=False):
260 def branches(ui, repo, active=False):
261 """list repository named branches
261 """list repository named branches
262
262
263 List the repository's named branches, indicating which ones are
263 List the repository's named branches, indicating which ones are
264 inactive. If active is specified, only show active branches.
264 inactive. If active is specified, only show active branches.
265
265
266 A branch is considered active if it contains unmerged heads.
266 A branch is considered active if it contains unmerged heads.
267 """
267 """
268 b = repo.branchtags()
268 b = repo.branchtags()
269 heads = dict.fromkeys(repo.heads(), 1)
269 heads = dict.fromkeys(repo.heads(), 1)
270 l = [((n in heads), repo.changelog.rev(n), n, t) for t, n in b.items()]
270 l = [((n in heads), repo.changelog.rev(n), n, t) for t, n in b.items()]
271 l.sort()
271 l.sort()
272 l.reverse()
272 l.reverse()
273 for ishead, r, n, t in l:
273 for ishead, r, n, t in l:
274 if active and not ishead:
274 if active and not ishead:
275 # If we're only displaying active branches, abort the loop on
275 # If we're only displaying active branches, abort the loop on
276 # encountering the first inactive head
276 # encountering the first inactive head
277 break
277 break
278 else:
278 else:
279 hexfunc = ui.debugflag and hex or short
279 hexfunc = ui.debugflag and hex or short
280 if ui.quiet:
280 if ui.quiet:
281 ui.write("%s\n" % t)
281 ui.write("%s\n" % t)
282 else:
282 else:
283 spaces = " " * (30 - util.locallen(t))
283 spaces = " " * (30 - util.locallen(t))
284 # The code only gets here if inactive branches are being
284 # The code only gets here if inactive branches are being
285 # displayed or the branch is active.
285 # displayed or the branch is active.
286 isinactive = ((not ishead) and " (inactive)") or ''
286 isinactive = ((not ishead) and " (inactive)") or ''
287 ui.write("%s%s %s:%s%s\n" % (t, spaces, r, hexfunc(n), isinactive))
287 ui.write("%s%s %s:%s%s\n" % (t, spaces, r, hexfunc(n), isinactive))
288
288
289 def bundle(ui, repo, fname, dest=None, **opts):
289 def bundle(ui, repo, fname, dest=None, **opts):
290 """create a changegroup file
290 """create a changegroup file
291
291
292 Generate a compressed changegroup file collecting changesets not
292 Generate a compressed changegroup file collecting changesets not
293 found in the other repository.
293 found in the other repository.
294
294
295 If no destination repository is specified the destination is assumed
295 If no destination repository is specified the destination is assumed
296 to have all the nodes specified by one or more --base parameters.
296 to have all the nodes specified by one or more --base parameters.
297
297
298 The bundle file can then be transferred using conventional means and
298 The bundle file can then be transferred using conventional means and
299 applied to another repository with the unbundle or pull command.
299 applied to another repository with the unbundle or pull command.
300 This is useful when direct push and pull are not available or when
300 This is useful when direct push and pull are not available or when
301 exporting an entire repository is undesirable.
301 exporting an entire repository is undesirable.
302
302
303 Applying bundles preserves all changeset contents including
303 Applying bundles preserves all changeset contents including
304 permissions, copy/rename information, and revision history.
304 permissions, copy/rename information, and revision history.
305 """
305 """
306 revs = opts.get('rev') or None
306 revs = opts.get('rev') or None
307 if revs:
307 if revs:
308 revs = [repo.lookup(rev) for rev in revs]
308 revs = [repo.lookup(rev) for rev in revs]
309 base = opts.get('base')
309 base = opts.get('base')
310 if base:
310 if base:
311 if dest:
311 if dest:
312 raise util.Abort(_("--base is incompatible with specifiying "
312 raise util.Abort(_("--base is incompatible with specifiying "
313 "a destination"))
313 "a destination"))
314 base = [repo.lookup(rev) for rev in base]
314 base = [repo.lookup(rev) for rev in base]
315 # create the right base
315 # create the right base
316 # XXX: nodesbetween / changegroup* should be "fixed" instead
316 # XXX: nodesbetween / changegroup* should be "fixed" instead
317 o = []
317 o = []
318 has = {nullid: None}
318 has = {nullid: None}
319 for n in base:
319 for n in base:
320 has.update(repo.changelog.reachable(n))
320 has.update(repo.changelog.reachable(n))
321 if revs:
321 if revs:
322 visit = list(revs)
322 visit = list(revs)
323 else:
323 else:
324 visit = repo.changelog.heads()
324 visit = repo.changelog.heads()
325 seen = {}
325 seen = {}
326 while visit:
326 while visit:
327 n = visit.pop(0)
327 n = visit.pop(0)
328 parents = [p for p in repo.changelog.parents(n) if p not in has]
328 parents = [p for p in repo.changelog.parents(n) if p not in has]
329 if len(parents) == 0:
329 if len(parents) == 0:
330 o.insert(0, n)
330 o.insert(0, n)
331 else:
331 else:
332 for p in parents:
332 for p in parents:
333 if p not in seen:
333 if p not in seen:
334 seen[p] = 1
334 seen[p] = 1
335 visit.append(p)
335 visit.append(p)
336 else:
336 else:
337 cmdutil.setremoteconfig(ui, opts)
337 cmdutil.setremoteconfig(ui, opts)
338 dest, revs, checkout = hg.parseurl(
338 dest, revs, checkout = hg.parseurl(
339 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
339 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
340 other = hg.repository(ui, dest)
340 other = hg.repository(ui, dest)
341 o = repo.findoutgoing(other, force=opts['force'])
341 o = repo.findoutgoing(other, force=opts['force'])
342
342
343 if revs:
343 if revs:
344 cg = repo.changegroupsubset(o, revs, 'bundle')
344 cg = repo.changegroupsubset(o, revs, 'bundle')
345 else:
345 else:
346 cg = repo.changegroup(o, 'bundle')
346 cg = repo.changegroup(o, 'bundle')
347 changegroup.writebundle(cg, fname, "HG10BZ")
347 changegroup.writebundle(cg, fname, "HG10BZ")
348
348
349 def cat(ui, repo, file1, *pats, **opts):
349 def cat(ui, repo, file1, *pats, **opts):
350 """output the current or given revision of files
350 """output the current or given revision of files
351
351
352 Print the specified files as they were at the given revision.
352 Print the specified files as they were at the given revision.
353 If no revision is given, the parent of the working directory is used,
353 If no revision is given, the parent of the working directory is used,
354 or tip if no revision is checked out.
354 or tip if no revision is checked out.
355
355
356 Output may be to a file, in which case the name of the file is
356 Output may be to a file, in which case the name of the file is
357 given using a format string. The formatting rules are the same as
357 given using a format string. The formatting rules are the same as
358 for the export command, with the following additions:
358 for the export command, with the following additions:
359
359
360 %s basename of file being printed
360 %s basename of file being printed
361 %d dirname of file being printed, or '.' if in repo root
361 %d dirname of file being printed, or '.' if in repo root
362 %p root-relative path name of file being printed
362 %p root-relative path name of file being printed
363 """
363 """
364 ctx = repo.changectx(opts['rev'])
364 ctx = repo.changectx(opts['rev'])
365 err = 1
365 err = 1
366 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
366 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
367 ctx.node()):
367 ctx.node()):
368 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
368 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
369 fp.write(ctx.filectx(abs).data())
369 fp.write(ctx.filectx(abs).data())
370 err = 0
370 err = 0
371 return err
371 return err
372
372
373 def clone(ui, source, dest=None, **opts):
373 def clone(ui, source, dest=None, **opts):
374 """make a copy of an existing repository
374 """make a copy of an existing repository
375
375
376 Create a copy of an existing repository in a new directory.
376 Create a copy of an existing repository in a new directory.
377
377
378 If no destination directory name is specified, it defaults to the
378 If no destination directory name is specified, it defaults to the
379 basename of the source.
379 basename of the source.
380
380
381 The location of the source is added to the new repository's
381 The location of the source is added to the new repository's
382 .hg/hgrc file, as the default to be used for future pulls.
382 .hg/hgrc file, as the default to be used for future pulls.
383
383
384 For efficiency, hardlinks are used for cloning whenever the source
384 For efficiency, hardlinks are used for cloning whenever the source
385 and destination are on the same filesystem (note this applies only
385 and destination are on the same filesystem (note this applies only
386 to the repository data, not to the checked out files). Some
386 to the repository data, not to the checked out files). Some
387 filesystems, such as AFS, implement hardlinking incorrectly, but
387 filesystems, such as AFS, implement hardlinking incorrectly, but
388 do not report errors. In these cases, use the --pull option to
388 do not report errors. In these cases, use the --pull option to
389 avoid hardlinking.
389 avoid hardlinking.
390
390
391 You can safely clone repositories and checked out files using full
391 You can safely clone repositories and checked out files using full
392 hardlinks with
392 hardlinks with
393
393
394 $ cp -al REPO REPOCLONE
394 $ cp -al REPO REPOCLONE
395
395
396 which is the fastest way to clone. However, the operation is not
396 which is the fastest way to clone. However, the operation is not
397 atomic (making sure REPO is not modified during the operation is
397 atomic (making sure REPO is not modified during the operation is
398 up to you) and you have to make sure your editor breaks hardlinks
398 up to you) and you have to make sure your editor breaks hardlinks
399 (Emacs and most Linux Kernel tools do so).
399 (Emacs and most Linux Kernel tools do so).
400
400
401 If you use the -r option to clone up to a specific revision, no
401 If you use the -r option to clone up to a specific revision, no
402 subsequent revisions will be present in the cloned repository.
402 subsequent revisions will be present in the cloned repository.
403 This option implies --pull, even on local repositories.
403 This option implies --pull, even on local repositories.
404
404
405 See pull for valid source format details.
405 See pull for valid source format details.
406
406
407 It is possible to specify an ssh:// URL as the destination, but no
407 It is possible to specify an ssh:// URL as the destination, but no
408 .hg/hgrc and working directory will be created on the remote side.
408 .hg/hgrc and working directory will be created on the remote side.
409 Look at the help text for the pull command for important details
409 Look at the help text for the pull command for important details
410 about ssh:// URLs.
410 about ssh:// URLs.
411 """
411 """
412 cmdutil.setremoteconfig(ui, opts)
412 cmdutil.setremoteconfig(ui, opts)
413 hg.clone(ui, source, dest,
413 hg.clone(ui, source, dest,
414 pull=opts['pull'],
414 pull=opts['pull'],
415 stream=opts['uncompressed'],
415 stream=opts['uncompressed'],
416 rev=opts['rev'],
416 rev=opts['rev'],
417 update=not opts['noupdate'])
417 update=not opts['noupdate'])
418
418
419 def commit(ui, repo, *pats, **opts):
419 def commit(ui, repo, *pats, **opts):
420 """commit the specified files or all outstanding changes
420 """commit the specified files or all outstanding changes
421
421
422 Commit changes to the given files into the repository.
422 Commit changes to the given files into the repository.
423
423
424 If a list of files is omitted, all changes reported by "hg status"
424 If a list of files is omitted, all changes reported by "hg status"
425 will be committed.
425 will be committed.
426
426
427 If no commit message is specified, the editor configured in your hgrc
427 If no commit message is specified, the editor configured in your hgrc
428 or in the EDITOR environment variable is started to enter a message.
428 or in the EDITOR environment variable is started to enter a message.
429 """
429 """
430 def commitfunc(ui, repo, files, message, match, opts):
430 def commitfunc(ui, repo, files, message, match, opts):
431 return repo.commit(files, message, opts['user'], opts['date'], match,
431 return repo.commit(files, message, opts['user'], opts['date'], match,
432 force_editor=opts.get('force_editor'))
432 force_editor=opts.get('force_editor'))
433 cmdutil.commit(ui, repo, commitfunc, pats, opts)
433 cmdutil.commit(ui, repo, commitfunc, pats, opts)
434
434
435 def docopy(ui, repo, pats, opts):
435 def docopy(ui, repo, pats, opts):
436 # called with the repo lock held
436 # called with the repo lock held
437 #
437 #
438 # hgsep => pathname that uses "/" to separate directories
438 # hgsep => pathname that uses "/" to separate directories
439 # ossep => pathname that uses os.sep to separate directories
439 # ossep => pathname that uses os.sep to separate directories
440 cwd = repo.getcwd()
440 cwd = repo.getcwd()
441 errors = 0
441 errors = 0
442 copied = []
442 copied = []
443 targets = {}
443 targets = {}
444
444
445 # abs: hgsep
445 # abs: hgsep
446 # rel: ossep
446 # rel: ossep
447 # return: hgsep
447 # return: hgsep
448 def okaytocopy(abs, rel, exact):
448 def okaytocopy(abs, rel, exact):
449 reasons = {'?': _('is not managed'),
449 reasons = {'?': _('is not managed'),
450 'r': _('has been marked for remove')}
450 'r': _('has been marked for remove')}
451 state = repo.dirstate[abs]
451 state = repo.dirstate[abs]
452 reason = reasons.get(state)
452 reason = reasons.get(state)
453 if reason:
453 if reason:
454 if exact:
454 if exact:
455 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
455 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
456 else:
456 else:
457 if state == 'a':
457 if state == 'a':
458 origsrc = repo.dirstate.copied(abs)
458 origsrc = repo.dirstate.copied(abs)
459 if origsrc is not None:
459 if origsrc is not None:
460 return origsrc
460 return origsrc
461 return abs
461 return abs
462
462
463 # origsrc: hgsep
463 # origsrc: hgsep
464 # abssrc: hgsep
464 # abssrc: hgsep
465 # relsrc: ossep
465 # relsrc: ossep
466 # otarget: ossep
466 # otarget: ossep
467 def copy(origsrc, abssrc, relsrc, otarget, exact):
467 def copy(origsrc, abssrc, relsrc, otarget, exact):
468 abstarget = util.canonpath(repo.root, cwd, otarget)
468 abstarget = util.canonpath(repo.root, cwd, otarget)
469 reltarget = repo.pathto(abstarget, cwd)
469 reltarget = repo.pathto(abstarget, cwd)
470 prevsrc = targets.get(abstarget)
470 prevsrc = targets.get(abstarget)
471 src = repo.wjoin(abssrc)
471 src = repo.wjoin(abssrc)
472 target = repo.wjoin(abstarget)
472 target = repo.wjoin(abstarget)
473 if prevsrc is not None:
473 if prevsrc is not None:
474 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
474 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
475 (reltarget, repo.pathto(abssrc, cwd),
475 (reltarget, repo.pathto(abssrc, cwd),
476 repo.pathto(prevsrc, cwd)))
476 repo.pathto(prevsrc, cwd)))
477 return
477 return
478 if (not opts['after'] and os.path.exists(target) or
478 if (not opts['after'] and os.path.exists(target) or
479 opts['after'] and repo.dirstate[abstarget] in 'mn'):
479 opts['after'] and repo.dirstate[abstarget] in 'mn'):
480 if not opts['force']:
480 if not opts['force']:
481 ui.warn(_('%s: not overwriting - file exists\n') %
481 ui.warn(_('%s: not overwriting - file exists\n') %
482 reltarget)
482 reltarget)
483 return
483 return
484 if not opts['after'] and not opts.get('dry_run'):
484 if not opts['after'] and not opts.get('dry_run'):
485 os.unlink(target)
485 os.unlink(target)
486 if opts['after']:
486 if opts['after']:
487 if not os.path.exists(target):
487 if not os.path.exists(target):
488 return
488 return
489 else:
489 else:
490 targetdir = os.path.dirname(target) or '.'
490 targetdir = os.path.dirname(target) or '.'
491 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
491 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
492 os.makedirs(targetdir)
492 os.makedirs(targetdir)
493 try:
493 try:
494 restore = repo.dirstate[abstarget] == 'r'
494 restore = repo.dirstate[abstarget] == 'r'
495 if restore and not opts.get('dry_run'):
495 if restore and not opts.get('dry_run'):
496 repo.undelete([abstarget])
496 repo.undelete([abstarget])
497 try:
497 try:
498 if not opts.get('dry_run'):
498 if not opts.get('dry_run'):
499 util.copyfile(src, target)
499 util.copyfile(src, target)
500 restore = False
500 restore = False
501 finally:
501 finally:
502 if restore:
502 if restore:
503 repo.remove([abstarget])
503 repo.remove([abstarget])
504 except IOError, inst:
504 except IOError, inst:
505 if inst.errno == errno.ENOENT:
505 if inst.errno == errno.ENOENT:
506 ui.warn(_('%s: deleted in working copy\n') % relsrc)
506 ui.warn(_('%s: deleted in working copy\n') % relsrc)
507 else:
507 else:
508 ui.warn(_('%s: cannot copy - %s\n') %
508 ui.warn(_('%s: cannot copy - %s\n') %
509 (relsrc, inst.strerror))
509 (relsrc, inst.strerror))
510 errors += 1
510 errors += 1
511 return
511 return
512 if ui.verbose or not exact:
512 if ui.verbose or not exact:
513 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
513 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
514 targets[abstarget] = abssrc
514 targets[abstarget] = abssrc
515 if abstarget != origsrc:
515 if abstarget != origsrc:
516 if repo.dirstate[origsrc] == 'a':
516 if repo.dirstate[origsrc] == 'a':
517 if not ui.quiet:
517 if not ui.quiet:
518 ui.warn(_("%s has not been committed yet, so no copy "
518 ui.warn(_("%s has not been committed yet, so no copy "
519 "data will be stored for %s.\n")
519 "data will be stored for %s.\n")
520 % (repo.pathto(origsrc, cwd), reltarget))
520 % (repo.pathto(origsrc, cwd), reltarget))
521 if abstarget not in repo.dirstate and not opts.get('dry_run'):
521 if abstarget not in repo.dirstate and not opts.get('dry_run'):
522 repo.add([abstarget])
522 repo.add([abstarget])
523 elif not opts.get('dry_run'):
523 elif not opts.get('dry_run'):
524 repo.copy(origsrc, abstarget)
524 repo.copy(origsrc, abstarget)
525 copied.append((abssrc, relsrc, exact))
525 copied.append((abssrc, relsrc, exact))
526
526
527 # pat: ossep
527 # pat: ossep
528 # dest ossep
528 # dest ossep
529 # srcs: list of (hgsep, hgsep, ossep, bool)
529 # srcs: list of (hgsep, hgsep, ossep, bool)
530 # return: function that takes hgsep and returns ossep
530 # return: function that takes hgsep and returns ossep
531 def targetpathfn(pat, dest, srcs):
531 def targetpathfn(pat, dest, srcs):
532 if os.path.isdir(pat):
532 if os.path.isdir(pat):
533 abspfx = util.canonpath(repo.root, cwd, pat)
533 abspfx = util.canonpath(repo.root, cwd, pat)
534 abspfx = util.localpath(abspfx)
534 abspfx = util.localpath(abspfx)
535 if destdirexists:
535 if destdirexists:
536 striplen = len(os.path.split(abspfx)[0])
536 striplen = len(os.path.split(abspfx)[0])
537 else:
537 else:
538 striplen = len(abspfx)
538 striplen = len(abspfx)
539 if striplen:
539 if striplen:
540 striplen += len(os.sep)
540 striplen += len(os.sep)
541 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
541 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
542 elif destdirexists:
542 elif destdirexists:
543 res = lambda p: os.path.join(dest,
543 res = lambda p: os.path.join(dest,
544 os.path.basename(util.localpath(p)))
544 os.path.basename(util.localpath(p)))
545 else:
545 else:
546 res = lambda p: dest
546 res = lambda p: dest
547 return res
547 return res
548
548
549 # pat: ossep
549 # pat: ossep
550 # dest ossep
550 # dest ossep
551 # srcs: list of (hgsep, hgsep, ossep, bool)
551 # srcs: list of (hgsep, hgsep, ossep, bool)
552 # return: function that takes hgsep and returns ossep
552 # return: function that takes hgsep and returns ossep
553 def targetpathafterfn(pat, dest, srcs):
553 def targetpathafterfn(pat, dest, srcs):
554 if util.patkind(pat, None)[0]:
554 if util.patkind(pat, None)[0]:
555 # a mercurial pattern
555 # a mercurial pattern
556 res = lambda p: os.path.join(dest,
556 res = lambda p: os.path.join(dest,
557 os.path.basename(util.localpath(p)))
557 os.path.basename(util.localpath(p)))
558 else:
558 else:
559 abspfx = util.canonpath(repo.root, cwd, pat)
559 abspfx = util.canonpath(repo.root, cwd, pat)
560 if len(abspfx) < len(srcs[0][0]):
560 if len(abspfx) < len(srcs[0][0]):
561 # A directory. Either the target path contains the last
561 # A directory. Either the target path contains the last
562 # component of the source path or it does not.
562 # component of the source path or it does not.
563 def evalpath(striplen):
563 def evalpath(striplen):
564 score = 0
564 score = 0
565 for s in srcs:
565 for s in srcs:
566 t = os.path.join(dest, util.localpath(s[0])[striplen:])
566 t = os.path.join(dest, util.localpath(s[0])[striplen:])
567 if os.path.exists(t):
567 if os.path.exists(t):
568 score += 1
568 score += 1
569 return score
569 return score
570
570
571 abspfx = util.localpath(abspfx)
571 abspfx = util.localpath(abspfx)
572 striplen = len(abspfx)
572 striplen = len(abspfx)
573 if striplen:
573 if striplen:
574 striplen += len(os.sep)
574 striplen += len(os.sep)
575 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
575 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
576 score = evalpath(striplen)
576 score = evalpath(striplen)
577 striplen1 = len(os.path.split(abspfx)[0])
577 striplen1 = len(os.path.split(abspfx)[0])
578 if striplen1:
578 if striplen1:
579 striplen1 += len(os.sep)
579 striplen1 += len(os.sep)
580 if evalpath(striplen1) > score:
580 if evalpath(striplen1) > score:
581 striplen = striplen1
581 striplen = striplen1
582 res = lambda p: os.path.join(dest,
582 res = lambda p: os.path.join(dest,
583 util.localpath(p)[striplen:])
583 util.localpath(p)[striplen:])
584 else:
584 else:
585 # a file
585 # a file
586 if destdirexists:
586 if destdirexists:
587 res = lambda p: os.path.join(dest,
587 res = lambda p: os.path.join(dest,
588 os.path.basename(util.localpath(p)))
588 os.path.basename(util.localpath(p)))
589 else:
589 else:
590 res = lambda p: dest
590 res = lambda p: dest
591 return res
591 return res
592
592
593
593
594 pats = util.expand_glob(pats)
594 pats = util.expand_glob(pats)
595 if not pats:
595 if not pats:
596 raise util.Abort(_('no source or destination specified'))
596 raise util.Abort(_('no source or destination specified'))
597 if len(pats) == 1:
597 if len(pats) == 1:
598 raise util.Abort(_('no destination specified'))
598 raise util.Abort(_('no destination specified'))
599 dest = pats.pop()
599 dest = pats.pop()
600 destdirexists = os.path.isdir(dest)
600 destdirexists = os.path.isdir(dest)
601 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
601 if not destdirexists:
602 raise util.Abort(_('with multiple sources, destination must be an '
602 if len(pats) > 1 or util.patkind(pats[0], None)[0]:
603 'existing directory'))
603 raise util.Abort(_('with multiple sources, destination must be an '
604 'existing directory'))
605 if dest.endswith(os.sep) or os.altsep and dest.endswith(os.altsep):
606 raise util.Abort(_('destination %s is not a directory') % dest)
604 if opts['after']:
607 if opts['after']:
605 tfn = targetpathafterfn
608 tfn = targetpathafterfn
606 else:
609 else:
607 tfn = targetpathfn
610 tfn = targetpathfn
608 copylist = []
611 copylist = []
609 for pat in pats:
612 for pat in pats:
610 srcs = []
613 srcs = []
611 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts,
614 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts,
612 globbed=True):
615 globbed=True):
613 origsrc = okaytocopy(abssrc, relsrc, exact)
616 origsrc = okaytocopy(abssrc, relsrc, exact)
614 if origsrc:
617 if origsrc:
615 srcs.append((origsrc, abssrc, relsrc, exact))
618 srcs.append((origsrc, abssrc, relsrc, exact))
616 if not srcs:
619 if not srcs:
617 continue
620 continue
618 copylist.append((tfn(pat, dest, srcs), srcs))
621 copylist.append((tfn(pat, dest, srcs), srcs))
619 if not copylist:
622 if not copylist:
620 raise util.Abort(_('no files to copy'))
623 raise util.Abort(_('no files to copy'))
621
624
622 for targetpath, srcs in copylist:
625 for targetpath, srcs in copylist:
623 for origsrc, abssrc, relsrc, exact in srcs:
626 for origsrc, abssrc, relsrc, exact in srcs:
624 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
627 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
625
628
626 if errors:
629 if errors:
627 ui.warn(_('(consider using --after)\n'))
630 ui.warn(_('(consider using --after)\n'))
628 return errors, copied
631 return errors, copied
629
632
630 def copy(ui, repo, *pats, **opts):
633 def copy(ui, repo, *pats, **opts):
631 """mark files as copied for the next commit
634 """mark files as copied for the next commit
632
635
633 Mark dest as having copies of source files. If dest is a
636 Mark dest as having copies of source files. If dest is a
634 directory, copies are put in that directory. If dest is a file,
637 directory, copies are put in that directory. If dest is a file,
635 there can only be one source.
638 there can only be one source.
636
639
637 By default, this command copies the contents of files as they
640 By default, this command copies the contents of files as they
638 stand in the working directory. If invoked with --after, the
641 stand in the working directory. If invoked with --after, the
639 operation is recorded, but no copying is performed.
642 operation is recorded, but no copying is performed.
640
643
641 This command takes effect in the next commit. To undo a copy
644 This command takes effect in the next commit. To undo a copy
642 before that, see hg revert.
645 before that, see hg revert.
643 """
646 """
644 wlock = repo.wlock(False)
647 wlock = repo.wlock(False)
645 try:
648 try:
646 errs, copied = docopy(ui, repo, pats, opts)
649 errs, copied = docopy(ui, repo, pats, opts)
647 finally:
650 finally:
648 del wlock
651 del wlock
649 return errs
652 return errs
650
653
651 def debugancestor(ui, index, rev1, rev2):
654 def debugancestor(ui, index, rev1, rev2):
652 """find the ancestor revision of two revisions in a given index"""
655 """find the ancestor revision of two revisions in a given index"""
653 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
656 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
654 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
657 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
655 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
658 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
656
659
657 def debugcomplete(ui, cmd='', **opts):
660 def debugcomplete(ui, cmd='', **opts):
658 """returns the completion list associated with the given command"""
661 """returns the completion list associated with the given command"""
659
662
660 if opts['options']:
663 if opts['options']:
661 options = []
664 options = []
662 otables = [globalopts]
665 otables = [globalopts]
663 if cmd:
666 if cmd:
664 aliases, entry = cmdutil.findcmd(ui, cmd, table)
667 aliases, entry = cmdutil.findcmd(ui, cmd, table)
665 otables.append(entry[1])
668 otables.append(entry[1])
666 for t in otables:
669 for t in otables:
667 for o in t:
670 for o in t:
668 if o[0]:
671 if o[0]:
669 options.append('-%s' % o[0])
672 options.append('-%s' % o[0])
670 options.append('--%s' % o[1])
673 options.append('--%s' % o[1])
671 ui.write("%s\n" % "\n".join(options))
674 ui.write("%s\n" % "\n".join(options))
672 return
675 return
673
676
674 clist = cmdutil.findpossible(ui, cmd, table).keys()
677 clist = cmdutil.findpossible(ui, cmd, table).keys()
675 clist.sort()
678 clist.sort()
676 ui.write("%s\n" % "\n".join(clist))
679 ui.write("%s\n" % "\n".join(clist))
677
680
678 def debugrebuildstate(ui, repo, rev=""):
681 def debugrebuildstate(ui, repo, rev=""):
679 """rebuild the dirstate as it would look like for the given revision"""
682 """rebuild the dirstate as it would look like for the given revision"""
680 if rev == "":
683 if rev == "":
681 rev = repo.changelog.tip()
684 rev = repo.changelog.tip()
682 ctx = repo.changectx(rev)
685 ctx = repo.changectx(rev)
683 files = ctx.manifest()
686 files = ctx.manifest()
684 wlock = repo.wlock()
687 wlock = repo.wlock()
685 try:
688 try:
686 repo.dirstate.rebuild(rev, files)
689 repo.dirstate.rebuild(rev, files)
687 finally:
690 finally:
688 del wlock
691 del wlock
689
692
690 def debugcheckstate(ui, repo):
693 def debugcheckstate(ui, repo):
691 """validate the correctness of the current dirstate"""
694 """validate the correctness of the current dirstate"""
692 parent1, parent2 = repo.dirstate.parents()
695 parent1, parent2 = repo.dirstate.parents()
693 m1 = repo.changectx(parent1).manifest()
696 m1 = repo.changectx(parent1).manifest()
694 m2 = repo.changectx(parent2).manifest()
697 m2 = repo.changectx(parent2).manifest()
695 errors = 0
698 errors = 0
696 for f in repo.dirstate:
699 for f in repo.dirstate:
697 state = repo.dirstate[f]
700 state = repo.dirstate[f]
698 if state in "nr" and f not in m1:
701 if state in "nr" and f not in m1:
699 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
702 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
700 errors += 1
703 errors += 1
701 if state in "a" and f in m1:
704 if state in "a" and f in m1:
702 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
705 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
703 errors += 1
706 errors += 1
704 if state in "m" and f not in m1 and f not in m2:
707 if state in "m" and f not in m1 and f not in m2:
705 ui.warn(_("%s in state %s, but not in either manifest\n") %
708 ui.warn(_("%s in state %s, but not in either manifest\n") %
706 (f, state))
709 (f, state))
707 errors += 1
710 errors += 1
708 for f in m1:
711 for f in m1:
709 state = repo.dirstate[f]
712 state = repo.dirstate[f]
710 if state not in "nrm":
713 if state not in "nrm":
711 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
714 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
712 errors += 1
715 errors += 1
713 if errors:
716 if errors:
714 error = _(".hg/dirstate inconsistent with current parent's manifest")
717 error = _(".hg/dirstate inconsistent with current parent's manifest")
715 raise util.Abort(error)
718 raise util.Abort(error)
716
719
717 def showconfig(ui, repo, *values, **opts):
720 def showconfig(ui, repo, *values, **opts):
718 """show combined config settings from all hgrc files
721 """show combined config settings from all hgrc files
719
722
720 With no args, print names and values of all config items.
723 With no args, print names and values of all config items.
721
724
722 With one arg of the form section.name, print just the value of
725 With one arg of the form section.name, print just the value of
723 that config item.
726 that config item.
724
727
725 With multiple args, print names and values of all config items
728 With multiple args, print names and values of all config items
726 with matching section names."""
729 with matching section names."""
727
730
728 untrusted = bool(opts.get('untrusted'))
731 untrusted = bool(opts.get('untrusted'))
729 if values:
732 if values:
730 if len([v for v in values if '.' in v]) > 1:
733 if len([v for v in values if '.' in v]) > 1:
731 raise util.Abort(_('only one config item permitted'))
734 raise util.Abort(_('only one config item permitted'))
732 for section, name, value in ui.walkconfig(untrusted=untrusted):
735 for section, name, value in ui.walkconfig(untrusted=untrusted):
733 sectname = section + '.' + name
736 sectname = section + '.' + name
734 if values:
737 if values:
735 for v in values:
738 for v in values:
736 if v == section:
739 if v == section:
737 ui.write('%s=%s\n' % (sectname, value))
740 ui.write('%s=%s\n' % (sectname, value))
738 elif v == sectname:
741 elif v == sectname:
739 ui.write(value, '\n')
742 ui.write(value, '\n')
740 else:
743 else:
741 ui.write('%s=%s\n' % (sectname, value))
744 ui.write('%s=%s\n' % (sectname, value))
742
745
743 def debugsetparents(ui, repo, rev1, rev2=None):
746 def debugsetparents(ui, repo, rev1, rev2=None):
744 """manually set the parents of the current working directory
747 """manually set the parents of the current working directory
745
748
746 This is useful for writing repository conversion tools, but should
749 This is useful for writing repository conversion tools, but should
747 be used with care.
750 be used with care.
748 """
751 """
749
752
750 if not rev2:
753 if not rev2:
751 rev2 = hex(nullid)
754 rev2 = hex(nullid)
752
755
753 wlock = repo.wlock()
756 wlock = repo.wlock()
754 try:
757 try:
755 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
758 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
756 finally:
759 finally:
757 del wlock
760 del wlock
758
761
759 def debugstate(ui, repo):
762 def debugstate(ui, repo):
760 """show the contents of the current dirstate"""
763 """show the contents of the current dirstate"""
761 k = repo.dirstate._map.items()
764 k = repo.dirstate._map.items()
762 k.sort()
765 k.sort()
763 for file_, ent in k:
766 for file_, ent in k:
764 if ent[3] == -1:
767 if ent[3] == -1:
765 # Pad or slice to locale representation
768 # Pad or slice to locale representation
766 locale_len = len(time.strftime("%x %X", time.localtime(0)))
769 locale_len = len(time.strftime("%x %X", time.localtime(0)))
767 timestr = 'unset'
770 timestr = 'unset'
768 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
771 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
769 else:
772 else:
770 timestr = time.strftime("%x %X", time.localtime(ent[3]))
773 timestr = time.strftime("%x %X", time.localtime(ent[3]))
771 if ent[1] & 020000:
774 if ent[1] & 020000:
772 mode = 'lnk'
775 mode = 'lnk'
773 else:
776 else:
774 mode = '%3o' % (ent[1] & 0777)
777 mode = '%3o' % (ent[1] & 0777)
775 ui.write("%c %s %10d %s %s\n" % (ent[0], mode, ent[2], timestr, file_))
778 ui.write("%c %s %10d %s %s\n" % (ent[0], mode, ent[2], timestr, file_))
776 for f in repo.dirstate.copies():
779 for f in repo.dirstate.copies():
777 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
780 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
778
781
779 def debugdata(ui, file_, rev):
782 def debugdata(ui, file_, rev):
780 """dump the contents of a data file revision"""
783 """dump the contents of a data file revision"""
781 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
784 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
782 try:
785 try:
783 ui.write(r.revision(r.lookup(rev)))
786 ui.write(r.revision(r.lookup(rev)))
784 except KeyError:
787 except KeyError:
785 raise util.Abort(_('invalid revision identifier %s') % rev)
788 raise util.Abort(_('invalid revision identifier %s') % rev)
786
789
787 def debugdate(ui, date, range=None, **opts):
790 def debugdate(ui, date, range=None, **opts):
788 """parse and display a date"""
791 """parse and display a date"""
789 if opts["extended"]:
792 if opts["extended"]:
790 d = util.parsedate(date, util.extendeddateformats)
793 d = util.parsedate(date, util.extendeddateformats)
791 else:
794 else:
792 d = util.parsedate(date)
795 d = util.parsedate(date)
793 ui.write("internal: %s %s\n" % d)
796 ui.write("internal: %s %s\n" % d)
794 ui.write("standard: %s\n" % util.datestr(d))
797 ui.write("standard: %s\n" % util.datestr(d))
795 if range:
798 if range:
796 m = util.matchdate(range)
799 m = util.matchdate(range)
797 ui.write("match: %s\n" % m(d[0]))
800 ui.write("match: %s\n" % m(d[0]))
798
801
799 def debugindex(ui, file_):
802 def debugindex(ui, file_):
800 """dump the contents of an index file"""
803 """dump the contents of an index file"""
801 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
804 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
802 ui.write(" rev offset length base linkrev" +
805 ui.write(" rev offset length base linkrev" +
803 " nodeid p1 p2\n")
806 " nodeid p1 p2\n")
804 for i in xrange(r.count()):
807 for i in xrange(r.count()):
805 node = r.node(i)
808 node = r.node(i)
806 try:
809 try:
807 pp = r.parents(node)
810 pp = r.parents(node)
808 except:
811 except:
809 pp = [nullid, nullid]
812 pp = [nullid, nullid]
810 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
813 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
811 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
814 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
812 short(node), short(pp[0]), short(pp[1])))
815 short(node), short(pp[0]), short(pp[1])))
813
816
814 def debugindexdot(ui, file_):
817 def debugindexdot(ui, file_):
815 """dump an index DAG as a .dot file"""
818 """dump an index DAG as a .dot file"""
816 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
819 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
817 ui.write("digraph G {\n")
820 ui.write("digraph G {\n")
818 for i in xrange(r.count()):
821 for i in xrange(r.count()):
819 node = r.node(i)
822 node = r.node(i)
820 pp = r.parents(node)
823 pp = r.parents(node)
821 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
824 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
822 if pp[1] != nullid:
825 if pp[1] != nullid:
823 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
826 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
824 ui.write("}\n")
827 ui.write("}\n")
825
828
826 def debuginstall(ui):
829 def debuginstall(ui):
827 '''test Mercurial installation'''
830 '''test Mercurial installation'''
828
831
829 def writetemp(contents):
832 def writetemp(contents):
830 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
833 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
831 f = os.fdopen(fd, "wb")
834 f = os.fdopen(fd, "wb")
832 f.write(contents)
835 f.write(contents)
833 f.close()
836 f.close()
834 return name
837 return name
835
838
836 problems = 0
839 problems = 0
837
840
838 # encoding
841 # encoding
839 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
842 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
840 try:
843 try:
841 util.fromlocal("test")
844 util.fromlocal("test")
842 except util.Abort, inst:
845 except util.Abort, inst:
843 ui.write(" %s\n" % inst)
846 ui.write(" %s\n" % inst)
844 ui.write(_(" (check that your locale is properly set)\n"))
847 ui.write(_(" (check that your locale is properly set)\n"))
845 problems += 1
848 problems += 1
846
849
847 # compiled modules
850 # compiled modules
848 ui.status(_("Checking extensions...\n"))
851 ui.status(_("Checking extensions...\n"))
849 try:
852 try:
850 import bdiff, mpatch, base85
853 import bdiff, mpatch, base85
851 except Exception, inst:
854 except Exception, inst:
852 ui.write(" %s\n" % inst)
855 ui.write(" %s\n" % inst)
853 ui.write(_(" One or more extensions could not be found"))
856 ui.write(_(" One or more extensions could not be found"))
854 ui.write(_(" (check that you compiled the extensions)\n"))
857 ui.write(_(" (check that you compiled the extensions)\n"))
855 problems += 1
858 problems += 1
856
859
857 # templates
860 # templates
858 ui.status(_("Checking templates...\n"))
861 ui.status(_("Checking templates...\n"))
859 try:
862 try:
860 import templater
863 import templater
861 t = templater.templater(templater.templatepath("map-cmdline.default"))
864 t = templater.templater(templater.templatepath("map-cmdline.default"))
862 except Exception, inst:
865 except Exception, inst:
863 ui.write(" %s\n" % inst)
866 ui.write(" %s\n" % inst)
864 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
867 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
865 problems += 1
868 problems += 1
866
869
867 # patch
870 # patch
868 ui.status(_("Checking patch...\n"))
871 ui.status(_("Checking patch...\n"))
869 patchproblems = 0
872 patchproblems = 0
870 a = "1\n2\n3\n4\n"
873 a = "1\n2\n3\n4\n"
871 b = "1\n2\n3\ninsert\n4\n"
874 b = "1\n2\n3\ninsert\n4\n"
872 fa = writetemp(a)
875 fa = writetemp(a)
873 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa))
876 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa))
874 fd = writetemp(d)
877 fd = writetemp(d)
875
878
876 files = {}
879 files = {}
877 try:
880 try:
878 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
881 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
879 except util.Abort, e:
882 except util.Abort, e:
880 ui.write(_(" patch call failed:\n"))
883 ui.write(_(" patch call failed:\n"))
881 ui.write(" " + str(e) + "\n")
884 ui.write(" " + str(e) + "\n")
882 patchproblems += 1
885 patchproblems += 1
883 else:
886 else:
884 if list(files) != [os.path.basename(fa)]:
887 if list(files) != [os.path.basename(fa)]:
885 ui.write(_(" unexpected patch output!\n"))
888 ui.write(_(" unexpected patch output!\n"))
886 patchproblems += 1
889 patchproblems += 1
887 a = file(fa).read()
890 a = file(fa).read()
888 if a != b:
891 if a != b:
889 ui.write(_(" patch test failed!\n"))
892 ui.write(_(" patch test failed!\n"))
890 patchproblems += 1
893 patchproblems += 1
891
894
892 if patchproblems:
895 if patchproblems:
893 if ui.config('ui', 'patch'):
896 if ui.config('ui', 'patch'):
894 ui.write(_(" (Current patch tool may be incompatible with patch,"
897 ui.write(_(" (Current patch tool may be incompatible with patch,"
895 " or misconfigured. Please check your .hgrc file)\n"))
898 " or misconfigured. Please check your .hgrc file)\n"))
896 else:
899 else:
897 ui.write(_(" Internal patcher failure, please report this error"
900 ui.write(_(" Internal patcher failure, please report this error"
898 " to http://www.selenic.com/mercurial/bts\n"))
901 " to http://www.selenic.com/mercurial/bts\n"))
899 problems += patchproblems
902 problems += patchproblems
900
903
901 os.unlink(fa)
904 os.unlink(fa)
902 os.unlink(fd)
905 os.unlink(fd)
903
906
904 # merge helper
907 # merge helper
905 ui.status(_("Checking merge helper...\n"))
908 ui.status(_("Checking merge helper...\n"))
906 cmd = (os.environ.get("HGMERGE") or ui.config("ui", "merge")
909 cmd = (os.environ.get("HGMERGE") or ui.config("ui", "merge")
907 or "hgmerge")
910 or "hgmerge")
908 cmdpath = util.find_exe(cmd) or util.find_exe(cmd.split()[0])
911 cmdpath = util.find_exe(cmd) or util.find_exe(cmd.split()[0])
909 if not cmdpath:
912 if not cmdpath:
910 if cmd == 'hgmerge':
913 if cmd == 'hgmerge':
911 ui.write(_(" No merge helper set and can't find default"
914 ui.write(_(" No merge helper set and can't find default"
912 " hgmerge script in PATH\n"))
915 " hgmerge script in PATH\n"))
913 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
916 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
914 else:
917 else:
915 ui.write(_(" Can't find merge helper '%s' in PATH\n") % cmd)
918 ui.write(_(" Can't find merge helper '%s' in PATH\n") % cmd)
916 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
919 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
917 problems += 1
920 problems += 1
918 else:
921 else:
919 # actually attempt a patch here
922 # actually attempt a patch here
920 fa = writetemp("1\n2\n3\n4\n")
923 fa = writetemp("1\n2\n3\n4\n")
921 fl = writetemp("1\n2\n3\ninsert\n4\n")
924 fl = writetemp("1\n2\n3\ninsert\n4\n")
922 fr = writetemp("begin\n1\n2\n3\n4\n")
925 fr = writetemp("begin\n1\n2\n3\n4\n")
923 r = util.system('%s "%s" "%s" "%s"' % (cmd, fl, fa, fr))
926 r = util.system('%s "%s" "%s" "%s"' % (cmd, fl, fa, fr))
924 if r:
927 if r:
925 ui.write(_(" Got unexpected merge error %d!\n") % r)
928 ui.write(_(" Got unexpected merge error %d!\n") % r)
926 problems += 1
929 problems += 1
927 m = file(fl).read()
930 m = file(fl).read()
928 if m != "begin\n1\n2\n3\ninsert\n4\n":
931 if m != "begin\n1\n2\n3\ninsert\n4\n":
929 ui.write(_(" Got unexpected merge results!\n"))
932 ui.write(_(" Got unexpected merge results!\n"))
930 ui.write(_(" (your merge helper may have the"
933 ui.write(_(" (your merge helper may have the"
931 " wrong argument order)\n"))
934 " wrong argument order)\n"))
932 ui.write(_(" Result: %r\n") % m)
935 ui.write(_(" Result: %r\n") % m)
933 problems += 1
936 problems += 1
934 os.unlink(fa)
937 os.unlink(fa)
935 os.unlink(fl)
938 os.unlink(fl)
936 os.unlink(fr)
939 os.unlink(fr)
937
940
938 # editor
941 # editor
939 ui.status(_("Checking commit editor...\n"))
942 ui.status(_("Checking commit editor...\n"))
940 editor = (os.environ.get("HGEDITOR") or
943 editor = (os.environ.get("HGEDITOR") or
941 ui.config("ui", "editor") or
944 ui.config("ui", "editor") or
942 os.environ.get("EDITOR", "vi"))
945 os.environ.get("EDITOR", "vi"))
943 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
946 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
944 if not cmdpath:
947 if not cmdpath:
945 if editor == 'vi':
948 if editor == 'vi':
946 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
949 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
947 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
950 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
948 else:
951 else:
949 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
952 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
950 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
953 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
951 problems += 1
954 problems += 1
952
955
953 # check username
956 # check username
954 ui.status(_("Checking username...\n"))
957 ui.status(_("Checking username...\n"))
955 user = os.environ.get("HGUSER")
958 user = os.environ.get("HGUSER")
956 if user is None:
959 if user is None:
957 user = ui.config("ui", "username")
960 user = ui.config("ui", "username")
958 if user is None:
961 if user is None:
959 user = os.environ.get("EMAIL")
962 user = os.environ.get("EMAIL")
960 if not user:
963 if not user:
961 ui.warn(" ")
964 ui.warn(" ")
962 ui.username()
965 ui.username()
963 ui.write(_(" (specify a username in your .hgrc file)\n"))
966 ui.write(_(" (specify a username in your .hgrc file)\n"))
964
967
965 if not problems:
968 if not problems:
966 ui.status(_("No problems detected\n"))
969 ui.status(_("No problems detected\n"))
967 else:
970 else:
968 ui.write(_("%s problems detected,"
971 ui.write(_("%s problems detected,"
969 " please check your install!\n") % problems)
972 " please check your install!\n") % problems)
970
973
971 return problems
974 return problems
972
975
973 def debugrename(ui, repo, file1, *pats, **opts):
976 def debugrename(ui, repo, file1, *pats, **opts):
974 """dump rename information"""
977 """dump rename information"""
975
978
976 ctx = repo.changectx(opts.get('rev', 'tip'))
979 ctx = repo.changectx(opts.get('rev', 'tip'))
977 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
980 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
978 ctx.node()):
981 ctx.node()):
979 m = ctx.filectx(abs).renamed()
982 m = ctx.filectx(abs).renamed()
980 if m:
983 if m:
981 ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1])))
984 ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1])))
982 else:
985 else:
983 ui.write(_("%s not renamed\n") % rel)
986 ui.write(_("%s not renamed\n") % rel)
984
987
985 def debugwalk(ui, repo, *pats, **opts):
988 def debugwalk(ui, repo, *pats, **opts):
986 """show how files match on given patterns"""
989 """show how files match on given patterns"""
987 items = list(cmdutil.walk(repo, pats, opts))
990 items = list(cmdutil.walk(repo, pats, opts))
988 if not items:
991 if not items:
989 return
992 return
990 fmt = '%%s %%-%ds %%-%ds %%s' % (
993 fmt = '%%s %%-%ds %%-%ds %%s' % (
991 max([len(abs) for (src, abs, rel, exact) in items]),
994 max([len(abs) for (src, abs, rel, exact) in items]),
992 max([len(rel) for (src, abs, rel, exact) in items]))
995 max([len(rel) for (src, abs, rel, exact) in items]))
993 for src, abs, rel, exact in items:
996 for src, abs, rel, exact in items:
994 line = fmt % (src, abs, rel, exact and 'exact' or '')
997 line = fmt % (src, abs, rel, exact and 'exact' or '')
995 ui.write("%s\n" % line.rstrip())
998 ui.write("%s\n" % line.rstrip())
996
999
997 def diff(ui, repo, *pats, **opts):
1000 def diff(ui, repo, *pats, **opts):
998 """diff repository (or selected files)
1001 """diff repository (or selected files)
999
1002
1000 Show differences between revisions for the specified files.
1003 Show differences between revisions for the specified files.
1001
1004
1002 Differences between files are shown using the unified diff format.
1005 Differences between files are shown using the unified diff format.
1003
1006
1004 NOTE: diff may generate unexpected results for merges, as it will
1007 NOTE: diff may generate unexpected results for merges, as it will
1005 default to comparing against the working directory's first parent
1008 default to comparing against the working directory's first parent
1006 changeset if no revisions are specified.
1009 changeset if no revisions are specified.
1007
1010
1008 When two revision arguments are given, then changes are shown
1011 When two revision arguments are given, then changes are shown
1009 between those revisions. If only one revision is specified then
1012 between those revisions. If only one revision is specified then
1010 that revision is compared to the working directory, and, when no
1013 that revision is compared to the working directory, and, when no
1011 revisions are specified, the working directory files are compared
1014 revisions are specified, the working directory files are compared
1012 to its parent.
1015 to its parent.
1013
1016
1014 Without the -a option, diff will avoid generating diffs of files
1017 Without the -a option, diff will avoid generating diffs of files
1015 it detects as binary. With -a, diff will generate a diff anyway,
1018 it detects as binary. With -a, diff will generate a diff anyway,
1016 probably with undesirable results.
1019 probably with undesirable results.
1017 """
1020 """
1018 node1, node2 = cmdutil.revpair(repo, opts['rev'])
1021 node1, node2 = cmdutil.revpair(repo, opts['rev'])
1019
1022
1020 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1023 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1021
1024
1022 patch.diff(repo, node1, node2, fns, match=matchfn,
1025 patch.diff(repo, node1, node2, fns, match=matchfn,
1023 opts=patch.diffopts(ui, opts))
1026 opts=patch.diffopts(ui, opts))
1024
1027
1025 def export(ui, repo, *changesets, **opts):
1028 def export(ui, repo, *changesets, **opts):
1026 """dump the header and diffs for one or more changesets
1029 """dump the header and diffs for one or more changesets
1027
1030
1028 Print the changeset header and diffs for one or more revisions.
1031 Print the changeset header and diffs for one or more revisions.
1029
1032
1030 The information shown in the changeset header is: author,
1033 The information shown in the changeset header is: author,
1031 changeset hash, parent(s) and commit comment.
1034 changeset hash, parent(s) and commit comment.
1032
1035
1033 NOTE: export may generate unexpected diff output for merge changesets,
1036 NOTE: export may generate unexpected diff output for merge changesets,
1034 as it will compare the merge changeset against its first parent only.
1037 as it will compare the merge changeset against its first parent only.
1035
1038
1036 Output may be to a file, in which case the name of the file is
1039 Output may be to a file, in which case the name of the file is
1037 given using a format string. The formatting rules are as follows:
1040 given using a format string. The formatting rules are as follows:
1038
1041
1039 %% literal "%" character
1042 %% literal "%" character
1040 %H changeset hash (40 bytes of hexadecimal)
1043 %H changeset hash (40 bytes of hexadecimal)
1041 %N number of patches being generated
1044 %N number of patches being generated
1042 %R changeset revision number
1045 %R changeset revision number
1043 %b basename of the exporting repository
1046 %b basename of the exporting repository
1044 %h short-form changeset hash (12 bytes of hexadecimal)
1047 %h short-form changeset hash (12 bytes of hexadecimal)
1045 %n zero-padded sequence number, starting at 1
1048 %n zero-padded sequence number, starting at 1
1046 %r zero-padded changeset revision number
1049 %r zero-padded changeset revision number
1047
1050
1048 Without the -a option, export will avoid generating diffs of files
1051 Without the -a option, export will avoid generating diffs of files
1049 it detects as binary. With -a, export will generate a diff anyway,
1052 it detects as binary. With -a, export will generate a diff anyway,
1050 probably with undesirable results.
1053 probably with undesirable results.
1051
1054
1052 With the --switch-parent option, the diff will be against the second
1055 With the --switch-parent option, the diff will be against the second
1053 parent. It can be useful to review a merge.
1056 parent. It can be useful to review a merge.
1054 """
1057 """
1055 if not changesets:
1058 if not changesets:
1056 raise util.Abort(_("export requires at least one changeset"))
1059 raise util.Abort(_("export requires at least one changeset"))
1057 revs = cmdutil.revrange(repo, changesets)
1060 revs = cmdutil.revrange(repo, changesets)
1058 if len(revs) > 1:
1061 if len(revs) > 1:
1059 ui.note(_('exporting patches:\n'))
1062 ui.note(_('exporting patches:\n'))
1060 else:
1063 else:
1061 ui.note(_('exporting patch:\n'))
1064 ui.note(_('exporting patch:\n'))
1062 patch.export(repo, revs, template=opts['output'],
1065 patch.export(repo, revs, template=opts['output'],
1063 switch_parent=opts['switch_parent'],
1066 switch_parent=opts['switch_parent'],
1064 opts=patch.diffopts(ui, opts))
1067 opts=patch.diffopts(ui, opts))
1065
1068
1066 def grep(ui, repo, pattern, *pats, **opts):
1069 def grep(ui, repo, pattern, *pats, **opts):
1067 """search for a pattern in specified files and revisions
1070 """search for a pattern in specified files and revisions
1068
1071
1069 Search revisions of files for a regular expression.
1072 Search revisions of files for a regular expression.
1070
1073
1071 This command behaves differently than Unix grep. It only accepts
1074 This command behaves differently than Unix grep. It only accepts
1072 Python/Perl regexps. It searches repository history, not the
1075 Python/Perl regexps. It searches repository history, not the
1073 working directory. It always prints the revision number in which
1076 working directory. It always prints the revision number in which
1074 a match appears.
1077 a match appears.
1075
1078
1076 By default, grep only prints output for the first revision of a
1079 By default, grep only prints output for the first revision of a
1077 file in which it finds a match. To get it to print every revision
1080 file in which it finds a match. To get it to print every revision
1078 that contains a change in match status ("-" for a match that
1081 that contains a change in match status ("-" for a match that
1079 becomes a non-match, or "+" for a non-match that becomes a match),
1082 becomes a non-match, or "+" for a non-match that becomes a match),
1080 use the --all flag.
1083 use the --all flag.
1081 """
1084 """
1082 reflags = 0
1085 reflags = 0
1083 if opts['ignore_case']:
1086 if opts['ignore_case']:
1084 reflags |= re.I
1087 reflags |= re.I
1085 try:
1088 try:
1086 regexp = re.compile(pattern, reflags)
1089 regexp = re.compile(pattern, reflags)
1087 except Exception, inst:
1090 except Exception, inst:
1088 ui.warn(_("grep: invalid match pattern: %s!\n") % inst)
1091 ui.warn(_("grep: invalid match pattern: %s!\n") % inst)
1089 return None
1092 return None
1090 sep, eol = ':', '\n'
1093 sep, eol = ':', '\n'
1091 if opts['print0']:
1094 if opts['print0']:
1092 sep = eol = '\0'
1095 sep = eol = '\0'
1093
1096
1094 fcache = {}
1097 fcache = {}
1095 def getfile(fn):
1098 def getfile(fn):
1096 if fn not in fcache:
1099 if fn not in fcache:
1097 fcache[fn] = repo.file(fn)
1100 fcache[fn] = repo.file(fn)
1098 return fcache[fn]
1101 return fcache[fn]
1099
1102
1100 def matchlines(body):
1103 def matchlines(body):
1101 begin = 0
1104 begin = 0
1102 linenum = 0
1105 linenum = 0
1103 while True:
1106 while True:
1104 match = regexp.search(body, begin)
1107 match = regexp.search(body, begin)
1105 if not match:
1108 if not match:
1106 break
1109 break
1107 mstart, mend = match.span()
1110 mstart, mend = match.span()
1108 linenum += body.count('\n', begin, mstart) + 1
1111 linenum += body.count('\n', begin, mstart) + 1
1109 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1112 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1110 lend = body.find('\n', mend)
1113 lend = body.find('\n', mend)
1111 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1114 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1112 begin = lend + 1
1115 begin = lend + 1
1113
1116
1114 class linestate(object):
1117 class linestate(object):
1115 def __init__(self, line, linenum, colstart, colend):
1118 def __init__(self, line, linenum, colstart, colend):
1116 self.line = line
1119 self.line = line
1117 self.linenum = linenum
1120 self.linenum = linenum
1118 self.colstart = colstart
1121 self.colstart = colstart
1119 self.colend = colend
1122 self.colend = colend
1120
1123
1121 def __eq__(self, other):
1124 def __eq__(self, other):
1122 return self.line == other.line
1125 return self.line == other.line
1123
1126
1124 matches = {}
1127 matches = {}
1125 copies = {}
1128 copies = {}
1126 def grepbody(fn, rev, body):
1129 def grepbody(fn, rev, body):
1127 matches[rev].setdefault(fn, [])
1130 matches[rev].setdefault(fn, [])
1128 m = matches[rev][fn]
1131 m = matches[rev][fn]
1129 for lnum, cstart, cend, line in matchlines(body):
1132 for lnum, cstart, cend, line in matchlines(body):
1130 s = linestate(line, lnum, cstart, cend)
1133 s = linestate(line, lnum, cstart, cend)
1131 m.append(s)
1134 m.append(s)
1132
1135
1133 def difflinestates(a, b):
1136 def difflinestates(a, b):
1134 sm = difflib.SequenceMatcher(None, a, b)
1137 sm = difflib.SequenceMatcher(None, a, b)
1135 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1138 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1136 if tag == 'insert':
1139 if tag == 'insert':
1137 for i in xrange(blo, bhi):
1140 for i in xrange(blo, bhi):
1138 yield ('+', b[i])
1141 yield ('+', b[i])
1139 elif tag == 'delete':
1142 elif tag == 'delete':
1140 for i in xrange(alo, ahi):
1143 for i in xrange(alo, ahi):
1141 yield ('-', a[i])
1144 yield ('-', a[i])
1142 elif tag == 'replace':
1145 elif tag == 'replace':
1143 for i in xrange(alo, ahi):
1146 for i in xrange(alo, ahi):
1144 yield ('-', a[i])
1147 yield ('-', a[i])
1145 for i in xrange(blo, bhi):
1148 for i in xrange(blo, bhi):
1146 yield ('+', b[i])
1149 yield ('+', b[i])
1147
1150
1148 prev = {}
1151 prev = {}
1149 def display(fn, rev, states, prevstates):
1152 def display(fn, rev, states, prevstates):
1150 found = False
1153 found = False
1151 filerevmatches = {}
1154 filerevmatches = {}
1152 r = prev.get(fn, -1)
1155 r = prev.get(fn, -1)
1153 if opts['all']:
1156 if opts['all']:
1154 iter = difflinestates(states, prevstates)
1157 iter = difflinestates(states, prevstates)
1155 else:
1158 else:
1156 iter = [('', l) for l in prevstates]
1159 iter = [('', l) for l in prevstates]
1157 for change, l in iter:
1160 for change, l in iter:
1158 cols = [fn, str(r)]
1161 cols = [fn, str(r)]
1159 if opts['line_number']:
1162 if opts['line_number']:
1160 cols.append(str(l.linenum))
1163 cols.append(str(l.linenum))
1161 if opts['all']:
1164 if opts['all']:
1162 cols.append(change)
1165 cols.append(change)
1163 if opts['user']:
1166 if opts['user']:
1164 cols.append(ui.shortuser(get(r)[1]))
1167 cols.append(ui.shortuser(get(r)[1]))
1165 if opts['files_with_matches']:
1168 if opts['files_with_matches']:
1166 c = (fn, r)
1169 c = (fn, r)
1167 if c in filerevmatches:
1170 if c in filerevmatches:
1168 continue
1171 continue
1169 filerevmatches[c] = 1
1172 filerevmatches[c] = 1
1170 else:
1173 else:
1171 cols.append(l.line)
1174 cols.append(l.line)
1172 ui.write(sep.join(cols), eol)
1175 ui.write(sep.join(cols), eol)
1173 found = True
1176 found = True
1174 return found
1177 return found
1175
1178
1176 fstate = {}
1179 fstate = {}
1177 skip = {}
1180 skip = {}
1178 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1181 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1179 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1182 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1180 found = False
1183 found = False
1181 follow = opts.get('follow')
1184 follow = opts.get('follow')
1182 for st, rev, fns in changeiter:
1185 for st, rev, fns in changeiter:
1183 if st == 'window':
1186 if st == 'window':
1184 matches.clear()
1187 matches.clear()
1185 elif st == 'add':
1188 elif st == 'add':
1186 mf = repo.changectx(rev).manifest()
1189 mf = repo.changectx(rev).manifest()
1187 matches[rev] = {}
1190 matches[rev] = {}
1188 for fn in fns:
1191 for fn in fns:
1189 if fn in skip:
1192 if fn in skip:
1190 continue
1193 continue
1191 try:
1194 try:
1192 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1195 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1193 fstate.setdefault(fn, [])
1196 fstate.setdefault(fn, [])
1194 if follow:
1197 if follow:
1195 copied = getfile(fn).renamed(mf[fn])
1198 copied = getfile(fn).renamed(mf[fn])
1196 if copied:
1199 if copied:
1197 copies.setdefault(rev, {})[fn] = copied[0]
1200 copies.setdefault(rev, {})[fn] = copied[0]
1198 except KeyError:
1201 except KeyError:
1199 pass
1202 pass
1200 elif st == 'iter':
1203 elif st == 'iter':
1201 states = matches[rev].items()
1204 states = matches[rev].items()
1202 states.sort()
1205 states.sort()
1203 for fn, m in states:
1206 for fn, m in states:
1204 copy = copies.get(rev, {}).get(fn)
1207 copy = copies.get(rev, {}).get(fn)
1205 if fn in skip:
1208 if fn in skip:
1206 if copy:
1209 if copy:
1207 skip[copy] = True
1210 skip[copy] = True
1208 continue
1211 continue
1209 if fn in prev or fstate[fn]:
1212 if fn in prev or fstate[fn]:
1210 r = display(fn, rev, m, fstate[fn])
1213 r = display(fn, rev, m, fstate[fn])
1211 found = found or r
1214 found = found or r
1212 if r and not opts['all']:
1215 if r and not opts['all']:
1213 skip[fn] = True
1216 skip[fn] = True
1214 if copy:
1217 if copy:
1215 skip[copy] = True
1218 skip[copy] = True
1216 fstate[fn] = m
1219 fstate[fn] = m
1217 if copy:
1220 if copy:
1218 fstate[copy] = m
1221 fstate[copy] = m
1219 prev[fn] = rev
1222 prev[fn] = rev
1220
1223
1221 fstate = fstate.items()
1224 fstate = fstate.items()
1222 fstate.sort()
1225 fstate.sort()
1223 for fn, state in fstate:
1226 for fn, state in fstate:
1224 if fn in skip:
1227 if fn in skip:
1225 continue
1228 continue
1226 if fn not in copies.get(prev[fn], {}):
1229 if fn not in copies.get(prev[fn], {}):
1227 found = display(fn, rev, {}, state) or found
1230 found = display(fn, rev, {}, state) or found
1228 return (not found and 1) or 0
1231 return (not found and 1) or 0
1229
1232
1230 def heads(ui, repo, *branchrevs, **opts):
1233 def heads(ui, repo, *branchrevs, **opts):
1231 """show current repository heads or show branch heads
1234 """show current repository heads or show branch heads
1232
1235
1233 With no arguments, show all repository head changesets.
1236 With no arguments, show all repository head changesets.
1234
1237
1235 If branch or revisions names are given this will show the heads of
1238 If branch or revisions names are given this will show the heads of
1236 the specified branches or the branches those revisions are tagged
1239 the specified branches or the branches those revisions are tagged
1237 with.
1240 with.
1238
1241
1239 Repository "heads" are changesets that don't have child
1242 Repository "heads" are changesets that don't have child
1240 changesets. They are where development generally takes place and
1243 changesets. They are where development generally takes place and
1241 are the usual targets for update and merge operations.
1244 are the usual targets for update and merge operations.
1242
1245
1243 Branch heads are changesets that have a given branch tag, but have
1246 Branch heads are changesets that have a given branch tag, but have
1244 no child changesets with that tag. They are usually where
1247 no child changesets with that tag. They are usually where
1245 development on the given branch takes place.
1248 development on the given branch takes place.
1246 """
1249 """
1247 if opts['rev']:
1250 if opts['rev']:
1248 start = repo.lookup(opts['rev'])
1251 start = repo.lookup(opts['rev'])
1249 else:
1252 else:
1250 start = None
1253 start = None
1251 if not branchrevs:
1254 if not branchrevs:
1252 # Assume we're looking repo-wide heads if no revs were specified.
1255 # Assume we're looking repo-wide heads if no revs were specified.
1253 heads = repo.heads(start)
1256 heads = repo.heads(start)
1254 else:
1257 else:
1255 heads = []
1258 heads = []
1256 visitedset = util.set()
1259 visitedset = util.set()
1257 for branchrev in branchrevs:
1260 for branchrev in branchrevs:
1258 branch = repo.changectx(branchrev).branch()
1261 branch = repo.changectx(branchrev).branch()
1259 if branch in visitedset:
1262 if branch in visitedset:
1260 continue
1263 continue
1261 visitedset.add(branch)
1264 visitedset.add(branch)
1262 bheads = repo.branchheads(branch, start)
1265 bheads = repo.branchheads(branch, start)
1263 if not bheads:
1266 if not bheads:
1264 if branch != branchrev:
1267 if branch != branchrev:
1265 ui.warn(_("no changes on branch %s containing %s are "
1268 ui.warn(_("no changes on branch %s containing %s are "
1266 "reachable from %s\n")
1269 "reachable from %s\n")
1267 % (branch, branchrev, opts['rev']))
1270 % (branch, branchrev, opts['rev']))
1268 else:
1271 else:
1269 ui.warn(_("no changes on branch %s are reachable from %s\n")
1272 ui.warn(_("no changes on branch %s are reachable from %s\n")
1270 % (branch, opts['rev']))
1273 % (branch, opts['rev']))
1271 heads.extend(bheads)
1274 heads.extend(bheads)
1272 if not heads:
1275 if not heads:
1273 return 1
1276 return 1
1274 displayer = cmdutil.show_changeset(ui, repo, opts)
1277 displayer = cmdutil.show_changeset(ui, repo, opts)
1275 for n in heads:
1278 for n in heads:
1276 displayer.show(changenode=n)
1279 displayer.show(changenode=n)
1277
1280
1278 def help_(ui, name=None, with_version=False):
1281 def help_(ui, name=None, with_version=False):
1279 """show help for a command, extension, or list of commands
1282 """show help for a command, extension, or list of commands
1280
1283
1281 With no arguments, print a list of commands and short help.
1284 With no arguments, print a list of commands and short help.
1282
1285
1283 Given a command name, print help for that command.
1286 Given a command name, print help for that command.
1284
1287
1285 Given an extension name, print help for that extension, and the
1288 Given an extension name, print help for that extension, and the
1286 commands it provides."""
1289 commands it provides."""
1287 option_lists = []
1290 option_lists = []
1288
1291
1289 def addglobalopts(aliases):
1292 def addglobalopts(aliases):
1290 if ui.verbose:
1293 if ui.verbose:
1291 option_lists.append((_("global options:"), globalopts))
1294 option_lists.append((_("global options:"), globalopts))
1292 if name == 'shortlist':
1295 if name == 'shortlist':
1293 option_lists.append((_('use "hg help" for the full list '
1296 option_lists.append((_('use "hg help" for the full list '
1294 'of commands'), ()))
1297 'of commands'), ()))
1295 else:
1298 else:
1296 if name == 'shortlist':
1299 if name == 'shortlist':
1297 msg = _('use "hg help" for the full list of commands '
1300 msg = _('use "hg help" for the full list of commands '
1298 'or "hg -v" for details')
1301 'or "hg -v" for details')
1299 elif aliases:
1302 elif aliases:
1300 msg = _('use "hg -v help%s" to show aliases and '
1303 msg = _('use "hg -v help%s" to show aliases and '
1301 'global options') % (name and " " + name or "")
1304 'global options') % (name and " " + name or "")
1302 else:
1305 else:
1303 msg = _('use "hg -v help %s" to show global options') % name
1306 msg = _('use "hg -v help %s" to show global options') % name
1304 option_lists.append((msg, ()))
1307 option_lists.append((msg, ()))
1305
1308
1306 def helpcmd(name):
1309 def helpcmd(name):
1307 if with_version:
1310 if with_version:
1308 version_(ui)
1311 version_(ui)
1309 ui.write('\n')
1312 ui.write('\n')
1310 aliases, i = cmdutil.findcmd(ui, name, table)
1313 aliases, i = cmdutil.findcmd(ui, name, table)
1311 # synopsis
1314 # synopsis
1312 ui.write("%s\n\n" % i[2])
1315 ui.write("%s\n\n" % i[2])
1313
1316
1314 # description
1317 # description
1315 doc = i[0].__doc__
1318 doc = i[0].__doc__
1316 if not doc:
1319 if not doc:
1317 doc = _("(No help text available)")
1320 doc = _("(No help text available)")
1318 if ui.quiet:
1321 if ui.quiet:
1319 doc = doc.splitlines(0)[0]
1322 doc = doc.splitlines(0)[0]
1320 ui.write("%s\n" % doc.rstrip())
1323 ui.write("%s\n" % doc.rstrip())
1321
1324
1322 if not ui.quiet:
1325 if not ui.quiet:
1323 # aliases
1326 # aliases
1324 if len(aliases) > 1:
1327 if len(aliases) > 1:
1325 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1328 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1326
1329
1327 # options
1330 # options
1328 if i[1]:
1331 if i[1]:
1329 option_lists.append((_("options:\n"), i[1]))
1332 option_lists.append((_("options:\n"), i[1]))
1330
1333
1331 addglobalopts(False)
1334 addglobalopts(False)
1332
1335
1333 def helplist(header, select=None):
1336 def helplist(header, select=None):
1334 h = {}
1337 h = {}
1335 cmds = {}
1338 cmds = {}
1336 for c, e in table.items():
1339 for c, e in table.items():
1337 f = c.split("|", 1)[0]
1340 f = c.split("|", 1)[0]
1338 if select and not select(f):
1341 if select and not select(f):
1339 continue
1342 continue
1340 if name == "shortlist" and not f.startswith("^"):
1343 if name == "shortlist" and not f.startswith("^"):
1341 continue
1344 continue
1342 f = f.lstrip("^")
1345 f = f.lstrip("^")
1343 if not ui.debugflag and f.startswith("debug"):
1346 if not ui.debugflag and f.startswith("debug"):
1344 continue
1347 continue
1345 doc = e[0].__doc__
1348 doc = e[0].__doc__
1346 if not doc:
1349 if not doc:
1347 doc = _("(No help text available)")
1350 doc = _("(No help text available)")
1348 h[f] = doc.splitlines(0)[0].rstrip()
1351 h[f] = doc.splitlines(0)[0].rstrip()
1349 cmds[f] = c.lstrip("^")
1352 cmds[f] = c.lstrip("^")
1350
1353
1351 if not h:
1354 if not h:
1352 ui.status(_('no commands defined\n'))
1355 ui.status(_('no commands defined\n'))
1353 return
1356 return
1354
1357
1355 ui.status(header)
1358 ui.status(header)
1356 fns = h.keys()
1359 fns = h.keys()
1357 fns.sort()
1360 fns.sort()
1358 m = max(map(len, fns))
1361 m = max(map(len, fns))
1359 for f in fns:
1362 for f in fns:
1360 if ui.verbose:
1363 if ui.verbose:
1361 commands = cmds[f].replace("|",", ")
1364 commands = cmds[f].replace("|",", ")
1362 ui.write(" %s:\n %s\n"%(commands, h[f]))
1365 ui.write(" %s:\n %s\n"%(commands, h[f]))
1363 else:
1366 else:
1364 ui.write(' %-*s %s\n' % (m, f, h[f]))
1367 ui.write(' %-*s %s\n' % (m, f, h[f]))
1365
1368
1366 if not ui.quiet:
1369 if not ui.quiet:
1367 addglobalopts(True)
1370 addglobalopts(True)
1368
1371
1369 def helptopic(name):
1372 def helptopic(name):
1370 v = None
1373 v = None
1371 for i in help.helptable:
1374 for i in help.helptable:
1372 l = i.split('|')
1375 l = i.split('|')
1373 if name in l:
1376 if name in l:
1374 v = i
1377 v = i
1375 header = l[-1]
1378 header = l[-1]
1376 if not v:
1379 if not v:
1377 raise cmdutil.UnknownCommand(name)
1380 raise cmdutil.UnknownCommand(name)
1378
1381
1379 # description
1382 # description
1380 doc = help.helptable[v]
1383 doc = help.helptable[v]
1381 if not doc:
1384 if not doc:
1382 doc = _("(No help text available)")
1385 doc = _("(No help text available)")
1383 if callable(doc):
1386 if callable(doc):
1384 doc = doc()
1387 doc = doc()
1385
1388
1386 ui.write("%s\n" % header)
1389 ui.write("%s\n" % header)
1387 ui.write("%s\n" % doc.rstrip())
1390 ui.write("%s\n" % doc.rstrip())
1388
1391
1389 def helpext(name):
1392 def helpext(name):
1390 try:
1393 try:
1391 mod = extensions.find(name)
1394 mod = extensions.find(name)
1392 except KeyError:
1395 except KeyError:
1393 raise cmdutil.UnknownCommand(name)
1396 raise cmdutil.UnknownCommand(name)
1394
1397
1395 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1398 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1396 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1399 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1397 for d in doc[1:]:
1400 for d in doc[1:]:
1398 ui.write(d, '\n')
1401 ui.write(d, '\n')
1399
1402
1400 ui.status('\n')
1403 ui.status('\n')
1401
1404
1402 try:
1405 try:
1403 ct = mod.cmdtable
1406 ct = mod.cmdtable
1404 except AttributeError:
1407 except AttributeError:
1405 ct = {}
1408 ct = {}
1406
1409
1407 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1410 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1408 helplist(_('list of commands:\n\n'), modcmds.has_key)
1411 helplist(_('list of commands:\n\n'), modcmds.has_key)
1409
1412
1410 if name and name != 'shortlist':
1413 if name and name != 'shortlist':
1411 i = None
1414 i = None
1412 for f in (helpcmd, helptopic, helpext):
1415 for f in (helpcmd, helptopic, helpext):
1413 try:
1416 try:
1414 f(name)
1417 f(name)
1415 i = None
1418 i = None
1416 break
1419 break
1417 except cmdutil.UnknownCommand, inst:
1420 except cmdutil.UnknownCommand, inst:
1418 i = inst
1421 i = inst
1419 if i:
1422 if i:
1420 raise i
1423 raise i
1421
1424
1422 else:
1425 else:
1423 # program name
1426 # program name
1424 if ui.verbose or with_version:
1427 if ui.verbose or with_version:
1425 version_(ui)
1428 version_(ui)
1426 else:
1429 else:
1427 ui.status(_("Mercurial Distributed SCM\n"))
1430 ui.status(_("Mercurial Distributed SCM\n"))
1428 ui.status('\n')
1431 ui.status('\n')
1429
1432
1430 # list of commands
1433 # list of commands
1431 if name == "shortlist":
1434 if name == "shortlist":
1432 header = _('basic commands:\n\n')
1435 header = _('basic commands:\n\n')
1433 else:
1436 else:
1434 header = _('list of commands:\n\n')
1437 header = _('list of commands:\n\n')
1435
1438
1436 helplist(header)
1439 helplist(header)
1437
1440
1438 # list all option lists
1441 # list all option lists
1439 opt_output = []
1442 opt_output = []
1440 for title, options in option_lists:
1443 for title, options in option_lists:
1441 opt_output.append(("\n%s" % title, None))
1444 opt_output.append(("\n%s" % title, None))
1442 for shortopt, longopt, default, desc in options:
1445 for shortopt, longopt, default, desc in options:
1443 if "DEPRECATED" in desc and not ui.verbose: continue
1446 if "DEPRECATED" in desc and not ui.verbose: continue
1444 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1447 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1445 longopt and " --%s" % longopt),
1448 longopt and " --%s" % longopt),
1446 "%s%s" % (desc,
1449 "%s%s" % (desc,
1447 default
1450 default
1448 and _(" (default: %s)") % default
1451 and _(" (default: %s)") % default
1449 or "")))
1452 or "")))
1450
1453
1451 if opt_output:
1454 if opt_output:
1452 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1455 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1453 for first, second in opt_output:
1456 for first, second in opt_output:
1454 if second:
1457 if second:
1455 ui.write(" %-*s %s\n" % (opts_len, first, second))
1458 ui.write(" %-*s %s\n" % (opts_len, first, second))
1456 else:
1459 else:
1457 ui.write("%s\n" % first)
1460 ui.write("%s\n" % first)
1458
1461
1459 def identify(ui, repo, source=None,
1462 def identify(ui, repo, source=None,
1460 rev=None, num=None, id=None, branch=None, tags=None):
1463 rev=None, num=None, id=None, branch=None, tags=None):
1461 """identify the working copy or specified revision
1464 """identify the working copy or specified revision
1462
1465
1463 With no revision, print a summary of the current state of the repo.
1466 With no revision, print a summary of the current state of the repo.
1464
1467
1465 With a path, do a lookup in another repository.
1468 With a path, do a lookup in another repository.
1466
1469
1467 This summary identifies the repository state using one or two parent
1470 This summary identifies the repository state using one or two parent
1468 hash identifiers, followed by a "+" if there are uncommitted changes
1471 hash identifiers, followed by a "+" if there are uncommitted changes
1469 in the working directory, a list of tags for this revision and a branch
1472 in the working directory, a list of tags for this revision and a branch
1470 name for non-default branches.
1473 name for non-default branches.
1471 """
1474 """
1472
1475
1476 if not repo and not source:
1477 raise util.Abort(_("There is no Mercurial repository here "
1478 "(.hg not found)"))
1479
1473 hexfunc = ui.debugflag and hex or short
1480 hexfunc = ui.debugflag and hex or short
1474 default = not (num or id or branch or tags)
1481 default = not (num or id or branch or tags)
1475 output = []
1482 output = []
1476
1483
1477 if source:
1484 if source:
1478 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1485 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1479 srepo = hg.repository(ui, source)
1486 srepo = hg.repository(ui, source)
1480 if not rev and revs:
1487 if not rev and revs:
1481 rev = revs[0]
1488 rev = revs[0]
1482 if not rev:
1489 if not rev:
1483 rev = "tip"
1490 rev = "tip"
1484 if num or branch or tags:
1491 if num or branch or tags:
1485 raise util.Abort(
1492 raise util.Abort(
1486 "can't query remote revision number, branch, or tags")
1493 "can't query remote revision number, branch, or tags")
1487 output = [hexfunc(srepo.lookup(rev))]
1494 output = [hexfunc(srepo.lookup(rev))]
1488 elif not rev:
1495 elif not rev:
1489 ctx = repo.workingctx()
1496 ctx = repo.workingctx()
1490 parents = ctx.parents()
1497 parents = ctx.parents()
1491 changed = False
1498 changed = False
1492 if default or id or num:
1499 if default or id or num:
1493 changed = ctx.files() + ctx.deleted()
1500 changed = ctx.files() + ctx.deleted()
1494 if default or id:
1501 if default or id:
1495 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1502 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1496 (changed) and "+" or "")]
1503 (changed) and "+" or "")]
1497 if num:
1504 if num:
1498 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1505 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1499 (changed) and "+" or ""))
1506 (changed) and "+" or ""))
1500 else:
1507 else:
1501 ctx = repo.changectx(rev)
1508 ctx = repo.changectx(rev)
1502 if default or id:
1509 if default or id:
1503 output = [hexfunc(ctx.node())]
1510 output = [hexfunc(ctx.node())]
1504 if num:
1511 if num:
1505 output.append(str(ctx.rev()))
1512 output.append(str(ctx.rev()))
1506
1513
1507 if not source and default and not ui.quiet:
1514 if not source and default and not ui.quiet:
1508 b = util.tolocal(ctx.branch())
1515 b = util.tolocal(ctx.branch())
1509 if b != 'default':
1516 if b != 'default':
1510 output.append("(%s)" % b)
1517 output.append("(%s)" % b)
1511
1518
1512 # multiple tags for a single parent separated by '/'
1519 # multiple tags for a single parent separated by '/'
1513 t = "/".join(ctx.tags())
1520 t = "/".join(ctx.tags())
1514 if t:
1521 if t:
1515 output.append(t)
1522 output.append(t)
1516
1523
1517 if branch:
1524 if branch:
1518 output.append(util.tolocal(ctx.branch()))
1525 output.append(util.tolocal(ctx.branch()))
1519
1526
1520 if tags:
1527 if tags:
1521 output.extend(ctx.tags())
1528 output.extend(ctx.tags())
1522
1529
1523 ui.write("%s\n" % ' '.join(output))
1530 ui.write("%s\n" % ' '.join(output))
1524
1531
1525 def import_(ui, repo, patch1, *patches, **opts):
1532 def import_(ui, repo, patch1, *patches, **opts):
1526 """import an ordered set of patches
1533 """import an ordered set of patches
1527
1534
1528 Import a list of patches and commit them individually.
1535 Import a list of patches and commit them individually.
1529
1536
1530 If there are outstanding changes in the working directory, import
1537 If there are outstanding changes in the working directory, import
1531 will abort unless given the -f flag.
1538 will abort unless given the -f flag.
1532
1539
1533 You can import a patch straight from a mail message. Even patches
1540 You can import a patch straight from a mail message. Even patches
1534 as attachments work (body part must be type text/plain or
1541 as attachments work (body part must be type text/plain or
1535 text/x-patch to be used). From and Subject headers of email
1542 text/x-patch to be used). From and Subject headers of email
1536 message are used as default committer and commit message. All
1543 message are used as default committer and commit message. All
1537 text/plain body parts before first diff are added to commit
1544 text/plain body parts before first diff are added to commit
1538 message.
1545 message.
1539
1546
1540 If the imported patch was generated by hg export, user and description
1547 If the imported patch was generated by hg export, user and description
1541 from patch override values from message headers and body. Values
1548 from patch override values from message headers and body. Values
1542 given on command line with -m and -u override these.
1549 given on command line with -m and -u override these.
1543
1550
1544 If --exact is specified, import will set the working directory
1551 If --exact is specified, import will set the working directory
1545 to the parent of each patch before applying it, and will abort
1552 to the parent of each patch before applying it, and will abort
1546 if the resulting changeset has a different ID than the one
1553 if the resulting changeset has a different ID than the one
1547 recorded in the patch. This may happen due to character set
1554 recorded in the patch. This may happen due to character set
1548 problems or other deficiencies in the text patch format.
1555 problems or other deficiencies in the text patch format.
1549
1556
1550 To read a patch from standard input, use patch name "-".
1557 To read a patch from standard input, use patch name "-".
1551 """
1558 """
1552 patches = (patch1,) + patches
1559 patches = (patch1,) + patches
1553
1560
1554 if opts.get('exact') or not opts['force']:
1561 if opts.get('exact') or not opts['force']:
1555 cmdutil.bail_if_changed(repo)
1562 cmdutil.bail_if_changed(repo)
1556
1563
1557 d = opts["base"]
1564 d = opts["base"]
1558 strip = opts["strip"]
1565 strip = opts["strip"]
1559 wlock = lock = None
1566 wlock = lock = None
1560 try:
1567 try:
1561 wlock = repo.wlock()
1568 wlock = repo.wlock()
1562 lock = repo.lock()
1569 lock = repo.lock()
1563 for p in patches:
1570 for p in patches:
1564 pf = os.path.join(d, p)
1571 pf = os.path.join(d, p)
1565
1572
1566 if pf == '-':
1573 if pf == '-':
1567 ui.status(_("applying patch from stdin\n"))
1574 ui.status(_("applying patch from stdin\n"))
1568 data = patch.extract(ui, sys.stdin)
1575 data = patch.extract(ui, sys.stdin)
1569 else:
1576 else:
1570 ui.status(_("applying %s\n") % p)
1577 ui.status(_("applying %s\n") % p)
1571 if os.path.exists(pf):
1578 if os.path.exists(pf):
1572 data = patch.extract(ui, file(pf, 'rb'))
1579 data = patch.extract(ui, file(pf, 'rb'))
1573 else:
1580 else:
1574 data = patch.extract(ui, urllib.urlopen(pf))
1581 data = patch.extract(ui, urllib.urlopen(pf))
1575 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1582 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1576
1583
1577 if tmpname is None:
1584 if tmpname is None:
1578 raise util.Abort(_('no diffs found'))
1585 raise util.Abort(_('no diffs found'))
1579
1586
1580 try:
1587 try:
1581 cmdline_message = cmdutil.logmessage(opts)
1588 cmdline_message = cmdutil.logmessage(opts)
1582 if cmdline_message:
1589 if cmdline_message:
1583 # pickup the cmdline msg
1590 # pickup the cmdline msg
1584 message = cmdline_message
1591 message = cmdline_message
1585 elif message:
1592 elif message:
1586 # pickup the patch msg
1593 # pickup the patch msg
1587 message = message.strip()
1594 message = message.strip()
1588 else:
1595 else:
1589 # launch the editor
1596 # launch the editor
1590 message = None
1597 message = None
1591 ui.debug(_('message:\n%s\n') % message)
1598 ui.debug(_('message:\n%s\n') % message)
1592
1599
1593 wp = repo.workingctx().parents()
1600 wp = repo.workingctx().parents()
1594 if opts.get('exact'):
1601 if opts.get('exact'):
1595 if not nodeid or not p1:
1602 if not nodeid or not p1:
1596 raise util.Abort(_('not a mercurial patch'))
1603 raise util.Abort(_('not a mercurial patch'))
1597 p1 = repo.lookup(p1)
1604 p1 = repo.lookup(p1)
1598 p2 = repo.lookup(p2 or hex(nullid))
1605 p2 = repo.lookup(p2 or hex(nullid))
1599
1606
1600 if p1 != wp[0].node():
1607 if p1 != wp[0].node():
1601 hg.clean(repo, p1)
1608 hg.clean(repo, p1)
1602 repo.dirstate.setparents(p1, p2)
1609 repo.dirstate.setparents(p1, p2)
1603 elif p2:
1610 elif p2:
1604 try:
1611 try:
1605 p1 = repo.lookup(p1)
1612 p1 = repo.lookup(p1)
1606 p2 = repo.lookup(p2)
1613 p2 = repo.lookup(p2)
1607 if p1 == wp[0].node():
1614 if p1 == wp[0].node():
1608 repo.dirstate.setparents(p1, p2)
1615 repo.dirstate.setparents(p1, p2)
1609 except hg.RepoError:
1616 except hg.RepoError:
1610 pass
1617 pass
1611 if opts.get('exact') or opts.get('import_branch'):
1618 if opts.get('exact') or opts.get('import_branch'):
1612 repo.dirstate.setbranch(branch or 'default')
1619 repo.dirstate.setbranch(branch or 'default')
1613
1620
1614 files = {}
1621 files = {}
1615 try:
1622 try:
1616 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1623 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1617 files=files)
1624 files=files)
1618 finally:
1625 finally:
1619 files = patch.updatedir(ui, repo, files)
1626 files = patch.updatedir(ui, repo, files)
1620 n = repo.commit(files, message, user, date)
1627 n = repo.commit(files, message, user, date)
1621 if opts.get('exact'):
1628 if opts.get('exact'):
1622 if hex(n) != nodeid:
1629 if hex(n) != nodeid:
1623 repo.rollback()
1630 repo.rollback()
1624 raise util.Abort(_('patch is damaged' +
1631 raise util.Abort(_('patch is damaged' +
1625 ' or loses information'))
1632 ' or loses information'))
1626 finally:
1633 finally:
1627 os.unlink(tmpname)
1634 os.unlink(tmpname)
1628 finally:
1635 finally:
1629 del lock, wlock
1636 del lock, wlock
1630
1637
1631 def incoming(ui, repo, source="default", **opts):
1638 def incoming(ui, repo, source="default", **opts):
1632 """show new changesets found in source
1639 """show new changesets found in source
1633
1640
1634 Show new changesets found in the specified path/URL or the default
1641 Show new changesets found in the specified path/URL or the default
1635 pull location. These are the changesets that would be pulled if a pull
1642 pull location. These are the changesets that would be pulled if a pull
1636 was requested.
1643 was requested.
1637
1644
1638 For remote repository, using --bundle avoids downloading the changesets
1645 For remote repository, using --bundle avoids downloading the changesets
1639 twice if the incoming is followed by a pull.
1646 twice if the incoming is followed by a pull.
1640
1647
1641 See pull for valid source format details.
1648 See pull for valid source format details.
1642 """
1649 """
1643 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
1650 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
1644 cmdutil.setremoteconfig(ui, opts)
1651 cmdutil.setremoteconfig(ui, opts)
1645
1652
1646 other = hg.repository(ui, source)
1653 other = hg.repository(ui, source)
1647 ui.status(_('comparing with %s\n') % source)
1654 ui.status(_('comparing with %s\n') % source)
1648 if revs:
1655 if revs:
1649 revs = [other.lookup(rev) for rev in revs]
1656 revs = [other.lookup(rev) for rev in revs]
1650 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1657 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1651 if not incoming:
1658 if not incoming:
1652 try:
1659 try:
1653 os.unlink(opts["bundle"])
1660 os.unlink(opts["bundle"])
1654 except:
1661 except:
1655 pass
1662 pass
1656 ui.status(_("no changes found\n"))
1663 ui.status(_("no changes found\n"))
1657 return 1
1664 return 1
1658
1665
1659 cleanup = None
1666 cleanup = None
1660 try:
1667 try:
1661 fname = opts["bundle"]
1668 fname = opts["bundle"]
1662 if fname or not other.local():
1669 if fname or not other.local():
1663 # create a bundle (uncompressed if other repo is not local)
1670 # create a bundle (uncompressed if other repo is not local)
1664 if revs is None:
1671 if revs is None:
1665 cg = other.changegroup(incoming, "incoming")
1672 cg = other.changegroup(incoming, "incoming")
1666 else:
1673 else:
1667 cg = other.changegroupsubset(incoming, revs, 'incoming')
1674 cg = other.changegroupsubset(incoming, revs, 'incoming')
1668 bundletype = other.local() and "HG10BZ" or "HG10UN"
1675 bundletype = other.local() and "HG10BZ" or "HG10UN"
1669 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1676 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1670 # keep written bundle?
1677 # keep written bundle?
1671 if opts["bundle"]:
1678 if opts["bundle"]:
1672 cleanup = None
1679 cleanup = None
1673 if not other.local():
1680 if not other.local():
1674 # use the created uncompressed bundlerepo
1681 # use the created uncompressed bundlerepo
1675 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1682 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1676
1683
1677 o = other.changelog.nodesbetween(incoming, revs)[0]
1684 o = other.changelog.nodesbetween(incoming, revs)[0]
1678 if opts['newest_first']:
1685 if opts['newest_first']:
1679 o.reverse()
1686 o.reverse()
1680 displayer = cmdutil.show_changeset(ui, other, opts)
1687 displayer = cmdutil.show_changeset(ui, other, opts)
1681 for n in o:
1688 for n in o:
1682 parents = [p for p in other.changelog.parents(n) if p != nullid]
1689 parents = [p for p in other.changelog.parents(n) if p != nullid]
1683 if opts['no_merges'] and len(parents) == 2:
1690 if opts['no_merges'] and len(parents) == 2:
1684 continue
1691 continue
1685 displayer.show(changenode=n)
1692 displayer.show(changenode=n)
1686 finally:
1693 finally:
1687 if hasattr(other, 'close'):
1694 if hasattr(other, 'close'):
1688 other.close()
1695 other.close()
1689 if cleanup:
1696 if cleanup:
1690 os.unlink(cleanup)
1697 os.unlink(cleanup)
1691
1698
1692 def init(ui, dest=".", **opts):
1699 def init(ui, dest=".", **opts):
1693 """create a new repository in the given directory
1700 """create a new repository in the given directory
1694
1701
1695 Initialize a new repository in the given directory. If the given
1702 Initialize a new repository in the given directory. If the given
1696 directory does not exist, it is created.
1703 directory does not exist, it is created.
1697
1704
1698 If no directory is given, the current directory is used.
1705 If no directory is given, the current directory is used.
1699
1706
1700 It is possible to specify an ssh:// URL as the destination.
1707 It is possible to specify an ssh:// URL as the destination.
1701 Look at the help text for the pull command for important details
1708 Look at the help text for the pull command for important details
1702 about ssh:// URLs.
1709 about ssh:// URLs.
1703 """
1710 """
1704 cmdutil.setremoteconfig(ui, opts)
1711 cmdutil.setremoteconfig(ui, opts)
1705 hg.repository(ui, dest, create=1)
1712 hg.repository(ui, dest, create=1)
1706
1713
1707 def locate(ui, repo, *pats, **opts):
1714 def locate(ui, repo, *pats, **opts):
1708 """locate files matching specific patterns
1715 """locate files matching specific patterns
1709
1716
1710 Print all files under Mercurial control whose names match the
1717 Print all files under Mercurial control whose names match the
1711 given patterns.
1718 given patterns.
1712
1719
1713 This command searches the entire repository by default. To search
1720 This command searches the entire repository by default. To search
1714 just the current directory and its subdirectories, use
1721 just the current directory and its subdirectories, use
1715 "--include .".
1722 "--include .".
1716
1723
1717 If no patterns are given to match, this command prints all file
1724 If no patterns are given to match, this command prints all file
1718 names.
1725 names.
1719
1726
1720 If you want to feed the output of this command into the "xargs"
1727 If you want to feed the output of this command into the "xargs"
1721 command, use the "-0" option to both this command and "xargs".
1728 command, use the "-0" option to both this command and "xargs".
1722 This will avoid the problem of "xargs" treating single filenames
1729 This will avoid the problem of "xargs" treating single filenames
1723 that contain white space as multiple filenames.
1730 that contain white space as multiple filenames.
1724 """
1731 """
1725 end = opts['print0'] and '\0' or '\n'
1732 end = opts['print0'] and '\0' or '\n'
1726 rev = opts['rev']
1733 rev = opts['rev']
1727 if rev:
1734 if rev:
1728 node = repo.lookup(rev)
1735 node = repo.lookup(rev)
1729 else:
1736 else:
1730 node = None
1737 node = None
1731
1738
1732 ret = 1
1739 ret = 1
1733 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1740 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1734 badmatch=util.always,
1741 badmatch=util.always,
1735 default='relglob'):
1742 default='relglob'):
1736 if src == 'b':
1743 if src == 'b':
1737 continue
1744 continue
1738 if not node and abs not in repo.dirstate:
1745 if not node and abs not in repo.dirstate:
1739 continue
1746 continue
1740 if opts['fullpath']:
1747 if opts['fullpath']:
1741 ui.write(os.path.join(repo.root, abs), end)
1748 ui.write(os.path.join(repo.root, abs), end)
1742 else:
1749 else:
1743 ui.write(((pats and rel) or abs), end)
1750 ui.write(((pats and rel) or abs), end)
1744 ret = 0
1751 ret = 0
1745
1752
1746 return ret
1753 return ret
1747
1754
1748 def log(ui, repo, *pats, **opts):
1755 def log(ui, repo, *pats, **opts):
1749 """show revision history of entire repository or files
1756 """show revision history of entire repository or files
1750
1757
1751 Print the revision history of the specified files or the entire
1758 Print the revision history of the specified files or the entire
1752 project.
1759 project.
1753
1760
1754 File history is shown without following rename or copy history of
1761 File history is shown without following rename or copy history of
1755 files. Use -f/--follow with a file name to follow history across
1762 files. Use -f/--follow with a file name to follow history across
1756 renames and copies. --follow without a file name will only show
1763 renames and copies. --follow without a file name will only show
1757 ancestors or descendants of the starting revision. --follow-first
1764 ancestors or descendants of the starting revision. --follow-first
1758 only follows the first parent of merge revisions.
1765 only follows the first parent of merge revisions.
1759
1766
1760 If no revision range is specified, the default is tip:0 unless
1767 If no revision range is specified, the default is tip:0 unless
1761 --follow is set, in which case the working directory parent is
1768 --follow is set, in which case the working directory parent is
1762 used as the starting revision.
1769 used as the starting revision.
1763
1770
1764 By default this command outputs: changeset id and hash, tags,
1771 By default this command outputs: changeset id and hash, tags,
1765 non-trivial parents, user, date and time, and a summary for each
1772 non-trivial parents, user, date and time, and a summary for each
1766 commit. When the -v/--verbose switch is used, the list of changed
1773 commit. When the -v/--verbose switch is used, the list of changed
1767 files and full commit message is shown.
1774 files and full commit message is shown.
1768
1775
1769 NOTE: log -p may generate unexpected diff output for merge
1776 NOTE: log -p may generate unexpected diff output for merge
1770 changesets, as it will compare the merge changeset against its
1777 changesets, as it will compare the merge changeset against its
1771 first parent only. Also, the files: list will only reflect files
1778 first parent only. Also, the files: list will only reflect files
1772 that are different from BOTH parents.
1779 that are different from BOTH parents.
1773
1780
1774 """
1781 """
1775
1782
1776 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1783 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1777 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1784 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1778
1785
1779 if opts['limit']:
1786 if opts['limit']:
1780 try:
1787 try:
1781 limit = int(opts['limit'])
1788 limit = int(opts['limit'])
1782 except ValueError:
1789 except ValueError:
1783 raise util.Abort(_('limit must be a positive integer'))
1790 raise util.Abort(_('limit must be a positive integer'))
1784 if limit <= 0: raise util.Abort(_('limit must be positive'))
1791 if limit <= 0: raise util.Abort(_('limit must be positive'))
1785 else:
1792 else:
1786 limit = sys.maxint
1793 limit = sys.maxint
1787 count = 0
1794 count = 0
1788
1795
1789 if opts['copies'] and opts['rev']:
1796 if opts['copies'] and opts['rev']:
1790 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1797 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1791 else:
1798 else:
1792 endrev = repo.changelog.count()
1799 endrev = repo.changelog.count()
1793 rcache = {}
1800 rcache = {}
1794 ncache = {}
1801 ncache = {}
1795 dcache = []
1802 dcache = []
1796 def getrenamed(fn, rev, man):
1803 def getrenamed(fn, rev, man):
1797 '''looks up all renames for a file (up to endrev) the first
1804 '''looks up all renames for a file (up to endrev) the first
1798 time the file is given. It indexes on the changerev and only
1805 time the file is given. It indexes on the changerev and only
1799 parses the manifest if linkrev != changerev.
1806 parses the manifest if linkrev != changerev.
1800 Returns rename info for fn at changerev rev.'''
1807 Returns rename info for fn at changerev rev.'''
1801 if fn not in rcache:
1808 if fn not in rcache:
1802 rcache[fn] = {}
1809 rcache[fn] = {}
1803 ncache[fn] = {}
1810 ncache[fn] = {}
1804 fl = repo.file(fn)
1811 fl = repo.file(fn)
1805 for i in xrange(fl.count()):
1812 for i in xrange(fl.count()):
1806 node = fl.node(i)
1813 node = fl.node(i)
1807 lr = fl.linkrev(node)
1814 lr = fl.linkrev(node)
1808 renamed = fl.renamed(node)
1815 renamed = fl.renamed(node)
1809 rcache[fn][lr] = renamed
1816 rcache[fn][lr] = renamed
1810 if renamed:
1817 if renamed:
1811 ncache[fn][node] = renamed
1818 ncache[fn][node] = renamed
1812 if lr >= endrev:
1819 if lr >= endrev:
1813 break
1820 break
1814 if rev in rcache[fn]:
1821 if rev in rcache[fn]:
1815 return rcache[fn][rev]
1822 return rcache[fn][rev]
1816 mr = repo.manifest.rev(man)
1823 mr = repo.manifest.rev(man)
1817 if repo.manifest.parentrevs(mr) != (mr - 1, nullrev):
1824 if repo.manifest.parentrevs(mr) != (mr - 1, nullrev):
1818 return ncache[fn].get(repo.manifest.find(man, fn)[0])
1825 return ncache[fn].get(repo.manifest.find(man, fn)[0])
1819 if not dcache or dcache[0] != man:
1826 if not dcache or dcache[0] != man:
1820 dcache[:] = [man, repo.manifest.readdelta(man)]
1827 dcache[:] = [man, repo.manifest.readdelta(man)]
1821 if fn in dcache[1]:
1828 if fn in dcache[1]:
1822 return ncache[fn].get(dcache[1][fn])
1829 return ncache[fn].get(dcache[1][fn])
1823 return None
1830 return None
1824
1831
1825 df = False
1832 df = False
1826 if opts["date"]:
1833 if opts["date"]:
1827 df = util.matchdate(opts["date"])
1834 df = util.matchdate(opts["date"])
1828
1835
1829 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1836 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1830 for st, rev, fns in changeiter:
1837 for st, rev, fns in changeiter:
1831 if st == 'add':
1838 if st == 'add':
1832 changenode = repo.changelog.node(rev)
1839 changenode = repo.changelog.node(rev)
1833 parents = [p for p in repo.changelog.parentrevs(rev)
1840 parents = [p for p in repo.changelog.parentrevs(rev)
1834 if p != nullrev]
1841 if p != nullrev]
1835 if opts['no_merges'] and len(parents) == 2:
1842 if opts['no_merges'] and len(parents) == 2:
1836 continue
1843 continue
1837 if opts['only_merges'] and len(parents) != 2:
1844 if opts['only_merges'] and len(parents) != 2:
1838 continue
1845 continue
1839
1846
1840 if df:
1847 if df:
1841 changes = get(rev)
1848 changes = get(rev)
1842 if not df(changes[2][0]):
1849 if not df(changes[2][0]):
1843 continue
1850 continue
1844
1851
1845 if opts['keyword']:
1852 if opts['keyword']:
1846 changes = get(rev)
1853 changes = get(rev)
1847 miss = 0
1854 miss = 0
1848 for k in [kw.lower() for kw in opts['keyword']]:
1855 for k in [kw.lower() for kw in opts['keyword']]:
1849 if not (k in changes[1].lower() or
1856 if not (k in changes[1].lower() or
1850 k in changes[4].lower() or
1857 k in changes[4].lower() or
1851 k in " ".join(changes[3]).lower()):
1858 k in " ".join(changes[3]).lower()):
1852 miss = 1
1859 miss = 1
1853 break
1860 break
1854 if miss:
1861 if miss:
1855 continue
1862 continue
1856
1863
1857 copies = []
1864 copies = []
1858 if opts.get('copies') and rev:
1865 if opts.get('copies') and rev:
1859 mf = get(rev)[0]
1866 mf = get(rev)[0]
1860 for fn in get(rev)[3]:
1867 for fn in get(rev)[3]:
1861 rename = getrenamed(fn, rev, mf)
1868 rename = getrenamed(fn, rev, mf)
1862 if rename:
1869 if rename:
1863 copies.append((fn, rename[0]))
1870 copies.append((fn, rename[0]))
1864 displayer.show(rev, changenode, copies=copies)
1871 displayer.show(rev, changenode, copies=copies)
1865 elif st == 'iter':
1872 elif st == 'iter':
1866 if count == limit: break
1873 if count == limit: break
1867 if displayer.flush(rev):
1874 if displayer.flush(rev):
1868 count += 1
1875 count += 1
1869
1876
1870 def manifest(ui, repo, node=None, rev=None):
1877 def manifest(ui, repo, node=None, rev=None):
1871 """output the current or given revision of the project manifest
1878 """output the current or given revision of the project manifest
1872
1879
1873 Print a list of version controlled files for the given revision.
1880 Print a list of version controlled files for the given revision.
1874 If no revision is given, the parent of the working directory is used,
1881 If no revision is given, the parent of the working directory is used,
1875 or tip if no revision is checked out.
1882 or tip if no revision is checked out.
1876
1883
1877 The manifest is the list of files being version controlled. If no revision
1884 The manifest is the list of files being version controlled. If no revision
1878 is given then the first parent of the working directory is used.
1885 is given then the first parent of the working directory is used.
1879
1886
1880 With -v flag, print file permissions. With --debug flag, print
1887 With -v flag, print file permissions. With --debug flag, print
1881 file revision hashes.
1888 file revision hashes.
1882 """
1889 """
1883
1890
1884 if rev and node:
1891 if rev and node:
1885 raise util.Abort(_("please specify just one revision"))
1892 raise util.Abort(_("please specify just one revision"))
1886
1893
1887 if not node:
1894 if not node:
1888 node = rev
1895 node = rev
1889
1896
1890 m = repo.changectx(node).manifest()
1897 m = repo.changectx(node).manifest()
1891 files = m.keys()
1898 files = m.keys()
1892 files.sort()
1899 files.sort()
1893
1900
1894 for f in files:
1901 for f in files:
1895 if ui.debugflag:
1902 if ui.debugflag:
1896 ui.write("%40s " % hex(m[f]))
1903 ui.write("%40s " % hex(m[f]))
1897 if ui.verbose:
1904 if ui.verbose:
1898 ui.write("%3s " % (m.execf(f) and "755" or "644"))
1905 ui.write("%3s " % (m.execf(f) and "755" or "644"))
1899 ui.write("%s\n" % f)
1906 ui.write("%s\n" % f)
1900
1907
1901 def merge(ui, repo, node=None, force=None, rev=None):
1908 def merge(ui, repo, node=None, force=None, rev=None):
1902 """merge working directory with another revision
1909 """merge working directory with another revision
1903
1910
1904 Merge the contents of the current working directory and the
1911 Merge the contents of the current working directory and the
1905 requested revision. Files that changed between either parent are
1912 requested revision. Files that changed between either parent are
1906 marked as changed for the next commit and a commit must be
1913 marked as changed for the next commit and a commit must be
1907 performed before any further updates are allowed.
1914 performed before any further updates are allowed.
1908
1915
1909 If no revision is specified, the working directory's parent is a
1916 If no revision is specified, the working directory's parent is a
1910 head revision, and the repository contains exactly one other head,
1917 head revision, and the repository contains exactly one other head,
1911 the other head is merged with by default. Otherwise, an explicit
1918 the other head is merged with by default. Otherwise, an explicit
1912 revision to merge with must be provided.
1919 revision to merge with must be provided.
1913 """
1920 """
1914
1921
1915 if rev and node:
1922 if rev and node:
1916 raise util.Abort(_("please specify just one revision"))
1923 raise util.Abort(_("please specify just one revision"))
1917 if not node:
1924 if not node:
1918 node = rev
1925 node = rev
1919
1926
1920 if not node:
1927 if not node:
1921 heads = repo.heads()
1928 heads = repo.heads()
1922 if len(heads) > 2:
1929 if len(heads) > 2:
1923 raise util.Abort(_('repo has %d heads - '
1930 raise util.Abort(_('repo has %d heads - '
1924 'please merge with an explicit rev') %
1931 'please merge with an explicit rev') %
1925 len(heads))
1932 len(heads))
1926 parent = repo.dirstate.parents()[0]
1933 parent = repo.dirstate.parents()[0]
1927 if len(heads) == 1:
1934 if len(heads) == 1:
1928 msg = _('there is nothing to merge')
1935 msg = _('there is nothing to merge')
1929 if parent != repo.lookup(repo.workingctx().branch()):
1936 if parent != repo.lookup(repo.workingctx().branch()):
1930 msg = _('%s - use "hg update" instead' % msg)
1937 msg = _('%s - use "hg update" instead' % msg)
1931 raise util.Abort(msg)
1938 raise util.Abort(msg)
1932
1939
1933 if parent not in heads:
1940 if parent not in heads:
1934 raise util.Abort(_('working dir not at a head rev - '
1941 raise util.Abort(_('working dir not at a head rev - '
1935 'use "hg update" or merge with an explicit rev'))
1942 'use "hg update" or merge with an explicit rev'))
1936 node = parent == heads[0] and heads[-1] or heads[0]
1943 node = parent == heads[0] and heads[-1] or heads[0]
1937 return hg.merge(repo, node, force=force)
1944 return hg.merge(repo, node, force=force)
1938
1945
1939 def outgoing(ui, repo, dest=None, **opts):
1946 def outgoing(ui, repo, dest=None, **opts):
1940 """show changesets not found in destination
1947 """show changesets not found in destination
1941
1948
1942 Show changesets not found in the specified destination repository or
1949 Show changesets not found in the specified destination repository or
1943 the default push location. These are the changesets that would be pushed
1950 the default push location. These are the changesets that would be pushed
1944 if a push was requested.
1951 if a push was requested.
1945
1952
1946 See pull for valid destination format details.
1953 See pull for valid destination format details.
1947 """
1954 """
1948 dest, revs, checkout = hg.parseurl(
1955 dest, revs, checkout = hg.parseurl(
1949 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1956 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1950 cmdutil.setremoteconfig(ui, opts)
1957 cmdutil.setremoteconfig(ui, opts)
1951 if revs:
1958 if revs:
1952 revs = [repo.lookup(rev) for rev in revs]
1959 revs = [repo.lookup(rev) for rev in revs]
1953
1960
1954 other = hg.repository(ui, dest)
1961 other = hg.repository(ui, dest)
1955 ui.status(_('comparing with %s\n') % dest)
1962 ui.status(_('comparing with %s\n') % dest)
1956 o = repo.findoutgoing(other, force=opts['force'])
1963 o = repo.findoutgoing(other, force=opts['force'])
1957 if not o:
1964 if not o:
1958 ui.status(_("no changes found\n"))
1965 ui.status(_("no changes found\n"))
1959 return 1
1966 return 1
1960 o = repo.changelog.nodesbetween(o, revs)[0]
1967 o = repo.changelog.nodesbetween(o, revs)[0]
1961 if opts['newest_first']:
1968 if opts['newest_first']:
1962 o.reverse()
1969 o.reverse()
1963 displayer = cmdutil.show_changeset(ui, repo, opts)
1970 displayer = cmdutil.show_changeset(ui, repo, opts)
1964 for n in o:
1971 for n in o:
1965 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1972 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1966 if opts['no_merges'] and len(parents) == 2:
1973 if opts['no_merges'] and len(parents) == 2:
1967 continue
1974 continue
1968 displayer.show(changenode=n)
1975 displayer.show(changenode=n)
1969
1976
1970 def parents(ui, repo, file_=None, **opts):
1977 def parents(ui, repo, file_=None, **opts):
1971 """show the parents of the working dir or revision
1978 """show the parents of the working dir or revision
1972
1979
1973 Print the working directory's parent revisions. If a
1980 Print the working directory's parent revisions. If a
1974 revision is given via --rev, the parent of that revision
1981 revision is given via --rev, the parent of that revision
1975 will be printed. If a file argument is given, revision in
1982 will be printed. If a file argument is given, revision in
1976 which the file was last changed (before the working directory
1983 which the file was last changed (before the working directory
1977 revision or the argument to --rev if given) is printed.
1984 revision or the argument to --rev if given) is printed.
1978 """
1985 """
1979 rev = opts.get('rev')
1986 rev = opts.get('rev')
1980 if rev:
1987 if rev:
1981 ctx = repo.changectx(rev)
1988 ctx = repo.changectx(rev)
1982 else:
1989 else:
1983 ctx = repo.workingctx()
1990 ctx = repo.workingctx()
1984
1991
1985 if file_:
1992 if file_:
1986 files, match, anypats = cmdutil.matchpats(repo, (file_,), opts)
1993 files, match, anypats = cmdutil.matchpats(repo, (file_,), opts)
1987 if anypats or len(files) != 1:
1994 if anypats or len(files) != 1:
1988 raise util.Abort(_('can only specify an explicit file name'))
1995 raise util.Abort(_('can only specify an explicit file name'))
1989 file_ = files[0]
1996 file_ = files[0]
1990 filenodes = []
1997 filenodes = []
1991 for cp in ctx.parents():
1998 for cp in ctx.parents():
1992 if not cp:
1999 if not cp:
1993 continue
2000 continue
1994 try:
2001 try:
1995 filenodes.append(cp.filenode(file_))
2002 filenodes.append(cp.filenode(file_))
1996 except revlog.LookupError:
2003 except revlog.LookupError:
1997 pass
2004 pass
1998 if not filenodes:
2005 if not filenodes:
1999 raise util.Abort(_("'%s' not found in manifest!") % file_)
2006 raise util.Abort(_("'%s' not found in manifest!") % file_)
2000 fl = repo.file(file_)
2007 fl = repo.file(file_)
2001 p = [repo.lookup(fl.linkrev(fn)) for fn in filenodes]
2008 p = [repo.lookup(fl.linkrev(fn)) for fn in filenodes]
2002 else:
2009 else:
2003 p = [cp.node() for cp in ctx.parents()]
2010 p = [cp.node() for cp in ctx.parents()]
2004
2011
2005 displayer = cmdutil.show_changeset(ui, repo, opts)
2012 displayer = cmdutil.show_changeset(ui, repo, opts)
2006 for n in p:
2013 for n in p:
2007 if n != nullid:
2014 if n != nullid:
2008 displayer.show(changenode=n)
2015 displayer.show(changenode=n)
2009
2016
2010 def paths(ui, repo, search=None):
2017 def paths(ui, repo, search=None):
2011 """show definition of symbolic path names
2018 """show definition of symbolic path names
2012
2019
2013 Show definition of symbolic path name NAME. If no name is given, show
2020 Show definition of symbolic path name NAME. If no name is given, show
2014 definition of available names.
2021 definition of available names.
2015
2022
2016 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2023 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2017 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2024 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2018 """
2025 """
2019 if search:
2026 if search:
2020 for name, path in ui.configitems("paths"):
2027 for name, path in ui.configitems("paths"):
2021 if name == search:
2028 if name == search:
2022 ui.write("%s\n" % path)
2029 ui.write("%s\n" % path)
2023 return
2030 return
2024 ui.warn(_("not found!\n"))
2031 ui.warn(_("not found!\n"))
2025 return 1
2032 return 1
2026 else:
2033 else:
2027 for name, path in ui.configitems("paths"):
2034 for name, path in ui.configitems("paths"):
2028 ui.write("%s = %s\n" % (name, path))
2035 ui.write("%s = %s\n" % (name, path))
2029
2036
2030 def postincoming(ui, repo, modheads, optupdate, checkout):
2037 def postincoming(ui, repo, modheads, optupdate, checkout):
2031 if modheads == 0:
2038 if modheads == 0:
2032 return
2039 return
2033 if optupdate:
2040 if optupdate:
2034 if modheads <= 1 or checkout:
2041 if modheads <= 1 or checkout:
2035 return hg.update(repo, checkout)
2042 return hg.update(repo, checkout)
2036 else:
2043 else:
2037 ui.status(_("not updating, since new heads added\n"))
2044 ui.status(_("not updating, since new heads added\n"))
2038 if modheads > 1:
2045 if modheads > 1:
2039 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2046 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2040 else:
2047 else:
2041 ui.status(_("(run 'hg update' to get a working copy)\n"))
2048 ui.status(_("(run 'hg update' to get a working copy)\n"))
2042
2049
2043 def pull(ui, repo, source="default", **opts):
2050 def pull(ui, repo, source="default", **opts):
2044 """pull changes from the specified source
2051 """pull changes from the specified source
2045
2052
2046 Pull changes from a remote repository to a local one.
2053 Pull changes from a remote repository to a local one.
2047
2054
2048 This finds all changes from the repository at the specified path
2055 This finds all changes from the repository at the specified path
2049 or URL and adds them to the local repository. By default, this
2056 or URL and adds them to the local repository. By default, this
2050 does not update the copy of the project in the working directory.
2057 does not update the copy of the project in the working directory.
2051
2058
2052 Valid URLs are of the form:
2059 Valid URLs are of the form:
2053
2060
2054 local/filesystem/path (or file://local/filesystem/path)
2061 local/filesystem/path (or file://local/filesystem/path)
2055 http://[user@]host[:port]/[path]
2062 http://[user@]host[:port]/[path]
2056 https://[user@]host[:port]/[path]
2063 https://[user@]host[:port]/[path]
2057 ssh://[user@]host[:port]/[path]
2064 ssh://[user@]host[:port]/[path]
2058 static-http://host[:port]/[path]
2065 static-http://host[:port]/[path]
2059
2066
2060 Paths in the local filesystem can either point to Mercurial
2067 Paths in the local filesystem can either point to Mercurial
2061 repositories or to bundle files (as created by 'hg bundle' or
2068 repositories or to bundle files (as created by 'hg bundle' or
2062 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
2069 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
2063 allows access to a Mercurial repository where you simply use a web
2070 allows access to a Mercurial repository where you simply use a web
2064 server to publish the .hg directory as static content.
2071 server to publish the .hg directory as static content.
2065
2072
2066 An optional identifier after # indicates a particular branch, tag,
2073 An optional identifier after # indicates a particular branch, tag,
2067 or changeset to pull.
2074 or changeset to pull.
2068
2075
2069 Some notes about using SSH with Mercurial:
2076 Some notes about using SSH with Mercurial:
2070 - SSH requires an accessible shell account on the destination machine
2077 - SSH requires an accessible shell account on the destination machine
2071 and a copy of hg in the remote path or specified with as remotecmd.
2078 and a copy of hg in the remote path or specified with as remotecmd.
2072 - path is relative to the remote user's home directory by default.
2079 - path is relative to the remote user's home directory by default.
2073 Use an extra slash at the start of a path to specify an absolute path:
2080 Use an extra slash at the start of a path to specify an absolute path:
2074 ssh://example.com//tmp/repository
2081 ssh://example.com//tmp/repository
2075 - Mercurial doesn't use its own compression via SSH; the right thing
2082 - Mercurial doesn't use its own compression via SSH; the right thing
2076 to do is to configure it in your ~/.ssh/config, e.g.:
2083 to do is to configure it in your ~/.ssh/config, e.g.:
2077 Host *.mylocalnetwork.example.com
2084 Host *.mylocalnetwork.example.com
2078 Compression no
2085 Compression no
2079 Host *
2086 Host *
2080 Compression yes
2087 Compression yes
2081 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2088 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2082 with the --ssh command line option.
2089 with the --ssh command line option.
2083 """
2090 """
2084 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
2091 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
2085 cmdutil.setremoteconfig(ui, opts)
2092 cmdutil.setremoteconfig(ui, opts)
2086
2093
2087 other = hg.repository(ui, source)
2094 other = hg.repository(ui, source)
2088 ui.status(_('pulling from %s\n') % (source))
2095 ui.status(_('pulling from %s\n') % (source))
2089 if revs:
2096 if revs:
2090 try:
2097 try:
2091 revs = [other.lookup(rev) for rev in revs]
2098 revs = [other.lookup(rev) for rev in revs]
2092 except repo.NoCapability:
2099 except repo.NoCapability:
2093 error = _("Other repository doesn't support revision lookup, "
2100 error = _("Other repository doesn't support revision lookup, "
2094 "so a rev cannot be specified.")
2101 "so a rev cannot be specified.")
2095 raise util.Abort(error)
2102 raise util.Abort(error)
2096
2103
2097 modheads = repo.pull(other, heads=revs, force=opts['force'])
2104 modheads = repo.pull(other, heads=revs, force=opts['force'])
2098 return postincoming(ui, repo, modheads, opts['update'], checkout)
2105 return postincoming(ui, repo, modheads, opts['update'], checkout)
2099
2106
2100 def push(ui, repo, dest=None, **opts):
2107 def push(ui, repo, dest=None, **opts):
2101 """push changes to the specified destination
2108 """push changes to the specified destination
2102
2109
2103 Push changes from the local repository to the given destination.
2110 Push changes from the local repository to the given destination.
2104
2111
2105 This is the symmetrical operation for pull. It helps to move
2112 This is the symmetrical operation for pull. It helps to move
2106 changes from the current repository to a different one. If the
2113 changes from the current repository to a different one. If the
2107 destination is local this is identical to a pull in that directory
2114 destination is local this is identical to a pull in that directory
2108 from the current one.
2115 from the current one.
2109
2116
2110 By default, push will refuse to run if it detects the result would
2117 By default, push will refuse to run if it detects the result would
2111 increase the number of remote heads. This generally indicates the
2118 increase the number of remote heads. This generally indicates the
2112 the client has forgotten to sync and merge before pushing.
2119 the client has forgotten to sync and merge before pushing.
2113
2120
2114 Valid URLs are of the form:
2121 Valid URLs are of the form:
2115
2122
2116 local/filesystem/path (or file://local/filesystem/path)
2123 local/filesystem/path (or file://local/filesystem/path)
2117 ssh://[user@]host[:port]/[path]
2124 ssh://[user@]host[:port]/[path]
2118 http://[user@]host[:port]/[path]
2125 http://[user@]host[:port]/[path]
2119 https://[user@]host[:port]/[path]
2126 https://[user@]host[:port]/[path]
2120
2127
2121 An optional identifier after # indicates a particular branch, tag,
2128 An optional identifier after # indicates a particular branch, tag,
2122 or changeset to push.
2129 or changeset to push.
2123
2130
2124 Look at the help text for the pull command for important details
2131 Look at the help text for the pull command for important details
2125 about ssh:// URLs.
2132 about ssh:// URLs.
2126
2133
2127 Pushing to http:// and https:// URLs is only possible, if this
2134 Pushing to http:// and https:// URLs is only possible, if this
2128 feature is explicitly enabled on the remote Mercurial server.
2135 feature is explicitly enabled on the remote Mercurial server.
2129 """
2136 """
2130 dest, revs, checkout = hg.parseurl(
2137 dest, revs, checkout = hg.parseurl(
2131 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2138 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2132 cmdutil.setremoteconfig(ui, opts)
2139 cmdutil.setremoteconfig(ui, opts)
2133
2140
2134 other = hg.repository(ui, dest)
2141 other = hg.repository(ui, dest)
2135 ui.status('pushing to %s\n' % (dest))
2142 ui.status('pushing to %s\n' % (dest))
2136 if revs:
2143 if revs:
2137 revs = [repo.lookup(rev) for rev in revs]
2144 revs = [repo.lookup(rev) for rev in revs]
2138 r = repo.push(other, opts['force'], revs=revs)
2145 r = repo.push(other, opts['force'], revs=revs)
2139 return r == 0
2146 return r == 0
2140
2147
2141 def rawcommit(ui, repo, *pats, **opts):
2148 def rawcommit(ui, repo, *pats, **opts):
2142 """raw commit interface (DEPRECATED)
2149 """raw commit interface (DEPRECATED)
2143
2150
2144 (DEPRECATED)
2151 (DEPRECATED)
2145 Lowlevel commit, for use in helper scripts.
2152 Lowlevel commit, for use in helper scripts.
2146
2153
2147 This command is not intended to be used by normal users, as it is
2154 This command is not intended to be used by normal users, as it is
2148 primarily useful for importing from other SCMs.
2155 primarily useful for importing from other SCMs.
2149
2156
2150 This command is now deprecated and will be removed in a future
2157 This command is now deprecated and will be removed in a future
2151 release, please use debugsetparents and commit instead.
2158 release, please use debugsetparents and commit instead.
2152 """
2159 """
2153
2160
2154 ui.warn(_("(the rawcommit command is deprecated)\n"))
2161 ui.warn(_("(the rawcommit command is deprecated)\n"))
2155
2162
2156 message = cmdutil.logmessage(opts)
2163 message = cmdutil.logmessage(opts)
2157
2164
2158 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
2165 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
2159 if opts['files']:
2166 if opts['files']:
2160 files += open(opts['files']).read().splitlines()
2167 files += open(opts['files']).read().splitlines()
2161
2168
2162 parents = [repo.lookup(p) for p in opts['parent']]
2169 parents = [repo.lookup(p) for p in opts['parent']]
2163
2170
2164 try:
2171 try:
2165 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2172 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2166 except ValueError, inst:
2173 except ValueError, inst:
2167 raise util.Abort(str(inst))
2174 raise util.Abort(str(inst))
2168
2175
2169 def recover(ui, repo):
2176 def recover(ui, repo):
2170 """roll back an interrupted transaction
2177 """roll back an interrupted transaction
2171
2178
2172 Recover from an interrupted commit or pull.
2179 Recover from an interrupted commit or pull.
2173
2180
2174 This command tries to fix the repository status after an interrupted
2181 This command tries to fix the repository status after an interrupted
2175 operation. It should only be necessary when Mercurial suggests it.
2182 operation. It should only be necessary when Mercurial suggests it.
2176 """
2183 """
2177 if repo.recover():
2184 if repo.recover():
2178 return hg.verify(repo)
2185 return hg.verify(repo)
2179 return 1
2186 return 1
2180
2187
2181 def remove(ui, repo, *pats, **opts):
2188 def remove(ui, repo, *pats, **opts):
2182 """remove the specified files on the next commit
2189 """remove the specified files on the next commit
2183
2190
2184 Schedule the indicated files for removal from the repository.
2191 Schedule the indicated files for removal from the repository.
2185
2192
2186 This only removes files from the current branch, not from the
2193 This only removes files from the current branch, not from the
2187 entire project history. If the files still exist in the working
2194 entire project history. If the files still exist in the working
2188 directory, they will be deleted from it. If invoked with --after,
2195 directory, they will be deleted from it. If invoked with --after,
2189 files are marked as removed, but not actually unlinked unless --force
2196 files are marked as removed, but not actually unlinked unless --force
2190 is also given. Without exact file names, --after will only mark
2197 is also given. Without exact file names, --after will only mark
2191 files as removed if they are no longer in the working directory.
2198 files as removed if they are no longer in the working directory.
2192
2199
2193 This command schedules the files to be removed at the next commit.
2200 This command schedules the files to be removed at the next commit.
2194 To undo a remove before that, see hg revert.
2201 To undo a remove before that, see hg revert.
2195
2202
2196 Modified files and added files are not removed by default. To
2203 Modified files and added files are not removed by default. To
2197 remove them, use the -f/--force option.
2204 remove them, use the -f/--force option.
2198 """
2205 """
2199 if not opts['after'] and not pats:
2206 if not opts['after'] and not pats:
2200 raise util.Abort(_('no files specified'))
2207 raise util.Abort(_('no files specified'))
2201 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2208 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2202 exact = dict.fromkeys(files)
2209 exact = dict.fromkeys(files)
2203 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2210 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2204 modified, added, removed, deleted, unknown = mardu
2211 modified, added, removed, deleted, unknown = mardu
2205 remove, forget = [], []
2212 remove, forget = [], []
2206 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2213 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2207 reason = None
2214 reason = None
2208 if abs in modified and not opts['force']:
2215 if abs in modified and not opts['force']:
2209 reason = _('is modified (use -f to force removal)')
2216 reason = _('is modified (use -f to force removal)')
2210 elif abs in added:
2217 elif abs in added:
2211 if opts['force']:
2218 if opts['force']:
2212 forget.append(abs)
2219 forget.append(abs)
2213 continue
2220 continue
2214 reason = _('has been marked for add (use -f to force removal)')
2221 reason = _('has been marked for add (use -f to force removal)')
2215 elif abs not in repo.dirstate:
2222 elif abs not in repo.dirstate:
2216 reason = _('is not managed')
2223 reason = _('is not managed')
2217 elif opts['after'] and not exact and abs not in deleted:
2224 elif opts['after'] and not exact and abs not in deleted:
2218 continue
2225 continue
2219 elif abs in removed:
2226 elif abs in removed:
2220 continue
2227 continue
2221 if reason:
2228 if reason:
2222 if exact:
2229 if exact:
2223 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2230 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2224 else:
2231 else:
2225 if ui.verbose or not exact:
2232 if ui.verbose or not exact:
2226 ui.status(_('removing %s\n') % rel)
2233 ui.status(_('removing %s\n') % rel)
2227 remove.append(abs)
2234 remove.append(abs)
2228 repo.forget(forget)
2235 repo.forget(forget)
2229 repo.remove(remove, unlink=opts['force'] or not opts['after'])
2236 repo.remove(remove, unlink=opts['force'] or not opts['after'])
2230
2237
2231 def rename(ui, repo, *pats, **opts):
2238 def rename(ui, repo, *pats, **opts):
2232 """rename files; equivalent of copy + remove
2239 """rename files; equivalent of copy + remove
2233
2240
2234 Mark dest as copies of sources; mark sources for deletion. If
2241 Mark dest as copies of sources; mark sources for deletion. If
2235 dest is a directory, copies are put in that directory. If dest is
2242 dest is a directory, copies are put in that directory. If dest is
2236 a file, there can only be one source.
2243 a file, there can only be one source.
2237
2244
2238 By default, this command copies the contents of files as they
2245 By default, this command copies the contents of files as they
2239 stand in the working directory. If invoked with --after, the
2246 stand in the working directory. If invoked with --after, the
2240 operation is recorded, but no copying is performed.
2247 operation is recorded, but no copying is performed.
2241
2248
2242 This command takes effect in the next commit. To undo a rename
2249 This command takes effect in the next commit. To undo a rename
2243 before that, see hg revert.
2250 before that, see hg revert.
2244 """
2251 """
2245 wlock = repo.wlock(False)
2252 wlock = repo.wlock(False)
2246 try:
2253 try:
2247 errs, copied = docopy(ui, repo, pats, opts)
2254 errs, copied = docopy(ui, repo, pats, opts)
2248 names = []
2255 names = []
2249 for abs, rel, exact in copied:
2256 for abs, rel, exact in copied:
2250 if ui.verbose or not exact:
2257 if ui.verbose or not exact:
2251 ui.status(_('removing %s\n') % rel)
2258 ui.status(_('removing %s\n') % rel)
2252 names.append(abs)
2259 names.append(abs)
2253 if not opts.get('dry_run'):
2260 if not opts.get('dry_run'):
2254 repo.remove(names, True)
2261 repo.remove(names, True)
2255 return errs
2262 return errs
2256 finally:
2263 finally:
2257 del wlock
2264 del wlock
2258
2265
2259 def revert(ui, repo, *pats, **opts):
2266 def revert(ui, repo, *pats, **opts):
2260 """revert files or dirs to their states as of some revision
2267 """revert files or dirs to their states as of some revision
2261
2268
2262 With no revision specified, revert the named files or directories
2269 With no revision specified, revert the named files or directories
2263 to the contents they had in the parent of the working directory.
2270 to the contents they had in the parent of the working directory.
2264 This restores the contents of the affected files to an unmodified
2271 This restores the contents of the affected files to an unmodified
2265 state and unschedules adds, removes, copies, and renames. If the
2272 state and unschedules adds, removes, copies, and renames. If the
2266 working directory has two parents, you must explicitly specify the
2273 working directory has two parents, you must explicitly specify the
2267 revision to revert to.
2274 revision to revert to.
2268
2275
2269 Modified files are saved with a .orig suffix before reverting.
2276 Modified files are saved with a .orig suffix before reverting.
2270 To disable these backups, use --no-backup.
2277 To disable these backups, use --no-backup.
2271
2278
2272 Using the -r option, revert the given files or directories to their
2279 Using the -r option, revert the given files or directories to their
2273 contents as of a specific revision. This can be helpful to "roll
2280 contents as of a specific revision. This can be helpful to "roll
2274 back" some or all of a change that should not have been committed.
2281 back" some or all of a change that should not have been committed.
2275
2282
2276 Revert modifies the working directory. It does not commit any
2283 Revert modifies the working directory. It does not commit any
2277 changes, or change the parent of the working directory. If you
2284 changes, or change the parent of the working directory. If you
2278 revert to a revision other than the parent of the working
2285 revert to a revision other than the parent of the working
2279 directory, the reverted files will thus appear modified
2286 directory, the reverted files will thus appear modified
2280 afterwards.
2287 afterwards.
2281
2288
2282 If a file has been deleted, it is restored. If the executable
2289 If a file has been deleted, it is restored. If the executable
2283 mode of a file was changed, it is reset.
2290 mode of a file was changed, it is reset.
2284
2291
2285 If names are given, all files matching the names are reverted.
2292 If names are given, all files matching the names are reverted.
2286
2293
2287 If no arguments are given, no files are reverted.
2294 If no arguments are given, no files are reverted.
2288 """
2295 """
2289
2296
2290 if opts["date"]:
2297 if opts["date"]:
2291 if opts["rev"]:
2298 if opts["rev"]:
2292 raise util.Abort(_("you can't specify a revision and a date"))
2299 raise util.Abort(_("you can't specify a revision and a date"))
2293 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2300 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2294
2301
2295 if not pats and not opts['all']:
2302 if not pats and not opts['all']:
2296 raise util.Abort(_('no files or directories specified; '
2303 raise util.Abort(_('no files or directories specified; '
2297 'use --all to revert the whole repo'))
2304 'use --all to revert the whole repo'))
2298
2305
2299 parent, p2 = repo.dirstate.parents()
2306 parent, p2 = repo.dirstate.parents()
2300 if not opts['rev'] and p2 != nullid:
2307 if not opts['rev'] and p2 != nullid:
2301 raise util.Abort(_('uncommitted merge - please provide a '
2308 raise util.Abort(_('uncommitted merge - please provide a '
2302 'specific revision'))
2309 'specific revision'))
2303 ctx = repo.changectx(opts['rev'])
2310 ctx = repo.changectx(opts['rev'])
2304 node = ctx.node()
2311 node = ctx.node()
2305 mf = ctx.manifest()
2312 mf = ctx.manifest()
2306 if node == parent:
2313 if node == parent:
2307 pmf = mf
2314 pmf = mf
2308 else:
2315 else:
2309 pmf = None
2316 pmf = None
2310
2317
2311 # need all matching names in dirstate and manifest of target rev,
2318 # need all matching names in dirstate and manifest of target rev,
2312 # so have to walk both. do not print errors if files exist in one
2319 # so have to walk both. do not print errors if files exist in one
2313 # but not other.
2320 # but not other.
2314
2321
2315 names = {}
2322 names = {}
2316 target_only = {}
2323 target_only = {}
2317
2324
2318 wlock = repo.wlock()
2325 wlock = repo.wlock()
2319 try:
2326 try:
2320 # walk dirstate.
2327 # walk dirstate.
2321 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2328 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2322 badmatch=mf.has_key):
2329 badmatch=mf.has_key):
2323 names[abs] = (rel, exact)
2330 names[abs] = (rel, exact)
2324 if src == 'b':
2331 if src == 'b':
2325 target_only[abs] = True
2332 target_only[abs] = True
2326
2333
2327 # walk target manifest.
2334 # walk target manifest.
2328
2335
2329 def badmatch(path):
2336 def badmatch(path):
2330 if path in names:
2337 if path in names:
2331 return True
2338 return True
2332 path_ = path + '/'
2339 path_ = path + '/'
2333 for f in names:
2340 for f in names:
2334 if f.startswith(path_):
2341 if f.startswith(path_):
2335 return True
2342 return True
2336 return False
2343 return False
2337
2344
2338 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2345 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2339 badmatch=badmatch):
2346 badmatch=badmatch):
2340 if abs in names or src == 'b':
2347 if abs in names or src == 'b':
2341 continue
2348 continue
2342 names[abs] = (rel, exact)
2349 names[abs] = (rel, exact)
2343 target_only[abs] = True
2350 target_only[abs] = True
2344
2351
2345 changes = repo.status(match=names.has_key)[:5]
2352 changes = repo.status(match=names.has_key)[:5]
2346 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2353 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2347
2354
2348 # if f is a rename, also revert the source
2355 # if f is a rename, also revert the source
2349 cwd = repo.getcwd()
2356 cwd = repo.getcwd()
2350 for f in added:
2357 for f in added:
2351 src = repo.dirstate.copied(f)
2358 src = repo.dirstate.copied(f)
2352 if src and src not in names and repo.dirstate[src] == 'r':
2359 if src and src not in names and repo.dirstate[src] == 'r':
2353 removed[src] = None
2360 removed[src] = None
2354 names[src] = (repo.pathto(src, cwd), True)
2361 names[src] = (repo.pathto(src, cwd), True)
2355
2362
2356 revert = ([], _('reverting %s\n'))
2363 revert = ([], _('reverting %s\n'))
2357 add = ([], _('adding %s\n'))
2364 add = ([], _('adding %s\n'))
2358 remove = ([], _('removing %s\n'))
2365 remove = ([], _('removing %s\n'))
2359 forget = ([], _('forgetting %s\n'))
2366 forget = ([], _('forgetting %s\n'))
2360 undelete = ([], _('undeleting %s\n'))
2367 undelete = ([], _('undeleting %s\n'))
2361 update = {}
2368 update = {}
2362
2369
2363 disptable = (
2370 disptable = (
2364 # dispatch table:
2371 # dispatch table:
2365 # file state
2372 # file state
2366 # action if in target manifest
2373 # action if in target manifest
2367 # action if not in target manifest
2374 # action if not in target manifest
2368 # make backup if in target manifest
2375 # make backup if in target manifest
2369 # make backup if not in target manifest
2376 # make backup if not in target manifest
2370 (modified, revert, remove, True, True),
2377 (modified, revert, remove, True, True),
2371 (added, revert, forget, True, False),
2378 (added, revert, forget, True, False),
2372 (removed, undelete, None, False, False),
2379 (removed, undelete, None, False, False),
2373 (deleted, revert, remove, False, False),
2380 (deleted, revert, remove, False, False),
2374 (unknown, add, None, True, False),
2381 (unknown, add, None, True, False),
2375 (target_only, add, None, False, False),
2382 (target_only, add, None, False, False),
2376 )
2383 )
2377
2384
2378 entries = names.items()
2385 entries = names.items()
2379 entries.sort()
2386 entries.sort()
2380
2387
2381 for abs, (rel, exact) in entries:
2388 for abs, (rel, exact) in entries:
2382 mfentry = mf.get(abs)
2389 mfentry = mf.get(abs)
2383 target = repo.wjoin(abs)
2390 target = repo.wjoin(abs)
2384 def handle(xlist, dobackup):
2391 def handle(xlist, dobackup):
2385 xlist[0].append(abs)
2392 xlist[0].append(abs)
2386 update[abs] = 1
2393 update[abs] = 1
2387 if dobackup and not opts['no_backup'] and util.lexists(target):
2394 if dobackup and not opts['no_backup'] and util.lexists(target):
2388 bakname = "%s.orig" % rel
2395 bakname = "%s.orig" % rel
2389 ui.note(_('saving current version of %s as %s\n') %
2396 ui.note(_('saving current version of %s as %s\n') %
2390 (rel, bakname))
2397 (rel, bakname))
2391 if not opts.get('dry_run'):
2398 if not opts.get('dry_run'):
2392 util.copyfile(target, bakname)
2399 util.copyfile(target, bakname)
2393 if ui.verbose or not exact:
2400 if ui.verbose or not exact:
2394 ui.status(xlist[1] % rel)
2401 ui.status(xlist[1] % rel)
2395 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2402 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2396 if abs not in table: continue
2403 if abs not in table: continue
2397 # file has changed in dirstate
2404 # file has changed in dirstate
2398 if mfentry:
2405 if mfentry:
2399 handle(hitlist, backuphit)
2406 handle(hitlist, backuphit)
2400 elif misslist is not None:
2407 elif misslist is not None:
2401 handle(misslist, backupmiss)
2408 handle(misslist, backupmiss)
2402 else:
2409 else:
2403 if exact: ui.warn(_('file not managed: %s\n') % rel)
2410 if exact: ui.warn(_('file not managed: %s\n') % rel)
2404 break
2411 break
2405 else:
2412 else:
2406 # file has not changed in dirstate
2413 # file has not changed in dirstate
2407 if node == parent:
2414 if node == parent:
2408 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2415 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2409 continue
2416 continue
2410 if pmf is None:
2417 if pmf is None:
2411 # only need parent manifest in this unlikely case,
2418 # only need parent manifest in this unlikely case,
2412 # so do not read by default
2419 # so do not read by default
2413 pmf = repo.changectx(parent).manifest()
2420 pmf = repo.changectx(parent).manifest()
2414 if abs in pmf:
2421 if abs in pmf:
2415 if mfentry:
2422 if mfentry:
2416 # if version of file is same in parent and target
2423 # if version of file is same in parent and target
2417 # manifests, do nothing
2424 # manifests, do nothing
2418 if pmf[abs] != mfentry:
2425 if pmf[abs] != mfentry:
2419 handle(revert, False)
2426 handle(revert, False)
2420 else:
2427 else:
2421 handle(remove, False)
2428 handle(remove, False)
2422
2429
2423 if not opts.get('dry_run'):
2430 if not opts.get('dry_run'):
2424 for f in forget[0]:
2431 for f in forget[0]:
2425 repo.dirstate.forget(f)
2432 repo.dirstate.forget(f)
2426 r = hg.revert(repo, node, update.has_key)
2433 r = hg.revert(repo, node, update.has_key)
2427 for f in add[0]:
2434 for f in add[0]:
2428 repo.dirstate.add(f)
2435 repo.dirstate.add(f)
2429 for f in undelete[0]:
2436 for f in undelete[0]:
2430 repo.dirstate.normal(f)
2437 repo.dirstate.normal(f)
2431 for f in remove[0]:
2438 for f in remove[0]:
2432 repo.dirstate.remove(f)
2439 repo.dirstate.remove(f)
2433 return r
2440 return r
2434 finally:
2441 finally:
2435 del wlock
2442 del wlock
2436
2443
2437 def rollback(ui, repo):
2444 def rollback(ui, repo):
2438 """roll back the last transaction in this repository
2445 """roll back the last transaction in this repository
2439
2446
2440 Roll back the last transaction in this repository, restoring the
2447 Roll back the last transaction in this repository, restoring the
2441 project to its state prior to the transaction.
2448 project to its state prior to the transaction.
2442
2449
2443 Transactions are used to encapsulate the effects of all commands
2450 Transactions are used to encapsulate the effects of all commands
2444 that create new changesets or propagate existing changesets into a
2451 that create new changesets or propagate existing changesets into a
2445 repository. For example, the following commands are transactional,
2452 repository. For example, the following commands are transactional,
2446 and their effects can be rolled back:
2453 and their effects can be rolled back:
2447
2454
2448 commit
2455 commit
2449 import
2456 import
2450 pull
2457 pull
2451 push (with this repository as destination)
2458 push (with this repository as destination)
2452 unbundle
2459 unbundle
2453
2460
2454 This command should be used with care. There is only one level of
2461 This command should be used with care. There is only one level of
2455 rollback, and there is no way to undo a rollback. It will also
2462 rollback, and there is no way to undo a rollback. It will also
2456 restore the dirstate at the time of the last transaction, which
2463 restore the dirstate at the time of the last transaction, which
2457 may lose subsequent dirstate changes.
2464 may lose subsequent dirstate changes.
2458
2465
2459 This command is not intended for use on public repositories. Once
2466 This command is not intended for use on public repositories. Once
2460 changes are visible for pull by other users, rolling a transaction
2467 changes are visible for pull by other users, rolling a transaction
2461 back locally is ineffective (someone else may already have pulled
2468 back locally is ineffective (someone else may already have pulled
2462 the changes). Furthermore, a race is possible with readers of the
2469 the changes). Furthermore, a race is possible with readers of the
2463 repository; for example an in-progress pull from the repository
2470 repository; for example an in-progress pull from the repository
2464 may fail if a rollback is performed.
2471 may fail if a rollback is performed.
2465 """
2472 """
2466 repo.rollback()
2473 repo.rollback()
2467
2474
2468 def root(ui, repo):
2475 def root(ui, repo):
2469 """print the root (top) of the current working dir
2476 """print the root (top) of the current working dir
2470
2477
2471 Print the root directory of the current repository.
2478 Print the root directory of the current repository.
2472 """
2479 """
2473 ui.write(repo.root + "\n")
2480 ui.write(repo.root + "\n")
2474
2481
2475 def serve(ui, repo, **opts):
2482 def serve(ui, repo, **opts):
2476 """export the repository via HTTP
2483 """export the repository via HTTP
2477
2484
2478 Start a local HTTP repository browser and pull server.
2485 Start a local HTTP repository browser and pull server.
2479
2486
2480 By default, the server logs accesses to stdout and errors to
2487 By default, the server logs accesses to stdout and errors to
2481 stderr. Use the "-A" and "-E" options to log to files.
2488 stderr. Use the "-A" and "-E" options to log to files.
2482 """
2489 """
2483
2490
2484 if opts["stdio"]:
2491 if opts["stdio"]:
2485 if repo is None:
2492 if repo is None:
2486 raise hg.RepoError(_("There is no Mercurial repository here"
2493 raise hg.RepoError(_("There is no Mercurial repository here"
2487 " (.hg not found)"))
2494 " (.hg not found)"))
2488 s = sshserver.sshserver(ui, repo)
2495 s = sshserver.sshserver(ui, repo)
2489 s.serve_forever()
2496 s.serve_forever()
2490
2497
2491 parentui = ui.parentui or ui
2498 parentui = ui.parentui or ui
2492 optlist = ("name templates style address port ipv6"
2499 optlist = ("name templates style address port ipv6"
2493 " accesslog errorlog webdir_conf certificate")
2500 " accesslog errorlog webdir_conf certificate")
2494 for o in optlist.split():
2501 for o in optlist.split():
2495 if opts[o]:
2502 if opts[o]:
2496 parentui.setconfig("web", o, str(opts[o]))
2503 parentui.setconfig("web", o, str(opts[o]))
2497 if (repo is not None) and (repo.ui != parentui):
2504 if (repo is not None) and (repo.ui != parentui):
2498 repo.ui.setconfig("web", o, str(opts[o]))
2505 repo.ui.setconfig("web", o, str(opts[o]))
2499
2506
2500 if repo is None and not ui.config("web", "webdir_conf"):
2507 if repo is None and not ui.config("web", "webdir_conf"):
2501 raise hg.RepoError(_("There is no Mercurial repository here"
2508 raise hg.RepoError(_("There is no Mercurial repository here"
2502 " (.hg not found)"))
2509 " (.hg not found)"))
2503
2510
2504 class service:
2511 class service:
2505 def init(self):
2512 def init(self):
2506 util.set_signal_handler()
2513 util.set_signal_handler()
2507 try:
2514 try:
2508 self.httpd = hgweb.server.create_server(parentui, repo)
2515 self.httpd = hgweb.server.create_server(parentui, repo)
2509 except socket.error, inst:
2516 except socket.error, inst:
2510 raise util.Abort(_('cannot start server: ') + inst.args[1])
2517 raise util.Abort(_('cannot start server: ') + inst.args[1])
2511
2518
2512 if not ui.verbose: return
2519 if not ui.verbose: return
2513
2520
2514 if self.httpd.port != 80:
2521 if self.httpd.port != 80:
2515 ui.status(_('listening at http://%s:%d/\n') %
2522 ui.status(_('listening at http://%s:%d/\n') %
2516 (self.httpd.addr, self.httpd.port))
2523 (self.httpd.addr, self.httpd.port))
2517 else:
2524 else:
2518 ui.status(_('listening at http://%s/\n') % self.httpd.addr)
2525 ui.status(_('listening at http://%s/\n') % self.httpd.addr)
2519
2526
2520 def run(self):
2527 def run(self):
2521 self.httpd.serve_forever()
2528 self.httpd.serve_forever()
2522
2529
2523 service = service()
2530 service = service()
2524
2531
2525 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2532 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2526
2533
2527 def status(ui, repo, *pats, **opts):
2534 def status(ui, repo, *pats, **opts):
2528 """show changed files in the working directory
2535 """show changed files in the working directory
2529
2536
2530 Show status of files in the repository. If names are given, only
2537 Show status of files in the repository. If names are given, only
2531 files that match are shown. Files that are clean or ignored, are
2538 files that match are shown. Files that are clean or ignored, are
2532 not listed unless -c (clean), -i (ignored) or -A is given.
2539 not listed unless -c (clean), -i (ignored) or -A is given.
2533
2540
2534 NOTE: status may appear to disagree with diff if permissions have
2541 NOTE: status may appear to disagree with diff if permissions have
2535 changed or a merge has occurred. The standard diff format does not
2542 changed or a merge has occurred. The standard diff format does not
2536 report permission changes and diff only reports changes relative
2543 report permission changes and diff only reports changes relative
2537 to one merge parent.
2544 to one merge parent.
2538
2545
2539 If one revision is given, it is used as the base revision.
2546 If one revision is given, it is used as the base revision.
2540 If two revisions are given, the difference between them is shown.
2547 If two revisions are given, the difference between them is shown.
2541
2548
2542 The codes used to show the status of files are:
2549 The codes used to show the status of files are:
2543 M = modified
2550 M = modified
2544 A = added
2551 A = added
2545 R = removed
2552 R = removed
2546 C = clean
2553 C = clean
2547 ! = deleted, but still tracked
2554 ! = deleted, but still tracked
2548 ? = not tracked
2555 ? = not tracked
2549 I = ignored (not shown by default)
2556 I = ignored (not shown by default)
2550 = the previous added file was copied from here
2557 = the previous added file was copied from here
2551 """
2558 """
2552
2559
2553 all = opts['all']
2560 all = opts['all']
2554 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2561 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2555
2562
2556 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2563 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2557 cwd = (pats and repo.getcwd()) or ''
2564 cwd = (pats and repo.getcwd()) or ''
2558 modified, added, removed, deleted, unknown, ignored, clean = [
2565 modified, added, removed, deleted, unknown, ignored, clean = [
2559 n for n in repo.status(node1=node1, node2=node2, files=files,
2566 n for n in repo.status(node1=node1, node2=node2, files=files,
2560 match=matchfn,
2567 match=matchfn,
2561 list_ignored=all or opts['ignored'],
2568 list_ignored=all or opts['ignored'],
2562 list_clean=all or opts['clean'])]
2569 list_clean=all or opts['clean'])]
2563
2570
2564 changetypes = (('modified', 'M', modified),
2571 changetypes = (('modified', 'M', modified),
2565 ('added', 'A', added),
2572 ('added', 'A', added),
2566 ('removed', 'R', removed),
2573 ('removed', 'R', removed),
2567 ('deleted', '!', deleted),
2574 ('deleted', '!', deleted),
2568 ('unknown', '?', unknown),
2575 ('unknown', '?', unknown),
2569 ('ignored', 'I', ignored))
2576 ('ignored', 'I', ignored))
2570
2577
2571 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2578 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2572
2579
2573 end = opts['print0'] and '\0' or '\n'
2580 end = opts['print0'] and '\0' or '\n'
2574
2581
2575 for opt, char, changes in ([ct for ct in explicit_changetypes
2582 for opt, char, changes in ([ct for ct in explicit_changetypes
2576 if all or opts[ct[0]]]
2583 if all or opts[ct[0]]]
2577 or changetypes):
2584 or changetypes):
2578 if opts['no_status']:
2585 if opts['no_status']:
2579 format = "%%s%s" % end
2586 format = "%%s%s" % end
2580 else:
2587 else:
2581 format = "%s %%s%s" % (char, end)
2588 format = "%s %%s%s" % (char, end)
2582
2589
2583 for f in changes:
2590 for f in changes:
2584 ui.write(format % repo.pathto(f, cwd))
2591 ui.write(format % repo.pathto(f, cwd))
2585 if ((all or opts.get('copies')) and not opts.get('no_status')):
2592 if ((all or opts.get('copies')) and not opts.get('no_status')):
2586 copied = repo.dirstate.copied(f)
2593 copied = repo.dirstate.copied(f)
2587 if copied:
2594 if copied:
2588 ui.write(' %s%s' % (repo.pathto(copied, cwd), end))
2595 ui.write(' %s%s' % (repo.pathto(copied, cwd), end))
2589
2596
2590 def tag(ui, repo, name, rev_=None, **opts):
2597 def tag(ui, repo, name, rev_=None, **opts):
2591 """add a tag for the current or given revision
2598 """add a tag for the current or given revision
2592
2599
2593 Name a particular revision using <name>.
2600 Name a particular revision using <name>.
2594
2601
2595 Tags are used to name particular revisions of the repository and are
2602 Tags are used to name particular revisions of the repository and are
2596 very useful to compare different revision, to go back to significant
2603 very useful to compare different revision, to go back to significant
2597 earlier versions or to mark branch points as releases, etc.
2604 earlier versions or to mark branch points as releases, etc.
2598
2605
2599 If no revision is given, the parent of the working directory is used,
2606 If no revision is given, the parent of the working directory is used,
2600 or tip if no revision is checked out.
2607 or tip if no revision is checked out.
2601
2608
2602 To facilitate version control, distribution, and merging of tags,
2609 To facilitate version control, distribution, and merging of tags,
2603 they are stored as a file named ".hgtags" which is managed
2610 they are stored as a file named ".hgtags" which is managed
2604 similarly to other project files and can be hand-edited if
2611 similarly to other project files and can be hand-edited if
2605 necessary. The file '.hg/localtags' is used for local tags (not
2612 necessary. The file '.hg/localtags' is used for local tags (not
2606 shared among repositories).
2613 shared among repositories).
2607 """
2614 """
2608 if name in ['tip', '.', 'null']:
2615 if name in ['tip', '.', 'null']:
2609 raise util.Abort(_("the name '%s' is reserved") % name)
2616 raise util.Abort(_("the name '%s' is reserved") % name)
2610 if rev_ is not None:
2617 if rev_ is not None:
2611 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2618 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2612 "please use 'hg tag [-r REV] NAME' instead\n"))
2619 "please use 'hg tag [-r REV] NAME' instead\n"))
2613 if opts['rev']:
2620 if opts['rev']:
2614 raise util.Abort(_("use only one form to specify the revision"))
2621 raise util.Abort(_("use only one form to specify the revision"))
2615 if opts['rev'] and opts['remove']:
2622 if opts['rev'] and opts['remove']:
2616 raise util.Abort(_("--rev and --remove are incompatible"))
2623 raise util.Abort(_("--rev and --remove are incompatible"))
2617 if opts['rev']:
2624 if opts['rev']:
2618 rev_ = opts['rev']
2625 rev_ = opts['rev']
2619 message = opts['message']
2626 message = opts['message']
2620 if opts['remove']:
2627 if opts['remove']:
2621 if not name in repo.tags():
2628 if not name in repo.tags():
2622 raise util.Abort(_('tag %s does not exist') % name)
2629 raise util.Abort(_('tag %s does not exist') % name)
2623 rev_ = nullid
2630 rev_ = nullid
2624 if not message:
2631 if not message:
2625 message = _('Removed tag %s') % name
2632 message = _('Removed tag %s') % name
2626 elif name in repo.tags() and not opts['force']:
2633 elif name in repo.tags() and not opts['force']:
2627 raise util.Abort(_('a tag named %s already exists (use -f to force)')
2634 raise util.Abort(_('a tag named %s already exists (use -f to force)')
2628 % name)
2635 % name)
2629 if not rev_ and repo.dirstate.parents()[1] != nullid:
2636 if not rev_ and repo.dirstate.parents()[1] != nullid:
2630 raise util.Abort(_('uncommitted merge - please provide a '
2637 raise util.Abort(_('uncommitted merge - please provide a '
2631 'specific revision'))
2638 'specific revision'))
2632 r = repo.changectx(rev_).node()
2639 r = repo.changectx(rev_).node()
2633
2640
2634 if not message:
2641 if not message:
2635 message = _('Added tag %s for changeset %s') % (name, short(r))
2642 message = _('Added tag %s for changeset %s') % (name, short(r))
2636
2643
2637 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2644 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2638
2645
2639 def tags(ui, repo):
2646 def tags(ui, repo):
2640 """list repository tags
2647 """list repository tags
2641
2648
2642 List the repository tags.
2649 List the repository tags.
2643
2650
2644 This lists both regular and local tags.
2651 This lists both regular and local tags.
2645 """
2652 """
2646
2653
2647 l = repo.tagslist()
2654 l = repo.tagslist()
2648 l.reverse()
2655 l.reverse()
2649 hexfunc = ui.debugflag and hex or short
2656 hexfunc = ui.debugflag and hex or short
2650 for t, n in l:
2657 for t, n in l:
2651 try:
2658 try:
2652 hn = hexfunc(n)
2659 hn = hexfunc(n)
2653 r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
2660 r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
2654 except revlog.LookupError:
2661 except revlog.LookupError:
2655 r = " ?:%s" % hn
2662 r = " ?:%s" % hn
2656 if ui.quiet:
2663 if ui.quiet:
2657 ui.write("%s\n" % t)
2664 ui.write("%s\n" % t)
2658 else:
2665 else:
2659 spaces = " " * (30 - util.locallen(t))
2666 spaces = " " * (30 - util.locallen(t))
2660 ui.write("%s%s %s\n" % (t, spaces, r))
2667 ui.write("%s%s %s\n" % (t, spaces, r))
2661
2668
2662 def tip(ui, repo, **opts):
2669 def tip(ui, repo, **opts):
2663 """show the tip revision
2670 """show the tip revision
2664
2671
2665 Show the tip revision.
2672 Show the tip revision.
2666 """
2673 """
2667 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2674 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2668
2675
2669 def unbundle(ui, repo, fname1, *fnames, **opts):
2676 def unbundle(ui, repo, fname1, *fnames, **opts):
2670 """apply one or more changegroup files
2677 """apply one or more changegroup files
2671
2678
2672 Apply one or more compressed changegroup files generated by the
2679 Apply one or more compressed changegroup files generated by the
2673 bundle command.
2680 bundle command.
2674 """
2681 """
2675 fnames = (fname1,) + fnames
2682 fnames = (fname1,) + fnames
2676 for fname in fnames:
2683 for fname in fnames:
2677 if os.path.exists(fname):
2684 if os.path.exists(fname):
2678 f = open(fname, "rb")
2685 f = open(fname, "rb")
2679 else:
2686 else:
2680 f = urllib.urlopen(fname)
2687 f = urllib.urlopen(fname)
2681 gen = changegroup.readbundle(f, fname)
2688 gen = changegroup.readbundle(f, fname)
2682 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2689 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2683
2690
2684 return postincoming(ui, repo, modheads, opts['update'], None)
2691 return postincoming(ui, repo, modheads, opts['update'], None)
2685
2692
2686 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2693 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2687 """update working directory
2694 """update working directory
2688
2695
2689 Update the working directory to the specified revision, or the
2696 Update the working directory to the specified revision, or the
2690 tip of the current branch if none is specified.
2697 tip of the current branch if none is specified.
2691
2698
2692 If there are no outstanding changes in the working directory and
2699 If there are no outstanding changes in the working directory and
2693 there is a linear relationship between the current version and the
2700 there is a linear relationship between the current version and the
2694 requested version, the result is the requested version.
2701 requested version, the result is the requested version.
2695
2702
2696 To merge the working directory with another revision, use the
2703 To merge the working directory with another revision, use the
2697 merge command.
2704 merge command.
2698
2705
2699 By default, update will refuse to run if doing so would require
2706 By default, update will refuse to run if doing so would require
2700 discarding local changes.
2707 discarding local changes.
2701 """
2708 """
2702 if rev and node:
2709 if rev and node:
2703 raise util.Abort(_("please specify just one revision"))
2710 raise util.Abort(_("please specify just one revision"))
2704
2711
2705 if not rev:
2712 if not rev:
2706 rev = node
2713 rev = node
2707
2714
2708 if date:
2715 if date:
2709 if rev:
2716 if rev:
2710 raise util.Abort(_("you can't specify a revision and a date"))
2717 raise util.Abort(_("you can't specify a revision and a date"))
2711 rev = cmdutil.finddate(ui, repo, date)
2718 rev = cmdutil.finddate(ui, repo, date)
2712
2719
2713 if clean:
2720 if clean:
2714 return hg.clean(repo, rev)
2721 return hg.clean(repo, rev)
2715 else:
2722 else:
2716 return hg.update(repo, rev)
2723 return hg.update(repo, rev)
2717
2724
2718 def verify(ui, repo):
2725 def verify(ui, repo):
2719 """verify the integrity of the repository
2726 """verify the integrity of the repository
2720
2727
2721 Verify the integrity of the current repository.
2728 Verify the integrity of the current repository.
2722
2729
2723 This will perform an extensive check of the repository's
2730 This will perform an extensive check of the repository's
2724 integrity, validating the hashes and checksums of each entry in
2731 integrity, validating the hashes and checksums of each entry in
2725 the changelog, manifest, and tracked files, as well as the
2732 the changelog, manifest, and tracked files, as well as the
2726 integrity of their crosslinks and indices.
2733 integrity of their crosslinks and indices.
2727 """
2734 """
2728 return hg.verify(repo)
2735 return hg.verify(repo)
2729
2736
2730 def version_(ui):
2737 def version_(ui):
2731 """output version and copyright information"""
2738 """output version and copyright information"""
2732 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2739 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2733 % version.get_version())
2740 % version.get_version())
2734 ui.status(_(
2741 ui.status(_(
2735 "\nCopyright (C) 2005-2007 Matt Mackall <mpm@selenic.com> and others\n"
2742 "\nCopyright (C) 2005-2007 Matt Mackall <mpm@selenic.com> and others\n"
2736 "This is free software; see the source for copying conditions. "
2743 "This is free software; see the source for copying conditions. "
2737 "There is NO\nwarranty; "
2744 "There is NO\nwarranty; "
2738 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2745 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2739 ))
2746 ))
2740
2747
2741 # Command options and aliases are listed here, alphabetically
2748 # Command options and aliases are listed here, alphabetically
2742
2749
2743 globalopts = [
2750 globalopts = [
2744 ('R', 'repository', '',
2751 ('R', 'repository', '',
2745 _('repository root directory or symbolic path name')),
2752 _('repository root directory or symbolic path name')),
2746 ('', 'cwd', '', _('change working directory')),
2753 ('', 'cwd', '', _('change working directory')),
2747 ('y', 'noninteractive', None,
2754 ('y', 'noninteractive', None,
2748 _('do not prompt, assume \'yes\' for any required answers')),
2755 _('do not prompt, assume \'yes\' for any required answers')),
2749 ('q', 'quiet', None, _('suppress output')),
2756 ('q', 'quiet', None, _('suppress output')),
2750 ('v', 'verbose', None, _('enable additional output')),
2757 ('v', 'verbose', None, _('enable additional output')),
2751 ('', 'config', [], _('set/override config option')),
2758 ('', 'config', [], _('set/override config option')),
2752 ('', 'debug', None, _('enable debugging output')),
2759 ('', 'debug', None, _('enable debugging output')),
2753 ('', 'debugger', None, _('start debugger')),
2760 ('', 'debugger', None, _('start debugger')),
2754 ('', 'encoding', util._encoding, _('set the charset encoding')),
2761 ('', 'encoding', util._encoding, _('set the charset encoding')),
2755 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2762 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2756 ('', 'lsprof', None, _('print improved command execution profile')),
2763 ('', 'lsprof', None, _('print improved command execution profile')),
2757 ('', 'traceback', None, _('print traceback on exception')),
2764 ('', 'traceback', None, _('print traceback on exception')),
2758 ('', 'time', None, _('time how long the command takes')),
2765 ('', 'time', None, _('time how long the command takes')),
2759 ('', 'profile', None, _('print command execution profile')),
2766 ('', 'profile', None, _('print command execution profile')),
2760 ('', 'version', None, _('output version information and exit')),
2767 ('', 'version', None, _('output version information and exit')),
2761 ('h', 'help', None, _('display help and exit')),
2768 ('h', 'help', None, _('display help and exit')),
2762 ]
2769 ]
2763
2770
2764 dryrunopts = [('n', 'dry-run', None,
2771 dryrunopts = [('n', 'dry-run', None,
2765 _('do not perform actions, just print output'))]
2772 _('do not perform actions, just print output'))]
2766
2773
2767 remoteopts = [
2774 remoteopts = [
2768 ('e', 'ssh', '', _('specify ssh command to use')),
2775 ('e', 'ssh', '', _('specify ssh command to use')),
2769 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2776 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2770 ]
2777 ]
2771
2778
2772 walkopts = [
2779 walkopts = [
2773 ('I', 'include', [], _('include names matching the given patterns')),
2780 ('I', 'include', [], _('include names matching the given patterns')),
2774 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2781 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2775 ]
2782 ]
2776
2783
2777 commitopts = [
2784 commitopts = [
2778 ('m', 'message', '', _('use <text> as commit message')),
2785 ('m', 'message', '', _('use <text> as commit message')),
2779 ('l', 'logfile', '', _('read commit message from <file>')),
2786 ('l', 'logfile', '', _('read commit message from <file>')),
2780 ]
2787 ]
2781
2788
2782 commitopts2 = [
2789 commitopts2 = [
2783 ('d', 'date', '', _('record datecode as commit date')),
2790 ('d', 'date', '', _('record datecode as commit date')),
2784 ('u', 'user', '', _('record user as committer')),
2791 ('u', 'user', '', _('record user as committer')),
2785 ]
2792 ]
2786
2793
2787 table = {
2794 table = {
2788 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2795 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2789 "addremove":
2796 "addremove":
2790 (addremove,
2797 (addremove,
2791 [('s', 'similarity', '',
2798 [('s', 'similarity', '',
2792 _('guess renamed files by similarity (0<=s<=100)')),
2799 _('guess renamed files by similarity (0<=s<=100)')),
2793 ] + walkopts + dryrunopts,
2800 ] + walkopts + dryrunopts,
2794 _('hg addremove [OPTION]... [FILE]...')),
2801 _('hg addremove [OPTION]... [FILE]...')),
2795 "^annotate":
2802 "^annotate":
2796 (annotate,
2803 (annotate,
2797 [('r', 'rev', '', _('annotate the specified revision')),
2804 [('r', 'rev', '', _('annotate the specified revision')),
2798 ('f', 'follow', None, _('follow file copies and renames')),
2805 ('f', 'follow', None, _('follow file copies and renames')),
2799 ('a', 'text', None, _('treat all files as text')),
2806 ('a', 'text', None, _('treat all files as text')),
2800 ('u', 'user', None, _('list the author')),
2807 ('u', 'user', None, _('list the author')),
2801 ('d', 'date', None, _('list the date')),
2808 ('d', 'date', None, _('list the date')),
2802 ('n', 'number', None, _('list the revision number (default)')),
2809 ('n', 'number', None, _('list the revision number (default)')),
2803 ('c', 'changeset', None, _('list the changeset')),
2810 ('c', 'changeset', None, _('list the changeset')),
2804 ('l', 'line-number', None,
2811 ('l', 'line-number', None,
2805 _('show line number at the first appearance'))
2812 _('show line number at the first appearance'))
2806 ] + walkopts,
2813 ] + walkopts,
2807 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
2814 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
2808 "archive":
2815 "archive":
2809 (archive,
2816 (archive,
2810 [('', 'no-decode', None, _('do not pass files through decoders')),
2817 [('', 'no-decode', None, _('do not pass files through decoders')),
2811 ('p', 'prefix', '', _('directory prefix for files in archive')),
2818 ('p', 'prefix', '', _('directory prefix for files in archive')),
2812 ('r', 'rev', '', _('revision to distribute')),
2819 ('r', 'rev', '', _('revision to distribute')),
2813 ('t', 'type', '', _('type of distribution to create')),
2820 ('t', 'type', '', _('type of distribution to create')),
2814 ] + walkopts,
2821 ] + walkopts,
2815 _('hg archive [OPTION]... DEST')),
2822 _('hg archive [OPTION]... DEST')),
2816 "backout":
2823 "backout":
2817 (backout,
2824 (backout,
2818 [('', 'merge', None,
2825 [('', 'merge', None,
2819 _('merge with old dirstate parent after backout')),
2826 _('merge with old dirstate parent after backout')),
2820 ('', 'parent', '', _('parent to choose when backing out merge')),
2827 ('', 'parent', '', _('parent to choose when backing out merge')),
2821 ('r', 'rev', '', _('revision to backout')),
2828 ('r', 'rev', '', _('revision to backout')),
2822 ] + walkopts + commitopts + commitopts2,
2829 ] + walkopts + commitopts + commitopts2,
2823 _('hg backout [OPTION]... [-r] REV')),
2830 _('hg backout [OPTION]... [-r] REV')),
2824 "branch":
2831 "branch":
2825 (branch,
2832 (branch,
2826 [('f', 'force', None,
2833 [('f', 'force', None,
2827 _('set branch name even if it shadows an existing branch'))],
2834 _('set branch name even if it shadows an existing branch'))],
2828 _('hg branch [NAME]')),
2835 _('hg branch [NAME]')),
2829 "branches":
2836 "branches":
2830 (branches,
2837 (branches,
2831 [('a', 'active', False,
2838 [('a', 'active', False,
2832 _('show only branches that have unmerged heads'))],
2839 _('show only branches that have unmerged heads'))],
2833 _('hg branches [-a]')),
2840 _('hg branches [-a]')),
2834 "bundle":
2841 "bundle":
2835 (bundle,
2842 (bundle,
2836 [('f', 'force', None,
2843 [('f', 'force', None,
2837 _('run even when remote repository is unrelated')),
2844 _('run even when remote repository is unrelated')),
2838 ('r', 'rev', [],
2845 ('r', 'rev', [],
2839 _('a changeset you would like to bundle')),
2846 _('a changeset you would like to bundle')),
2840 ('', 'base', [],
2847 ('', 'base', [],
2841 _('a base changeset to specify instead of a destination')),
2848 _('a base changeset to specify instead of a destination')),
2842 ] + remoteopts,
2849 ] + remoteopts,
2843 _('hg bundle [-f] [-r REV]... [--base REV]... FILE [DEST]')),
2850 _('hg bundle [-f] [-r REV]... [--base REV]... FILE [DEST]')),
2844 "cat":
2851 "cat":
2845 (cat,
2852 (cat,
2846 [('o', 'output', '', _('print output to file with formatted name')),
2853 [('o', 'output', '', _('print output to file with formatted name')),
2847 ('r', 'rev', '', _('print the given revision')),
2854 ('r', 'rev', '', _('print the given revision')),
2848 ] + walkopts,
2855 ] + walkopts,
2849 _('hg cat [OPTION]... FILE...')),
2856 _('hg cat [OPTION]... FILE...')),
2850 "^clone":
2857 "^clone":
2851 (clone,
2858 (clone,
2852 [('U', 'noupdate', None, _('do not update the new working directory')),
2859 [('U', 'noupdate', None, _('do not update the new working directory')),
2853 ('r', 'rev', [],
2860 ('r', 'rev', [],
2854 _('a changeset you would like to have after cloning')),
2861 _('a changeset you would like to have after cloning')),
2855 ('', 'pull', None, _('use pull protocol to copy metadata')),
2862 ('', 'pull', None, _('use pull protocol to copy metadata')),
2856 ('', 'uncompressed', None,
2863 ('', 'uncompressed', None,
2857 _('use uncompressed transfer (fast over LAN)')),
2864 _('use uncompressed transfer (fast over LAN)')),
2858 ] + remoteopts,
2865 ] + remoteopts,
2859 _('hg clone [OPTION]... SOURCE [DEST]')),
2866 _('hg clone [OPTION]... SOURCE [DEST]')),
2860 "^commit|ci":
2867 "^commit|ci":
2861 (commit,
2868 (commit,
2862 [('A', 'addremove', None,
2869 [('A', 'addremove', None,
2863 _('mark new/missing files as added/removed before committing')),
2870 _('mark new/missing files as added/removed before committing')),
2864 ] + walkopts + commitopts + commitopts2,
2871 ] + walkopts + commitopts + commitopts2,
2865 _('hg commit [OPTION]... [FILE]...')),
2872 _('hg commit [OPTION]... [FILE]...')),
2866 "copy|cp":
2873 "copy|cp":
2867 (copy,
2874 (copy,
2868 [('A', 'after', None, _('record a copy that has already occurred')),
2875 [('A', 'after', None, _('record a copy that has already occurred')),
2869 ('f', 'force', None,
2876 ('f', 'force', None,
2870 _('forcibly copy over an existing managed file')),
2877 _('forcibly copy over an existing managed file')),
2871 ] + walkopts + dryrunopts,
2878 ] + walkopts + dryrunopts,
2872 _('hg copy [OPTION]... [SOURCE]... DEST')),
2879 _('hg copy [OPTION]... [SOURCE]... DEST')),
2873 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2880 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2874 "debugcomplete":
2881 "debugcomplete":
2875 (debugcomplete,
2882 (debugcomplete,
2876 [('o', 'options', None, _('show the command options'))],
2883 [('o', 'options', None, _('show the command options'))],
2877 _('debugcomplete [-o] CMD')),
2884 _('debugcomplete [-o] CMD')),
2878 "debuginstall": (debuginstall, [], _('debuginstall')),
2885 "debuginstall": (debuginstall, [], _('debuginstall')),
2879 "debugrebuildstate":
2886 "debugrebuildstate":
2880 (debugrebuildstate,
2887 (debugrebuildstate,
2881 [('r', 'rev', '', _('revision to rebuild to'))],
2888 [('r', 'rev', '', _('revision to rebuild to'))],
2882 _('debugrebuildstate [-r REV] [REV]')),
2889 _('debugrebuildstate [-r REV] [REV]')),
2883 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2890 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2884 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2891 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2885 "debugstate": (debugstate, [], _('debugstate')),
2892 "debugstate": (debugstate, [], _('debugstate')),
2886 "debugdate":
2893 "debugdate":
2887 (debugdate,
2894 (debugdate,
2888 [('e', 'extended', None, _('try extended date formats'))],
2895 [('e', 'extended', None, _('try extended date formats'))],
2889 _('debugdate [-e] DATE [RANGE]')),
2896 _('debugdate [-e] DATE [RANGE]')),
2890 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2897 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2891 "debugindex": (debugindex, [], _('debugindex FILE')),
2898 "debugindex": (debugindex, [], _('debugindex FILE')),
2892 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2899 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2893 "debugrename":
2900 "debugrename":
2894 (debugrename,
2901 (debugrename,
2895 [('r', 'rev', '', _('revision to debug'))],
2902 [('r', 'rev', '', _('revision to debug'))],
2896 _('debugrename [-r REV] FILE')),
2903 _('debugrename [-r REV] FILE')),
2897 "debugwalk": (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
2904 "debugwalk": (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
2898 "^diff":
2905 "^diff":
2899 (diff,
2906 (diff,
2900 [('r', 'rev', [], _('revision')),
2907 [('r', 'rev', [], _('revision')),
2901 ('a', 'text', None, _('treat all files as text')),
2908 ('a', 'text', None, _('treat all files as text')),
2902 ('p', 'show-function', None,
2909 ('p', 'show-function', None,
2903 _('show which function each change is in')),
2910 _('show which function each change is in')),
2904 ('g', 'git', None, _('use git extended diff format')),
2911 ('g', 'git', None, _('use git extended diff format')),
2905 ('', 'nodates', None, _("don't include dates in diff headers")),
2912 ('', 'nodates', None, _("don't include dates in diff headers")),
2906 ('w', 'ignore-all-space', None,
2913 ('w', 'ignore-all-space', None,
2907 _('ignore white space when comparing lines')),
2914 _('ignore white space when comparing lines')),
2908 ('b', 'ignore-space-change', None,
2915 ('b', 'ignore-space-change', None,
2909 _('ignore changes in the amount of white space')),
2916 _('ignore changes in the amount of white space')),
2910 ('B', 'ignore-blank-lines', None,
2917 ('B', 'ignore-blank-lines', None,
2911 _('ignore changes whose lines are all blank')),
2918 _('ignore changes whose lines are all blank')),
2912 ] + walkopts,
2919 ] + walkopts,
2913 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
2920 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
2914 "^export":
2921 "^export":
2915 (export,
2922 (export,
2916 [('o', 'output', '', _('print output to file with formatted name')),
2923 [('o', 'output', '', _('print output to file with formatted name')),
2917 ('a', 'text', None, _('treat all files as text')),
2924 ('a', 'text', None, _('treat all files as text')),
2918 ('g', 'git', None, _('use git extended diff format')),
2925 ('g', 'git', None, _('use git extended diff format')),
2919 ('', 'nodates', None, _("don't include dates in diff headers")),
2926 ('', 'nodates', None, _("don't include dates in diff headers")),
2920 ('', 'switch-parent', None, _('diff against the second parent'))],
2927 ('', 'switch-parent', None, _('diff against the second parent'))],
2921 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
2928 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
2922 "grep":
2929 "grep":
2923 (grep,
2930 (grep,
2924 [('0', 'print0', None, _('end fields with NUL')),
2931 [('0', 'print0', None, _('end fields with NUL')),
2925 ('', 'all', None, _('print all revisions that match')),
2932 ('', 'all', None, _('print all revisions that match')),
2926 ('f', 'follow', None,
2933 ('f', 'follow', None,
2927 _('follow changeset history, or file history across copies and renames')),
2934 _('follow changeset history, or file history across copies and renames')),
2928 ('i', 'ignore-case', None, _('ignore case when matching')),
2935 ('i', 'ignore-case', None, _('ignore case when matching')),
2929 ('l', 'files-with-matches', None,
2936 ('l', 'files-with-matches', None,
2930 _('print only filenames and revs that match')),
2937 _('print only filenames and revs that match')),
2931 ('n', 'line-number', None, _('print matching line numbers')),
2938 ('n', 'line-number', None, _('print matching line numbers')),
2932 ('r', 'rev', [], _('search in given revision range')),
2939 ('r', 'rev', [], _('search in given revision range')),
2933 ('u', 'user', None, _('print user who committed change')),
2940 ('u', 'user', None, _('print user who committed change')),
2934 ] + walkopts,
2941 ] + walkopts,
2935 _('hg grep [OPTION]... PATTERN [FILE]...')),
2942 _('hg grep [OPTION]... PATTERN [FILE]...')),
2936 "heads":
2943 "heads":
2937 (heads,
2944 (heads,
2938 [('', 'style', '', _('display using template map file')),
2945 [('', 'style', '', _('display using template map file')),
2939 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2946 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2940 ('', 'template', '', _('display with template'))],
2947 ('', 'template', '', _('display with template'))],
2941 _('hg heads [-r REV] [REV]...')),
2948 _('hg heads [-r REV] [REV]...')),
2942 "help": (help_, [], _('hg help [COMMAND]')),
2949 "help": (help_, [], _('hg help [COMMAND]')),
2943 "identify|id":
2950 "identify|id":
2944 (identify,
2951 (identify,
2945 [('r', 'rev', '', _('identify the specified rev')),
2952 [('r', 'rev', '', _('identify the specified rev')),
2946 ('n', 'num', None, _('show local revision number')),
2953 ('n', 'num', None, _('show local revision number')),
2947 ('i', 'id', None, _('show global revision id')),
2954 ('i', 'id', None, _('show global revision id')),
2948 ('b', 'branch', None, _('show branch')),
2955 ('b', 'branch', None, _('show branch')),
2949 ('t', 'tags', None, _('show tags'))],
2956 ('t', 'tags', None, _('show tags'))],
2950 _('hg identify [-nibt] [-r REV] [SOURCE]')),
2957 _('hg identify [-nibt] [-r REV] [SOURCE]')),
2951 "import|patch":
2958 "import|patch":
2952 (import_,
2959 (import_,
2953 [('p', 'strip', 1,
2960 [('p', 'strip', 1,
2954 _('directory strip option for patch. This has the same\n'
2961 _('directory strip option for patch. This has the same\n'
2955 'meaning as the corresponding patch option')),
2962 'meaning as the corresponding patch option')),
2956 ('b', 'base', '', _('base path')),
2963 ('b', 'base', '', _('base path')),
2957 ('f', 'force', None,
2964 ('f', 'force', None,
2958 _('skip check for outstanding uncommitted changes')),
2965 _('skip check for outstanding uncommitted changes')),
2959 ('', 'exact', None,
2966 ('', 'exact', None,
2960 _('apply patch to the nodes from which it was generated')),
2967 _('apply patch to the nodes from which it was generated')),
2961 ('', 'import-branch', None,
2968 ('', 'import-branch', None,
2962 _('Use any branch information in patch (implied by --exact)'))] + commitopts,
2969 _('Use any branch information in patch (implied by --exact)'))] + commitopts,
2963 _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
2970 _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
2964 "incoming|in": (incoming,
2971 "incoming|in": (incoming,
2965 [('M', 'no-merges', None, _('do not show merges')),
2972 [('M', 'no-merges', None, _('do not show merges')),
2966 ('f', 'force', None,
2973 ('f', 'force', None,
2967 _('run even when remote repository is unrelated')),
2974 _('run even when remote repository is unrelated')),
2968 ('', 'style', '', _('display using template map file')),
2975 ('', 'style', '', _('display using template map file')),
2969 ('n', 'newest-first', None, _('show newest record first')),
2976 ('n', 'newest-first', None, _('show newest record first')),
2970 ('', 'bundle', '', _('file to store the bundles into')),
2977 ('', 'bundle', '', _('file to store the bundles into')),
2971 ('p', 'patch', None, _('show patch')),
2978 ('p', 'patch', None, _('show patch')),
2972 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2979 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2973 ('', 'template', '', _('display with template')),
2980 ('', 'template', '', _('display with template')),
2974 ] + remoteopts,
2981 ] + remoteopts,
2975 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
2982 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
2976 ' [--bundle FILENAME] [SOURCE]')),
2983 ' [--bundle FILENAME] [SOURCE]')),
2977 "^init":
2984 "^init":
2978 (init,
2985 (init,
2979 remoteopts,
2986 remoteopts,
2980 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
2987 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
2981 "locate":
2988 "locate":
2982 (locate,
2989 (locate,
2983 [('r', 'rev', '', _('search the repository as it stood at rev')),
2990 [('r', 'rev', '', _('search the repository as it stood at rev')),
2984 ('0', 'print0', None,
2991 ('0', 'print0', None,
2985 _('end filenames with NUL, for use with xargs')),
2992 _('end filenames with NUL, for use with xargs')),
2986 ('f', 'fullpath', None,
2993 ('f', 'fullpath', None,
2987 _('print complete paths from the filesystem root')),
2994 _('print complete paths from the filesystem root')),
2988 ] + walkopts,
2995 ] + walkopts,
2989 _('hg locate [OPTION]... [PATTERN]...')),
2996 _('hg locate [OPTION]... [PATTERN]...')),
2990 "^log|history":
2997 "^log|history":
2991 (log,
2998 (log,
2992 [('f', 'follow', None,
2999 [('f', 'follow', None,
2993 _('follow changeset history, or file history across copies and renames')),
3000 _('follow changeset history, or file history across copies and renames')),
2994 ('', 'follow-first', None,
3001 ('', 'follow-first', None,
2995 _('only follow the first parent of merge changesets')),
3002 _('only follow the first parent of merge changesets')),
2996 ('d', 'date', '', _('show revs matching date spec')),
3003 ('d', 'date', '', _('show revs matching date spec')),
2997 ('C', 'copies', None, _('show copied files')),
3004 ('C', 'copies', None, _('show copied files')),
2998 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3005 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
2999 ('l', 'limit', '', _('limit number of changes displayed')),
3006 ('l', 'limit', '', _('limit number of changes displayed')),
3000 ('r', 'rev', [], _('show the specified revision or range')),
3007 ('r', 'rev', [], _('show the specified revision or range')),
3001 ('', 'removed', None, _('include revs where files were removed')),
3008 ('', 'removed', None, _('include revs where files were removed')),
3002 ('M', 'no-merges', None, _('do not show merges')),
3009 ('M', 'no-merges', None, _('do not show merges')),
3003 ('', 'style', '', _('display using template map file')),
3010 ('', 'style', '', _('display using template map file')),
3004 ('m', 'only-merges', None, _('show only merges')),
3011 ('m', 'only-merges', None, _('show only merges')),
3005 ('p', 'patch', None, _('show patch')),
3012 ('p', 'patch', None, _('show patch')),
3006 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3013 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3007 ('', 'template', '', _('display with template')),
3014 ('', 'template', '', _('display with template')),
3008 ] + walkopts,
3015 ] + walkopts,
3009 _('hg log [OPTION]... [FILE]')),
3016 _('hg log [OPTION]... [FILE]')),
3010 "manifest": (manifest, [('r', 'rev', '', _('revision to display'))],
3017 "manifest": (manifest, [('r', 'rev', '', _('revision to display'))],
3011 _('hg manifest [-r REV]')),
3018 _('hg manifest [-r REV]')),
3012 "^merge":
3019 "^merge":
3013 (merge,
3020 (merge,
3014 [('f', 'force', None, _('force a merge with outstanding changes')),
3021 [('f', 'force', None, _('force a merge with outstanding changes')),
3015 ('r', 'rev', '', _('revision to merge')),
3022 ('r', 'rev', '', _('revision to merge')),
3016 ],
3023 ],
3017 _('hg merge [-f] [[-r] REV]')),
3024 _('hg merge [-f] [[-r] REV]')),
3018 "outgoing|out": (outgoing,
3025 "outgoing|out": (outgoing,
3019 [('M', 'no-merges', None, _('do not show merges')),
3026 [('M', 'no-merges', None, _('do not show merges')),
3020 ('f', 'force', None,
3027 ('f', 'force', None,
3021 _('run even when remote repository is unrelated')),
3028 _('run even when remote repository is unrelated')),
3022 ('p', 'patch', None, _('show patch')),
3029 ('p', 'patch', None, _('show patch')),
3023 ('', 'style', '', _('display using template map file')),
3030 ('', 'style', '', _('display using template map file')),
3024 ('r', 'rev', [], _('a specific revision you would like to push')),
3031 ('r', 'rev', [], _('a specific revision you would like to push')),
3025 ('n', 'newest-first', None, _('show newest record first')),
3032 ('n', 'newest-first', None, _('show newest record first')),
3026 ('', 'template', '', _('display with template')),
3033 ('', 'template', '', _('display with template')),
3027 ] + remoteopts,
3034 ] + remoteopts,
3028 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3035 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3029 "^parents":
3036 "^parents":
3030 (parents,
3037 (parents,
3031 [('r', 'rev', '', _('show parents from the specified rev')),
3038 [('r', 'rev', '', _('show parents from the specified rev')),
3032 ('', 'style', '', _('display using template map file')),
3039 ('', 'style', '', _('display using template map file')),
3033 ('', 'template', '', _('display with template'))],
3040 ('', 'template', '', _('display with template'))],
3034 _('hg parents [-r REV] [FILE]')),
3041 _('hg parents [-r REV] [FILE]')),
3035 "paths": (paths, [], _('hg paths [NAME]')),
3042 "paths": (paths, [], _('hg paths [NAME]')),
3036 "^pull":
3043 "^pull":
3037 (pull,
3044 (pull,
3038 [('u', 'update', None,
3045 [('u', 'update', None,
3039 _('update to new tip if changesets were pulled')),
3046 _('update to new tip if changesets were pulled')),
3040 ('f', 'force', None,
3047 ('f', 'force', None,
3041 _('run even when remote repository is unrelated')),
3048 _('run even when remote repository is unrelated')),
3042 ('r', 'rev', [],
3049 ('r', 'rev', [],
3043 _('a specific revision up to which you would like to pull')),
3050 _('a specific revision up to which you would like to pull')),
3044 ] + remoteopts,
3051 ] + remoteopts,
3045 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3052 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3046 "^push":
3053 "^push":
3047 (push,
3054 (push,
3048 [('f', 'force', None, _('force push')),
3055 [('f', 'force', None, _('force push')),
3049 ('r', 'rev', [], _('a specific revision you would like to push')),
3056 ('r', 'rev', [], _('a specific revision you would like to push')),
3050 ] + remoteopts,
3057 ] + remoteopts,
3051 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3058 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3052 "debugrawcommit|rawcommit":
3059 "debugrawcommit|rawcommit":
3053 (rawcommit,
3060 (rawcommit,
3054 [('p', 'parent', [], _('parent')),
3061 [('p', 'parent', [], _('parent')),
3055 ('F', 'files', '', _('file list'))
3062 ('F', 'files', '', _('file list'))
3056 ] + commitopts + commitopts2,
3063 ] + commitopts + commitopts2,
3057 _('hg debugrawcommit [OPTION]... [FILE]...')),
3064 _('hg debugrawcommit [OPTION]... [FILE]...')),
3058 "recover": (recover, [], _('hg recover')),
3065 "recover": (recover, [], _('hg recover')),
3059 "^remove|rm":
3066 "^remove|rm":
3060 (remove,
3067 (remove,
3061 [('A', 'after', None, _('record remove that has already occurred')),
3068 [('A', 'after', None, _('record remove that has already occurred')),
3062 ('f', 'force', None, _('remove file even if modified')),
3069 ('f', 'force', None, _('remove file even if modified')),
3063 ] + walkopts,
3070 ] + walkopts,
3064 _('hg remove [OPTION]... FILE...')),
3071 _('hg remove [OPTION]... FILE...')),
3065 "rename|mv":
3072 "rename|mv":
3066 (rename,
3073 (rename,
3067 [('A', 'after', None, _('record a rename that has already occurred')),
3074 [('A', 'after', None, _('record a rename that has already occurred')),
3068 ('f', 'force', None,
3075 ('f', 'force', None,
3069 _('forcibly copy over an existing managed file')),
3076 _('forcibly copy over an existing managed file')),
3070 ] + walkopts + dryrunopts,
3077 ] + walkopts + dryrunopts,
3071 _('hg rename [OPTION]... SOURCE... DEST')),
3078 _('hg rename [OPTION]... SOURCE... DEST')),
3072 "^revert":
3079 "^revert":
3073 (revert,
3080 (revert,
3074 [('a', 'all', None, _('revert all changes when no arguments given')),
3081 [('a', 'all', None, _('revert all changes when no arguments given')),
3075 ('d', 'date', '', _('tipmost revision matching date')),
3082 ('d', 'date', '', _('tipmost revision matching date')),
3076 ('r', 'rev', '', _('revision to revert to')),
3083 ('r', 'rev', '', _('revision to revert to')),
3077 ('', 'no-backup', None, _('do not save backup copies of files')),
3084 ('', 'no-backup', None, _('do not save backup copies of files')),
3078 ] + walkopts + dryrunopts,
3085 ] + walkopts + dryrunopts,
3079 _('hg revert [OPTION]... [-r REV] [NAME]...')),
3086 _('hg revert [OPTION]... [-r REV] [NAME]...')),
3080 "rollback": (rollback, [], _('hg rollback')),
3087 "rollback": (rollback, [], _('hg rollback')),
3081 "root": (root, [], _('hg root')),
3088 "root": (root, [], _('hg root')),
3082 "showconfig|debugconfig":
3089 "showconfig|debugconfig":
3083 (showconfig,
3090 (showconfig,
3084 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3091 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3085 _('showconfig [-u] [NAME]...')),
3092 _('showconfig [-u] [NAME]...')),
3086 "^serve":
3093 "^serve":
3087 (serve,
3094 (serve,
3088 [('A', 'accesslog', '', _('name of access log file to write to')),
3095 [('A', 'accesslog', '', _('name of access log file to write to')),
3089 ('d', 'daemon', None, _('run server in background')),
3096 ('d', 'daemon', None, _('run server in background')),
3090 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3097 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3091 ('E', 'errorlog', '', _('name of error log file to write to')),
3098 ('E', 'errorlog', '', _('name of error log file to write to')),
3092 ('p', 'port', 0, _('port to use (default: 8000)')),
3099 ('p', 'port', 0, _('port to use (default: 8000)')),
3093 ('a', 'address', '', _('address to use')),
3100 ('a', 'address', '', _('address to use')),
3094 ('n', 'name', '',
3101 ('n', 'name', '',
3095 _('name to show in web pages (default: working dir)')),
3102 _('name to show in web pages (default: working dir)')),
3096 ('', 'webdir-conf', '', _('name of the webdir config file'
3103 ('', 'webdir-conf', '', _('name of the webdir config file'
3097 ' (serve more than one repo)')),
3104 ' (serve more than one repo)')),
3098 ('', 'pid-file', '', _('name of file to write process ID to')),
3105 ('', 'pid-file', '', _('name of file to write process ID to')),
3099 ('', 'stdio', None, _('for remote clients')),
3106 ('', 'stdio', None, _('for remote clients')),
3100 ('t', 'templates', '', _('web templates to use')),
3107 ('t', 'templates', '', _('web templates to use')),
3101 ('', 'style', '', _('template style to use')),
3108 ('', 'style', '', _('template style to use')),
3102 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3109 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3103 ('', 'certificate', '', _('SSL certificate file'))],
3110 ('', 'certificate', '', _('SSL certificate file'))],
3104 _('hg serve [OPTION]...')),
3111 _('hg serve [OPTION]...')),
3105 "^status|st":
3112 "^status|st":
3106 (status,
3113 (status,
3107 [('A', 'all', None, _('show status of all files')),
3114 [('A', 'all', None, _('show status of all files')),
3108 ('m', 'modified', None, _('show only modified files')),
3115 ('m', 'modified', None, _('show only modified files')),
3109 ('a', 'added', None, _('show only added files')),
3116 ('a', 'added', None, _('show only added files')),
3110 ('r', 'removed', None, _('show only removed files')),
3117 ('r', 'removed', None, _('show only removed files')),
3111 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3118 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3112 ('c', 'clean', None, _('show only files without changes')),
3119 ('c', 'clean', None, _('show only files without changes')),
3113 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3120 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3114 ('i', 'ignored', None, _('show only ignored files')),
3121 ('i', 'ignored', None, _('show only ignored files')),
3115 ('n', 'no-status', None, _('hide status prefix')),
3122 ('n', 'no-status', None, _('hide status prefix')),
3116 ('C', 'copies', None, _('show source of copied files')),
3123 ('C', 'copies', None, _('show source of copied files')),
3117 ('0', 'print0', None,
3124 ('0', 'print0', None,
3118 _('end filenames with NUL, for use with xargs')),
3125 _('end filenames with NUL, for use with xargs')),
3119 ('', 'rev', [], _('show difference from revision')),
3126 ('', 'rev', [], _('show difference from revision')),
3120 ] + walkopts,
3127 ] + walkopts,
3121 _('hg status [OPTION]... [FILE]...')),
3128 _('hg status [OPTION]... [FILE]...')),
3122 "tag":
3129 "tag":
3123 (tag,
3130 (tag,
3124 [('f', 'force', None, _('replace existing tag')),
3131 [('f', 'force', None, _('replace existing tag')),
3125 ('l', 'local', None, _('make the tag local')),
3132 ('l', 'local', None, _('make the tag local')),
3126 ('r', 'rev', '', _('revision to tag')),
3133 ('r', 'rev', '', _('revision to tag')),
3127 ('', 'remove', None, _('remove a tag')),
3134 ('', 'remove', None, _('remove a tag')),
3128 # -l/--local is already there, commitopts cannot be used
3135 # -l/--local is already there, commitopts cannot be used
3129 ('m', 'message', '', _('use <text> as commit message')),
3136 ('m', 'message', '', _('use <text> as commit message')),
3130 ] + commitopts2,
3137 ] + commitopts2,
3131 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3138 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3132 "tags": (tags, [], _('hg tags')),
3139 "tags": (tags, [], _('hg tags')),
3133 "tip":
3140 "tip":
3134 (tip,
3141 (tip,
3135 [('', 'style', '', _('display using template map file')),
3142 [('', 'style', '', _('display using template map file')),
3136 ('p', 'patch', None, _('show patch')),
3143 ('p', 'patch', None, _('show patch')),
3137 ('', 'template', '', _('display with template'))],
3144 ('', 'template', '', _('display with template'))],
3138 _('hg tip [-p]')),
3145 _('hg tip [-p]')),
3139 "unbundle":
3146 "unbundle":
3140 (unbundle,
3147 (unbundle,
3141 [('u', 'update', None,
3148 [('u', 'update', None,
3142 _('update to new tip if changesets were unbundled'))],
3149 _('update to new tip if changesets were unbundled'))],
3143 _('hg unbundle [-u] FILE...')),
3150 _('hg unbundle [-u] FILE...')),
3144 "^update|up|checkout|co":
3151 "^update|up|checkout|co":
3145 (update,
3152 (update,
3146 [('C', 'clean', None, _('overwrite locally modified files')),
3153 [('C', 'clean', None, _('overwrite locally modified files')),
3147 ('d', 'date', '', _('tipmost revision matching date')),
3154 ('d', 'date', '', _('tipmost revision matching date')),
3148 ('r', 'rev', '', _('revision'))],
3155 ('r', 'rev', '', _('revision'))],
3149 _('hg update [-C] [-d DATE] [[-r] REV]')),
3156 _('hg update [-C] [-d DATE] [[-r] REV]')),
3150 "verify": (verify, [], _('hg verify')),
3157 "verify": (verify, [], _('hg verify')),
3151 "version": (version_, [], _('hg version')),
3158 "version": (version_, [], _('hg version')),
3152 }
3159 }
3153
3160
3154 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3161 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3155 " debugindex debugindexdot debugdate debuginstall")
3162 " debugindex debugindexdot debugdate debuginstall")
3156 optionalrepo = ("paths serve showconfig")
3163 optionalrepo = ("identify paths serve showconfig")
@@ -1,1205 +1,1208 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
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-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, mimetypes, re, zlib, mimetools, cStringIO, sys
9 import os, mimetypes, re, zlib, mimetools, cStringIO, sys
10 import tempfile, urllib, bz2
10 import tempfile, urllib, bz2
11 from mercurial.node import *
11 from mercurial.node import *
12 from mercurial.i18n import gettext as _
12 from mercurial.i18n import gettext as _
13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
14 from mercurial import revlog, templater
14 from mercurial import revlog, templater
15 from common import get_mtime, staticfile, style_map, paritygen
15 from common import get_mtime, staticfile, style_map, paritygen
16
16
17 def _up(p):
17 def _up(p):
18 if p[0] != "/":
18 if p[0] != "/":
19 p = "/" + p
19 p = "/" + p
20 if p[-1] == "/":
20 if p[-1] == "/":
21 p = p[:-1]
21 p = p[:-1]
22 up = os.path.dirname(p)
22 up = os.path.dirname(p)
23 if up == "/":
23 if up == "/":
24 return "/"
24 return "/"
25 return up + "/"
25 return up + "/"
26
26
27 def revnavgen(pos, pagelen, limit, nodefunc):
27 def revnavgen(pos, pagelen, limit, nodefunc):
28 def seq(factor, limit=None):
28 def seq(factor, limit=None):
29 if limit:
29 if limit:
30 yield limit
30 yield limit
31 if limit >= 20 and limit <= 40:
31 if limit >= 20 and limit <= 40:
32 yield 50
32 yield 50
33 else:
33 else:
34 yield 1 * factor
34 yield 1 * factor
35 yield 3 * factor
35 yield 3 * factor
36 for f in seq(factor * 10):
36 for f in seq(factor * 10):
37 yield f
37 yield f
38
38
39 def nav(**map):
39 def nav(**map):
40 l = []
40 l = []
41 last = 0
41 last = 0
42 for f in seq(1, pagelen):
42 for f in seq(1, pagelen):
43 if f < pagelen or f <= last:
43 if f < pagelen or f <= last:
44 continue
44 continue
45 if f > limit:
45 if f > limit:
46 break
46 break
47 last = f
47 last = f
48 if pos + f < limit:
48 if pos + f < limit:
49 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
49 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
50 if pos - f >= 0:
50 if pos - f >= 0:
51 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
51 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
52
52
53 try:
53 try:
54 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
54 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
55
55
56 for label, node in l:
56 for label, node in l:
57 yield {"label": label, "node": node}
57 yield {"label": label, "node": node}
58
58
59 yield {"label": "tip", "node": "tip"}
59 yield {"label": "tip", "node": "tip"}
60 except hg.RepoError:
60 except hg.RepoError:
61 pass
61 pass
62
62
63 return nav
63 return nav
64
64
65 class hgweb(object):
65 class hgweb(object):
66 def __init__(self, repo, name=None):
66 def __init__(self, repo, name=None):
67 if isinstance(repo, str):
67 if isinstance(repo, str):
68 parentui = ui.ui(report_untrusted=False, interactive=False)
68 parentui = ui.ui(report_untrusted=False, interactive=False)
69 self.repo = hg.repository(parentui, repo)
69 self.repo = hg.repository(parentui, repo)
70 else:
70 else:
71 self.repo = repo
71 self.repo = repo
72
72
73 self.mtime = -1
73 self.mtime = -1
74 self.reponame = name
74 self.reponame = name
75 self.archives = 'zip', 'gz', 'bz2'
75 self.archives = 'zip', 'gz', 'bz2'
76 self.stripecount = 1
76 self.stripecount = 1
77 # a repo owner may set web.templates in .hg/hgrc to get any file
77 # a repo owner may set web.templates in .hg/hgrc to get any file
78 # readable by the user running the CGI script
78 # readable by the user running the CGI script
79 self.templatepath = self.config("web", "templates",
79 self.templatepath = self.config("web", "templates",
80 templater.templatepath(),
80 templater.templatepath(),
81 untrusted=False)
81 untrusted=False)
82
82
83 # The CGI scripts are often run by a user different from the repo owner.
83 # The CGI scripts are often run by a user different from the repo owner.
84 # Trust the settings from the .hg/hgrc files by default.
84 # Trust the settings from the .hg/hgrc files by default.
85 def config(self, section, name, default=None, untrusted=True):
85 def config(self, section, name, default=None, untrusted=True):
86 return self.repo.ui.config(section, name, default,
86 return self.repo.ui.config(section, name, default,
87 untrusted=untrusted)
87 untrusted=untrusted)
88
88
89 def configbool(self, section, name, default=False, untrusted=True):
89 def configbool(self, section, name, default=False, untrusted=True):
90 return self.repo.ui.configbool(section, name, default,
90 return self.repo.ui.configbool(section, name, default,
91 untrusted=untrusted)
91 untrusted=untrusted)
92
92
93 def configlist(self, section, name, default=None, untrusted=True):
93 def configlist(self, section, name, default=None, untrusted=True):
94 return self.repo.ui.configlist(section, name, default,
94 return self.repo.ui.configlist(section, name, default,
95 untrusted=untrusted)
95 untrusted=untrusted)
96
96
97 def refresh(self):
97 def refresh(self):
98 mtime = get_mtime(self.repo.root)
98 mtime = get_mtime(self.repo.root)
99 if mtime != self.mtime:
99 if mtime != self.mtime:
100 self.mtime = mtime
100 self.mtime = mtime
101 self.repo = hg.repository(self.repo.ui, self.repo.root)
101 self.repo = hg.repository(self.repo.ui, self.repo.root)
102 self.maxchanges = int(self.config("web", "maxchanges", 10))
102 self.maxchanges = int(self.config("web", "maxchanges", 10))
103 self.stripecount = int(self.config("web", "stripes", 1))
103 self.stripecount = int(self.config("web", "stripes", 1))
104 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
104 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
105 self.maxfiles = int(self.config("web", "maxfiles", 10))
105 self.maxfiles = int(self.config("web", "maxfiles", 10))
106 self.allowpull = self.configbool("web", "allowpull", True)
106 self.allowpull = self.configbool("web", "allowpull", True)
107 self.encoding = self.config("web", "encoding", util._encoding)
107 self.encoding = self.config("web", "encoding", util._encoding)
108
108
109 def archivelist(self, nodeid):
109 def archivelist(self, nodeid):
110 allowed = self.configlist("web", "allow_archive")
110 allowed = self.configlist("web", "allow_archive")
111 for i, spec in self.archive_specs.iteritems():
111 for i, spec in self.archive_specs.iteritems():
112 if i in allowed or self.configbool("web", "allow" + i):
112 if i in allowed or self.configbool("web", "allow" + i):
113 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
113 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
114
114
115 def listfilediffs(self, files, changeset):
115 def listfilediffs(self, files, changeset):
116 for f in files[:self.maxfiles]:
116 for f in files[:self.maxfiles]:
117 yield self.t("filedifflink", node=hex(changeset), file=f)
117 yield self.t("filedifflink", node=hex(changeset), file=f)
118 if len(files) > self.maxfiles:
118 if len(files) > self.maxfiles:
119 yield self.t("fileellipses")
119 yield self.t("fileellipses")
120
120
121 def siblings(self, siblings=[], hiderev=None, **args):
121 def siblings(self, siblings=[], hiderev=None, **args):
122 siblings = [s for s in siblings if s.node() != nullid]
122 siblings = [s for s in siblings if s.node() != nullid]
123 if len(siblings) == 1 and siblings[0].rev() == hiderev:
123 if len(siblings) == 1 and siblings[0].rev() == hiderev:
124 return
124 return
125 for s in siblings:
125 for s in siblings:
126 d = {'node': hex(s.node()), 'rev': s.rev()}
126 d = {'node': hex(s.node()), 'rev': s.rev()}
127 if hasattr(s, 'path'):
127 if hasattr(s, 'path'):
128 d['file'] = s.path()
128 d['file'] = s.path()
129 d.update(args)
129 d.update(args)
130 yield d
130 yield d
131
131
132 def renamelink(self, fl, node):
132 def renamelink(self, fl, node):
133 r = fl.renamed(node)
133 r = fl.renamed(node)
134 if r:
134 if r:
135 return [dict(file=r[0], node=hex(r[1]))]
135 return [dict(file=r[0], node=hex(r[1]))]
136 return []
136 return []
137
137
138 def nodetagsdict(self, node):
138 def nodetagsdict(self, node):
139 return [{"name": i} for i in self.repo.nodetags(node)]
139 return [{"name": i} for i in self.repo.nodetags(node)]
140
140
141 def nodebranchdict(self, ctx):
141 def nodebranchdict(self, ctx):
142 branches = []
142 branches = []
143 branch = ctx.branch()
143 branch = ctx.branch()
144 if self.repo.branchtags()[branch] == ctx.node():
144 # If this is an empty repo, ctx.node() == nullid,
145 # ctx.branch() == 'default', but branchtags() is
146 # an empty dict. Using dict.get avoids a traceback.
147 if self.repo.branchtags().get(branch) == ctx.node():
145 branches.append({"name": branch})
148 branches.append({"name": branch})
146 return branches
149 return branches
147
150
148 def showtag(self, t1, node=nullid, **args):
151 def showtag(self, t1, node=nullid, **args):
149 for t in self.repo.nodetags(node):
152 for t in self.repo.nodetags(node):
150 yield self.t(t1, tag=t, **args)
153 yield self.t(t1, tag=t, **args)
151
154
152 def diff(self, node1, node2, files):
155 def diff(self, node1, node2, files):
153 def filterfiles(filters, files):
156 def filterfiles(filters, files):
154 l = [x for x in files if x in filters]
157 l = [x for x in files if x in filters]
155
158
156 for t in filters:
159 for t in filters:
157 if t and t[-1] != os.sep:
160 if t and t[-1] != os.sep:
158 t += os.sep
161 t += os.sep
159 l += [x for x in files if x.startswith(t)]
162 l += [x for x in files if x.startswith(t)]
160 return l
163 return l
161
164
162 parity = paritygen(self.stripecount)
165 parity = paritygen(self.stripecount)
163 def diffblock(diff, f, fn):
166 def diffblock(diff, f, fn):
164 yield self.t("diffblock",
167 yield self.t("diffblock",
165 lines=prettyprintlines(diff),
168 lines=prettyprintlines(diff),
166 parity=parity.next(),
169 parity=parity.next(),
167 file=f,
170 file=f,
168 filenode=hex(fn or nullid))
171 filenode=hex(fn or nullid))
169
172
170 def prettyprintlines(diff):
173 def prettyprintlines(diff):
171 for l in diff.splitlines(1):
174 for l in diff.splitlines(1):
172 if l.startswith('+'):
175 if l.startswith('+'):
173 yield self.t("difflineplus", line=l)
176 yield self.t("difflineplus", line=l)
174 elif l.startswith('-'):
177 elif l.startswith('-'):
175 yield self.t("difflineminus", line=l)
178 yield self.t("difflineminus", line=l)
176 elif l.startswith('@'):
179 elif l.startswith('@'):
177 yield self.t("difflineat", line=l)
180 yield self.t("difflineat", line=l)
178 else:
181 else:
179 yield self.t("diffline", line=l)
182 yield self.t("diffline", line=l)
180
183
181 r = self.repo
184 r = self.repo
182 c1 = r.changectx(node1)
185 c1 = r.changectx(node1)
183 c2 = r.changectx(node2)
186 c2 = r.changectx(node2)
184 date1 = util.datestr(c1.date())
187 date1 = util.datestr(c1.date())
185 date2 = util.datestr(c2.date())
188 date2 = util.datestr(c2.date())
186
189
187 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
190 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
188 if files:
191 if files:
189 modified, added, removed = map(lambda x: filterfiles(files, x),
192 modified, added, removed = map(lambda x: filterfiles(files, x),
190 (modified, added, removed))
193 (modified, added, removed))
191
194
192 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
195 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
193 for f in modified:
196 for f in modified:
194 to = c1.filectx(f).data()
197 to = c1.filectx(f).data()
195 tn = c2.filectx(f).data()
198 tn = c2.filectx(f).data()
196 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
199 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
197 opts=diffopts), f, tn)
200 opts=diffopts), f, tn)
198 for f in added:
201 for f in added:
199 to = None
202 to = None
200 tn = c2.filectx(f).data()
203 tn = c2.filectx(f).data()
201 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
204 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
202 opts=diffopts), f, tn)
205 opts=diffopts), f, tn)
203 for f in removed:
206 for f in removed:
204 to = c1.filectx(f).data()
207 to = c1.filectx(f).data()
205 tn = None
208 tn = None
206 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
209 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
207 opts=diffopts), f, tn)
210 opts=diffopts), f, tn)
208
211
209 def changelog(self, ctx, shortlog=False):
212 def changelog(self, ctx, shortlog=False):
210 def changelist(limit=0,**map):
213 def changelist(limit=0,**map):
211 cl = self.repo.changelog
214 cl = self.repo.changelog
212 l = [] # build a list in forward order for efficiency
215 l = [] # build a list in forward order for efficiency
213 for i in xrange(start, end):
216 for i in xrange(start, end):
214 ctx = self.repo.changectx(i)
217 ctx = self.repo.changectx(i)
215 n = ctx.node()
218 n = ctx.node()
216
219
217 l.insert(0, {"parity": parity.next(),
220 l.insert(0, {"parity": parity.next(),
218 "author": ctx.user(),
221 "author": ctx.user(),
219 "parent": self.siblings(ctx.parents(), i - 1),
222 "parent": self.siblings(ctx.parents(), i - 1),
220 "child": self.siblings(ctx.children(), i + 1),
223 "child": self.siblings(ctx.children(), i + 1),
221 "changelogtag": self.showtag("changelogtag",n),
224 "changelogtag": self.showtag("changelogtag",n),
222 "desc": ctx.description(),
225 "desc": ctx.description(),
223 "date": ctx.date(),
226 "date": ctx.date(),
224 "files": self.listfilediffs(ctx.files(), n),
227 "files": self.listfilediffs(ctx.files(), n),
225 "rev": i,
228 "rev": i,
226 "node": hex(n),
229 "node": hex(n),
227 "tags": self.nodetagsdict(n),
230 "tags": self.nodetagsdict(n),
228 "branches": self.nodebranchdict(ctx)})
231 "branches": self.nodebranchdict(ctx)})
229
232
230 if limit > 0:
233 if limit > 0:
231 l = l[:limit]
234 l = l[:limit]
232
235
233 for e in l:
236 for e in l:
234 yield e
237 yield e
235
238
236 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
239 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
237 cl = self.repo.changelog
240 cl = self.repo.changelog
238 count = cl.count()
241 count = cl.count()
239 pos = ctx.rev()
242 pos = ctx.rev()
240 start = max(0, pos - maxchanges + 1)
243 start = max(0, pos - maxchanges + 1)
241 end = min(count, start + maxchanges)
244 end = min(count, start + maxchanges)
242 pos = end - 1
245 pos = end - 1
243 parity = paritygen(self.stripecount, offset=start-end)
246 parity = paritygen(self.stripecount, offset=start-end)
244
247
245 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
248 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
246
249
247 yield self.t(shortlog and 'shortlog' or 'changelog',
250 yield self.t(shortlog and 'shortlog' or 'changelog',
248 changenav=changenav,
251 changenav=changenav,
249 node=hex(cl.tip()),
252 node=hex(cl.tip()),
250 rev=pos, changesets=count,
253 rev=pos, changesets=count,
251 entries=lambda **x: changelist(limit=0,**x),
254 entries=lambda **x: changelist(limit=0,**x),
252 latestentry=lambda **x: changelist(limit=1,**x),
255 latestentry=lambda **x: changelist(limit=1,**x),
253 archives=self.archivelist("tip"))
256 archives=self.archivelist("tip"))
254
257
255 def search(self, query):
258 def search(self, query):
256
259
257 def changelist(**map):
260 def changelist(**map):
258 cl = self.repo.changelog
261 cl = self.repo.changelog
259 count = 0
262 count = 0
260 qw = query.lower().split()
263 qw = query.lower().split()
261
264
262 def revgen():
265 def revgen():
263 for i in xrange(cl.count() - 1, 0, -100):
266 for i in xrange(cl.count() - 1, 0, -100):
264 l = []
267 l = []
265 for j in xrange(max(0, i - 100), i):
268 for j in xrange(max(0, i - 100), i):
266 ctx = self.repo.changectx(j)
269 ctx = self.repo.changectx(j)
267 l.append(ctx)
270 l.append(ctx)
268 l.reverse()
271 l.reverse()
269 for e in l:
272 for e in l:
270 yield e
273 yield e
271
274
272 for ctx in revgen():
275 for ctx in revgen():
273 miss = 0
276 miss = 0
274 for q in qw:
277 for q in qw:
275 if not (q in ctx.user().lower() or
278 if not (q in ctx.user().lower() or
276 q in ctx.description().lower() or
279 q in ctx.description().lower() or
277 q in " ".join(ctx.files()).lower()):
280 q in " ".join(ctx.files()).lower()):
278 miss = 1
281 miss = 1
279 break
282 break
280 if miss:
283 if miss:
281 continue
284 continue
282
285
283 count += 1
286 count += 1
284 n = ctx.node()
287 n = ctx.node()
285
288
286 yield self.t('searchentry',
289 yield self.t('searchentry',
287 parity=parity.next(),
290 parity=parity.next(),
288 author=ctx.user(),
291 author=ctx.user(),
289 parent=self.siblings(ctx.parents()),
292 parent=self.siblings(ctx.parents()),
290 child=self.siblings(ctx.children()),
293 child=self.siblings(ctx.children()),
291 changelogtag=self.showtag("changelogtag",n),
294 changelogtag=self.showtag("changelogtag",n),
292 desc=ctx.description(),
295 desc=ctx.description(),
293 date=ctx.date(),
296 date=ctx.date(),
294 files=self.listfilediffs(ctx.files(), n),
297 files=self.listfilediffs(ctx.files(), n),
295 rev=ctx.rev(),
298 rev=ctx.rev(),
296 node=hex(n),
299 node=hex(n),
297 tags=self.nodetagsdict(n),
300 tags=self.nodetagsdict(n),
298 branches=self.nodebranchdict(ctx))
301 branches=self.nodebranchdict(ctx))
299
302
300 if count >= self.maxchanges:
303 if count >= self.maxchanges:
301 break
304 break
302
305
303 cl = self.repo.changelog
306 cl = self.repo.changelog
304 parity = paritygen(self.stripecount)
307 parity = paritygen(self.stripecount)
305
308
306 yield self.t('search',
309 yield self.t('search',
307 query=query,
310 query=query,
308 node=hex(cl.tip()),
311 node=hex(cl.tip()),
309 entries=changelist,
312 entries=changelist,
310 archives=self.archivelist("tip"))
313 archives=self.archivelist("tip"))
311
314
312 def changeset(self, ctx):
315 def changeset(self, ctx):
313 n = ctx.node()
316 n = ctx.node()
314 parents = ctx.parents()
317 parents = ctx.parents()
315 p1 = parents[0].node()
318 p1 = parents[0].node()
316
319
317 files = []
320 files = []
318 parity = paritygen(self.stripecount)
321 parity = paritygen(self.stripecount)
319 for f in ctx.files():
322 for f in ctx.files():
320 files.append(self.t("filenodelink",
323 files.append(self.t("filenodelink",
321 node=hex(n), file=f,
324 node=hex(n), file=f,
322 parity=parity.next()))
325 parity=parity.next()))
323
326
324 def diff(**map):
327 def diff(**map):
325 yield self.diff(p1, n, None)
328 yield self.diff(p1, n, None)
326
329
327 yield self.t('changeset',
330 yield self.t('changeset',
328 diff=diff,
331 diff=diff,
329 rev=ctx.rev(),
332 rev=ctx.rev(),
330 node=hex(n),
333 node=hex(n),
331 parent=self.siblings(parents),
334 parent=self.siblings(parents),
332 child=self.siblings(ctx.children()),
335 child=self.siblings(ctx.children()),
333 changesettag=self.showtag("changesettag",n),
336 changesettag=self.showtag("changesettag",n),
334 author=ctx.user(),
337 author=ctx.user(),
335 desc=ctx.description(),
338 desc=ctx.description(),
336 date=ctx.date(),
339 date=ctx.date(),
337 files=files,
340 files=files,
338 archives=self.archivelist(hex(n)),
341 archives=self.archivelist(hex(n)),
339 tags=self.nodetagsdict(n),
342 tags=self.nodetagsdict(n),
340 branches=self.nodebranchdict(ctx))
343 branches=self.nodebranchdict(ctx))
341
344
342 def filelog(self, fctx):
345 def filelog(self, fctx):
343 f = fctx.path()
346 f = fctx.path()
344 fl = fctx.filelog()
347 fl = fctx.filelog()
345 count = fl.count()
348 count = fl.count()
346 pagelen = self.maxshortchanges
349 pagelen = self.maxshortchanges
347 pos = fctx.filerev()
350 pos = fctx.filerev()
348 start = max(0, pos - pagelen + 1)
351 start = max(0, pos - pagelen + 1)
349 end = min(count, start + pagelen)
352 end = min(count, start + pagelen)
350 pos = end - 1
353 pos = end - 1
351 parity = paritygen(self.stripecount, offset=start-end)
354 parity = paritygen(self.stripecount, offset=start-end)
352
355
353 def entries(limit=0, **map):
356 def entries(limit=0, **map):
354 l = []
357 l = []
355
358
356 for i in xrange(start, end):
359 for i in xrange(start, end):
357 ctx = fctx.filectx(i)
360 ctx = fctx.filectx(i)
358 n = fl.node(i)
361 n = fl.node(i)
359
362
360 l.insert(0, {"parity": parity.next(),
363 l.insert(0, {"parity": parity.next(),
361 "filerev": i,
364 "filerev": i,
362 "file": f,
365 "file": f,
363 "node": hex(ctx.node()),
366 "node": hex(ctx.node()),
364 "author": ctx.user(),
367 "author": ctx.user(),
365 "date": ctx.date(),
368 "date": ctx.date(),
366 "rename": self.renamelink(fl, n),
369 "rename": self.renamelink(fl, n),
367 "parent": self.siblings(fctx.parents()),
370 "parent": self.siblings(fctx.parents()),
368 "child": self.siblings(fctx.children()),
371 "child": self.siblings(fctx.children()),
369 "desc": ctx.description()})
372 "desc": ctx.description()})
370
373
371 if limit > 0:
374 if limit > 0:
372 l = l[:limit]
375 l = l[:limit]
373
376
374 for e in l:
377 for e in l:
375 yield e
378 yield e
376
379
377 nodefunc = lambda x: fctx.filectx(fileid=x)
380 nodefunc = lambda x: fctx.filectx(fileid=x)
378 nav = revnavgen(pos, pagelen, count, nodefunc)
381 nav = revnavgen(pos, pagelen, count, nodefunc)
379 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
382 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
380 entries=lambda **x: entries(limit=0, **x),
383 entries=lambda **x: entries(limit=0, **x),
381 latestentry=lambda **x: entries(limit=1, **x))
384 latestentry=lambda **x: entries(limit=1, **x))
382
385
383 def filerevision(self, fctx):
386 def filerevision(self, fctx):
384 f = fctx.path()
387 f = fctx.path()
385 text = fctx.data()
388 text = fctx.data()
386 fl = fctx.filelog()
389 fl = fctx.filelog()
387 n = fctx.filenode()
390 n = fctx.filenode()
388 parity = paritygen(self.stripecount)
391 parity = paritygen(self.stripecount)
389
392
390 mt = mimetypes.guess_type(f)[0]
393 mt = mimetypes.guess_type(f)[0]
391 rawtext = text
394 rawtext = text
392 if util.binary(text):
395 if util.binary(text):
393 mt = mt or 'application/octet-stream'
396 mt = mt or 'application/octet-stream'
394 text = "(binary:%s)" % mt
397 text = "(binary:%s)" % mt
395 mt = mt or 'text/plain'
398 mt = mt or 'text/plain'
396
399
397 def lines():
400 def lines():
398 for l, t in enumerate(text.splitlines(1)):
401 for l, t in enumerate(text.splitlines(1)):
399 yield {"line": t,
402 yield {"line": t,
400 "linenumber": "% 6d" % (l + 1),
403 "linenumber": "% 6d" % (l + 1),
401 "parity": parity.next()}
404 "parity": parity.next()}
402
405
403 yield self.t("filerevision",
406 yield self.t("filerevision",
404 file=f,
407 file=f,
405 path=_up(f),
408 path=_up(f),
406 text=lines(),
409 text=lines(),
407 raw=rawtext,
410 raw=rawtext,
408 mimetype=mt,
411 mimetype=mt,
409 rev=fctx.rev(),
412 rev=fctx.rev(),
410 node=hex(fctx.node()),
413 node=hex(fctx.node()),
411 author=fctx.user(),
414 author=fctx.user(),
412 date=fctx.date(),
415 date=fctx.date(),
413 desc=fctx.description(),
416 desc=fctx.description(),
414 parent=self.siblings(fctx.parents()),
417 parent=self.siblings(fctx.parents()),
415 child=self.siblings(fctx.children()),
418 child=self.siblings(fctx.children()),
416 rename=self.renamelink(fl, n),
419 rename=self.renamelink(fl, n),
417 permissions=fctx.manifest().flags(f))
420 permissions=fctx.manifest().flags(f))
418
421
419 def fileannotate(self, fctx):
422 def fileannotate(self, fctx):
420 f = fctx.path()
423 f = fctx.path()
421 n = fctx.filenode()
424 n = fctx.filenode()
422 fl = fctx.filelog()
425 fl = fctx.filelog()
423 parity = paritygen(self.stripecount)
426 parity = paritygen(self.stripecount)
424
427
425 def annotate(**map):
428 def annotate(**map):
426 last = None
429 last = None
427 for f, l in fctx.annotate(follow=True):
430 for f, l in fctx.annotate(follow=True):
428 fnode = f.filenode()
431 fnode = f.filenode()
429 name = self.repo.ui.shortuser(f.user())
432 name = self.repo.ui.shortuser(f.user())
430
433
431 if last != fnode:
434 if last != fnode:
432 last = fnode
435 last = fnode
433
436
434 yield {"parity": parity.next(),
437 yield {"parity": parity.next(),
435 "node": hex(f.node()),
438 "node": hex(f.node()),
436 "rev": f.rev(),
439 "rev": f.rev(),
437 "author": name,
440 "author": name,
438 "file": f.path(),
441 "file": f.path(),
439 "line": l}
442 "line": l}
440
443
441 yield self.t("fileannotate",
444 yield self.t("fileannotate",
442 file=f,
445 file=f,
443 annotate=annotate,
446 annotate=annotate,
444 path=_up(f),
447 path=_up(f),
445 rev=fctx.rev(),
448 rev=fctx.rev(),
446 node=hex(fctx.node()),
449 node=hex(fctx.node()),
447 author=fctx.user(),
450 author=fctx.user(),
448 date=fctx.date(),
451 date=fctx.date(),
449 desc=fctx.description(),
452 desc=fctx.description(),
450 rename=self.renamelink(fl, n),
453 rename=self.renamelink(fl, n),
451 parent=self.siblings(fctx.parents()),
454 parent=self.siblings(fctx.parents()),
452 child=self.siblings(fctx.children()),
455 child=self.siblings(fctx.children()),
453 permissions=fctx.manifest().flags(f))
456 permissions=fctx.manifest().flags(f))
454
457
455 def manifest(self, ctx, path):
458 def manifest(self, ctx, path):
456 mf = ctx.manifest()
459 mf = ctx.manifest()
457 node = ctx.node()
460 node = ctx.node()
458
461
459 files = {}
462 files = {}
460 parity = paritygen(self.stripecount)
463 parity = paritygen(self.stripecount)
461
464
462 if path and path[-1] != "/":
465 if path and path[-1] != "/":
463 path += "/"
466 path += "/"
464 l = len(path)
467 l = len(path)
465 abspath = "/" + path
468 abspath = "/" + path
466
469
467 for f, n in mf.items():
470 for f, n in mf.items():
468 if f[:l] != path:
471 if f[:l] != path:
469 continue
472 continue
470 remain = f[l:]
473 remain = f[l:]
471 if "/" in remain:
474 if "/" in remain:
472 short = remain[:remain.index("/") + 1] # bleah
475 short = remain[:remain.index("/") + 1] # bleah
473 files[short] = (f, None)
476 files[short] = (f, None)
474 else:
477 else:
475 short = os.path.basename(remain)
478 short = os.path.basename(remain)
476 files[short] = (f, n)
479 files[short] = (f, n)
477
480
478 def filelist(**map):
481 def filelist(**map):
479 fl = files.keys()
482 fl = files.keys()
480 fl.sort()
483 fl.sort()
481 for f in fl:
484 for f in fl:
482 full, fnode = files[f]
485 full, fnode = files[f]
483 if not fnode:
486 if not fnode:
484 continue
487 continue
485
488
486 fctx = ctx.filectx(full)
489 fctx = ctx.filectx(full)
487 yield {"file": full,
490 yield {"file": full,
488 "parity": parity.next(),
491 "parity": parity.next(),
489 "basename": f,
492 "basename": f,
490 "date": fctx.changectx().date(),
493 "date": fctx.changectx().date(),
491 "size": fctx.size(),
494 "size": fctx.size(),
492 "permissions": mf.flags(full)}
495 "permissions": mf.flags(full)}
493
496
494 def dirlist(**map):
497 def dirlist(**map):
495 fl = files.keys()
498 fl = files.keys()
496 fl.sort()
499 fl.sort()
497 for f in fl:
500 for f in fl:
498 full, fnode = files[f]
501 full, fnode = files[f]
499 if fnode:
502 if fnode:
500 continue
503 continue
501
504
502 yield {"parity": parity.next(),
505 yield {"parity": parity.next(),
503 "path": "%s%s" % (abspath, f),
506 "path": "%s%s" % (abspath, f),
504 "basename": f[:-1]}
507 "basename": f[:-1]}
505
508
506 yield self.t("manifest",
509 yield self.t("manifest",
507 rev=ctx.rev(),
510 rev=ctx.rev(),
508 node=hex(node),
511 node=hex(node),
509 path=abspath,
512 path=abspath,
510 up=_up(abspath),
513 up=_up(abspath),
511 upparity=parity.next(),
514 upparity=parity.next(),
512 fentries=filelist,
515 fentries=filelist,
513 dentries=dirlist,
516 dentries=dirlist,
514 archives=self.archivelist(hex(node)),
517 archives=self.archivelist(hex(node)),
515 tags=self.nodetagsdict(node),
518 tags=self.nodetagsdict(node),
516 branches=self.nodebranchdict(ctx))
519 branches=self.nodebranchdict(ctx))
517
520
518 def tags(self):
521 def tags(self):
519 i = self.repo.tagslist()
522 i = self.repo.tagslist()
520 i.reverse()
523 i.reverse()
521 parity = paritygen(self.stripecount)
524 parity = paritygen(self.stripecount)
522
525
523 def entries(notip=False,limit=0, **map):
526 def entries(notip=False,limit=0, **map):
524 count = 0
527 count = 0
525 for k, n in i:
528 for k, n in i:
526 if notip and k == "tip":
529 if notip and k == "tip":
527 continue
530 continue
528 if limit > 0 and count >= limit:
531 if limit > 0 and count >= limit:
529 continue
532 continue
530 count = count + 1
533 count = count + 1
531 yield {"parity": parity.next(),
534 yield {"parity": parity.next(),
532 "tag": k,
535 "tag": k,
533 "date": self.repo.changectx(n).date(),
536 "date": self.repo.changectx(n).date(),
534 "node": hex(n)}
537 "node": hex(n)}
535
538
536 yield self.t("tags",
539 yield self.t("tags",
537 node=hex(self.repo.changelog.tip()),
540 node=hex(self.repo.changelog.tip()),
538 entries=lambda **x: entries(False,0, **x),
541 entries=lambda **x: entries(False,0, **x),
539 entriesnotip=lambda **x: entries(True,0, **x),
542 entriesnotip=lambda **x: entries(True,0, **x),
540 latestentry=lambda **x: entries(True,1, **x))
543 latestentry=lambda **x: entries(True,1, **x))
541
544
542 def summary(self):
545 def summary(self):
543 i = self.repo.tagslist()
546 i = self.repo.tagslist()
544 i.reverse()
547 i.reverse()
545
548
546 def tagentries(**map):
549 def tagentries(**map):
547 parity = paritygen(self.stripecount)
550 parity = paritygen(self.stripecount)
548 count = 0
551 count = 0
549 for k, n in i:
552 for k, n in i:
550 if k == "tip": # skip tip
553 if k == "tip": # skip tip
551 continue;
554 continue;
552
555
553 count += 1
556 count += 1
554 if count > 10: # limit to 10 tags
557 if count > 10: # limit to 10 tags
555 break;
558 break;
556
559
557 yield self.t("tagentry",
560 yield self.t("tagentry",
558 parity=parity.next(),
561 parity=parity.next(),
559 tag=k,
562 tag=k,
560 node=hex(n),
563 node=hex(n),
561 date=self.repo.changectx(n).date())
564 date=self.repo.changectx(n).date())
562
565
563
566
564 def branches(**map):
567 def branches(**map):
565 parity = paritygen(self.stripecount)
568 parity = paritygen(self.stripecount)
566
569
567 b = self.repo.branchtags()
570 b = self.repo.branchtags()
568 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
571 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
569 l.sort()
572 l.sort()
570
573
571 for r,n,t in l:
574 for r,n,t in l:
572 ctx = self.repo.changectx(n)
575 ctx = self.repo.changectx(n)
573
576
574 yield {'parity': parity.next(),
577 yield {'parity': parity.next(),
575 'branch': t,
578 'branch': t,
576 'node': hex(n),
579 'node': hex(n),
577 'date': ctx.date()}
580 'date': ctx.date()}
578
581
579 def changelist(**map):
582 def changelist(**map):
580 parity = paritygen(self.stripecount, offset=start-end)
583 parity = paritygen(self.stripecount, offset=start-end)
581 l = [] # build a list in forward order for efficiency
584 l = [] # build a list in forward order for efficiency
582 for i in xrange(start, end):
585 for i in xrange(start, end):
583 ctx = self.repo.changectx(i)
586 ctx = self.repo.changectx(i)
584 n = ctx.node()
587 n = ctx.node()
585 hn = hex(n)
588 hn = hex(n)
586
589
587 l.insert(0, self.t(
590 l.insert(0, self.t(
588 'shortlogentry',
591 'shortlogentry',
589 parity=parity.next(),
592 parity=parity.next(),
590 author=ctx.user(),
593 author=ctx.user(),
591 desc=ctx.description(),
594 desc=ctx.description(),
592 date=ctx.date(),
595 date=ctx.date(),
593 rev=i,
596 rev=i,
594 node=hn,
597 node=hn,
595 tags=self.nodetagsdict(n),
598 tags=self.nodetagsdict(n),
596 branches=self.nodebranchdict(ctx)))
599 branches=self.nodebranchdict(ctx)))
597
600
598 yield l
601 yield l
599
602
600 cl = self.repo.changelog
603 cl = self.repo.changelog
601 count = cl.count()
604 count = cl.count()
602 start = max(0, count - self.maxchanges)
605 start = max(0, count - self.maxchanges)
603 end = min(count, start + self.maxchanges)
606 end = min(count, start + self.maxchanges)
604
607
605 yield self.t("summary",
608 yield self.t("summary",
606 desc=self.config("web", "description", "unknown"),
609 desc=self.config("web", "description", "unknown"),
607 owner=(self.config("ui", "username") or # preferred
610 owner=(self.config("ui", "username") or # preferred
608 self.config("web", "contact") or # deprecated
611 self.config("web", "contact") or # deprecated
609 self.config("web", "author", "unknown")), # also
612 self.config("web", "author", "unknown")), # also
610 lastchange=cl.read(cl.tip())[2],
613 lastchange=cl.read(cl.tip())[2],
611 tags=tagentries,
614 tags=tagentries,
612 branches=branches,
615 branches=branches,
613 shortlog=changelist,
616 shortlog=changelist,
614 node=hex(cl.tip()),
617 node=hex(cl.tip()),
615 archives=self.archivelist("tip"))
618 archives=self.archivelist("tip"))
616
619
617 def filediff(self, fctx):
620 def filediff(self, fctx):
618 n = fctx.node()
621 n = fctx.node()
619 path = fctx.path()
622 path = fctx.path()
620 parents = fctx.parents()
623 parents = fctx.parents()
621 p1 = parents and parents[0].node() or nullid
624 p1 = parents and parents[0].node() or nullid
622
625
623 def diff(**map):
626 def diff(**map):
624 yield self.diff(p1, n, [path])
627 yield self.diff(p1, n, [path])
625
628
626 yield self.t("filediff",
629 yield self.t("filediff",
627 file=path,
630 file=path,
628 node=hex(n),
631 node=hex(n),
629 rev=fctx.rev(),
632 rev=fctx.rev(),
630 parent=self.siblings(parents),
633 parent=self.siblings(parents),
631 child=self.siblings(fctx.children()),
634 child=self.siblings(fctx.children()),
632 diff=diff)
635 diff=diff)
633
636
634 archive_specs = {
637 archive_specs = {
635 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
638 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
636 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
639 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
637 'zip': ('application/zip', 'zip', '.zip', None),
640 'zip': ('application/zip', 'zip', '.zip', None),
638 }
641 }
639
642
640 def archive(self, req, key, type_):
643 def archive(self, req, key, type_):
641 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
644 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
642 cnode = self.repo.lookup(key)
645 cnode = self.repo.lookup(key)
643 arch_version = key
646 arch_version = key
644 if cnode == key or key == 'tip':
647 if cnode == key or key == 'tip':
645 arch_version = short(cnode)
648 arch_version = short(cnode)
646 name = "%s-%s" % (reponame, arch_version)
649 name = "%s-%s" % (reponame, arch_version)
647 mimetype, artype, extension, encoding = self.archive_specs[type_]
650 mimetype, artype, extension, encoding = self.archive_specs[type_]
648 headers = [('Content-type', mimetype),
651 headers = [('Content-type', mimetype),
649 ('Content-disposition', 'attachment; filename=%s%s' %
652 ('Content-disposition', 'attachment; filename=%s%s' %
650 (name, extension))]
653 (name, extension))]
651 if encoding:
654 if encoding:
652 headers.append(('Content-encoding', encoding))
655 headers.append(('Content-encoding', encoding))
653 req.header(headers)
656 req.header(headers)
654 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
657 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
655
658
656 # add tags to things
659 # add tags to things
657 # tags -> list of changesets corresponding to tags
660 # tags -> list of changesets corresponding to tags
658 # find tag, changeset, file
661 # find tag, changeset, file
659
662
660 def cleanpath(self, path):
663 def cleanpath(self, path):
661 path = path.lstrip('/')
664 path = path.lstrip('/')
662 return util.canonpath(self.repo.root, '', path)
665 return util.canonpath(self.repo.root, '', path)
663
666
664 def run(self):
667 def run(self):
665 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
668 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
666 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
669 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
667 import mercurial.hgweb.wsgicgi as wsgicgi
670 import mercurial.hgweb.wsgicgi as wsgicgi
668 from request import wsgiapplication
671 from request import wsgiapplication
669 def make_web_app():
672 def make_web_app():
670 return self
673 return self
671 wsgicgi.launch(wsgiapplication(make_web_app))
674 wsgicgi.launch(wsgiapplication(make_web_app))
672
675
673 def run_wsgi(self, req):
676 def run_wsgi(self, req):
674 def header(**map):
677 def header(**map):
675 header_file = cStringIO.StringIO(
678 header_file = cStringIO.StringIO(
676 ''.join(self.t("header", encoding=self.encoding, **map)))
679 ''.join(self.t("header", encoding=self.encoding, **map)))
677 msg = mimetools.Message(header_file, 0)
680 msg = mimetools.Message(header_file, 0)
678 req.header(msg.items())
681 req.header(msg.items())
679 yield header_file.read()
682 yield header_file.read()
680
683
681 def rawfileheader(**map):
684 def rawfileheader(**map):
682 req.header([('Content-type', map['mimetype']),
685 req.header([('Content-type', map['mimetype']),
683 ('Content-disposition', 'filename=%s' % map['file']),
686 ('Content-disposition', 'filename=%s' % map['file']),
684 ('Content-length', str(len(map['raw'])))])
687 ('Content-length', str(len(map['raw'])))])
685 yield ''
688 yield ''
686
689
687 def footer(**map):
690 def footer(**map):
688 yield self.t("footer", **map)
691 yield self.t("footer", **map)
689
692
690 def motd(**map):
693 def motd(**map):
691 yield self.config("web", "motd", "")
694 yield self.config("web", "motd", "")
692
695
693 def expand_form(form):
696 def expand_form(form):
694 shortcuts = {
697 shortcuts = {
695 'cl': [('cmd', ['changelog']), ('rev', None)],
698 'cl': [('cmd', ['changelog']), ('rev', None)],
696 'sl': [('cmd', ['shortlog']), ('rev', None)],
699 'sl': [('cmd', ['shortlog']), ('rev', None)],
697 'cs': [('cmd', ['changeset']), ('node', None)],
700 'cs': [('cmd', ['changeset']), ('node', None)],
698 'f': [('cmd', ['file']), ('filenode', None)],
701 'f': [('cmd', ['file']), ('filenode', None)],
699 'fl': [('cmd', ['filelog']), ('filenode', None)],
702 'fl': [('cmd', ['filelog']), ('filenode', None)],
700 'fd': [('cmd', ['filediff']), ('node', None)],
703 'fd': [('cmd', ['filediff']), ('node', None)],
701 'fa': [('cmd', ['annotate']), ('filenode', None)],
704 'fa': [('cmd', ['annotate']), ('filenode', None)],
702 'mf': [('cmd', ['manifest']), ('manifest', None)],
705 'mf': [('cmd', ['manifest']), ('manifest', None)],
703 'ca': [('cmd', ['archive']), ('node', None)],
706 'ca': [('cmd', ['archive']), ('node', None)],
704 'tags': [('cmd', ['tags'])],
707 'tags': [('cmd', ['tags'])],
705 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
708 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
706 'static': [('cmd', ['static']), ('file', None)]
709 'static': [('cmd', ['static']), ('file', None)]
707 }
710 }
708
711
709 for k in shortcuts.iterkeys():
712 for k in shortcuts.iterkeys():
710 if form.has_key(k):
713 if form.has_key(k):
711 for name, value in shortcuts[k]:
714 for name, value in shortcuts[k]:
712 if value is None:
715 if value is None:
713 value = form[k]
716 value = form[k]
714 form[name] = value
717 form[name] = value
715 del form[k]
718 del form[k]
716
719
717 def rewrite_request(req):
720 def rewrite_request(req):
718 '''translate new web interface to traditional format'''
721 '''translate new web interface to traditional format'''
719
722
720 def spliturl(req):
723 def spliturl(req):
721 def firstitem(query):
724 def firstitem(query):
722 return query.split('&', 1)[0].split(';', 1)[0]
725 return query.split('&', 1)[0].split(';', 1)[0]
723
726
724 def normurl(url):
727 def normurl(url):
725 inner = '/'.join([x for x in url.split('/') if x])
728 inner = '/'.join([x for x in url.split('/') if x])
726 tl = len(url) > 1 and url.endswith('/') and '/' or ''
729 tl = len(url) > 1 and url.endswith('/') and '/' or ''
727
730
728 return '%s%s%s' % (url.startswith('/') and '/' or '',
731 return '%s%s%s' % (url.startswith('/') and '/' or '',
729 inner, tl)
732 inner, tl)
730
733
731 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
734 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
732 pi = normurl(req.env.get('PATH_INFO', ''))
735 pi = normurl(req.env.get('PATH_INFO', ''))
733 if pi:
736 if pi:
734 # strip leading /
737 # strip leading /
735 pi = pi[1:]
738 pi = pi[1:]
736 if pi:
739 if pi:
737 root = root[:root.rfind(pi)]
740 root = root[:root.rfind(pi)]
738 if req.env.has_key('REPO_NAME'):
741 if req.env.has_key('REPO_NAME'):
739 rn = req.env['REPO_NAME'] + '/'
742 rn = req.env['REPO_NAME'] + '/'
740 root += rn
743 root += rn
741 query = pi[len(rn):]
744 query = pi[len(rn):]
742 else:
745 else:
743 query = pi
746 query = pi
744 else:
747 else:
745 root += '?'
748 root += '?'
746 query = firstitem(req.env['QUERY_STRING'])
749 query = firstitem(req.env['QUERY_STRING'])
747
750
748 return (root, query)
751 return (root, query)
749
752
750 req.url, query = spliturl(req)
753 req.url, query = spliturl(req)
751
754
752 if req.form.has_key('cmd'):
755 if req.form.has_key('cmd'):
753 # old style
756 # old style
754 return
757 return
755
758
756 args = query.split('/', 2)
759 args = query.split('/', 2)
757 if not args or not args[0]:
760 if not args or not args[0]:
758 return
761 return
759
762
760 cmd = args.pop(0)
763 cmd = args.pop(0)
761 style = cmd.rfind('-')
764 style = cmd.rfind('-')
762 if style != -1:
765 if style != -1:
763 req.form['style'] = [cmd[:style]]
766 req.form['style'] = [cmd[:style]]
764 cmd = cmd[style+1:]
767 cmd = cmd[style+1:]
765 # avoid accepting e.g. style parameter as command
768 # avoid accepting e.g. style parameter as command
766 if hasattr(self, 'do_' + cmd):
769 if hasattr(self, 'do_' + cmd):
767 req.form['cmd'] = [cmd]
770 req.form['cmd'] = [cmd]
768
771
769 if args and args[0]:
772 if args and args[0]:
770 node = args.pop(0)
773 node = args.pop(0)
771 req.form['node'] = [node]
774 req.form['node'] = [node]
772 if args:
775 if args:
773 req.form['file'] = args
776 req.form['file'] = args
774
777
775 if cmd == 'static':
778 if cmd == 'static':
776 req.form['file'] = req.form['node']
779 req.form['file'] = req.form['node']
777 elif cmd == 'archive':
780 elif cmd == 'archive':
778 fn = req.form['node'][0]
781 fn = req.form['node'][0]
779 for type_, spec in self.archive_specs.iteritems():
782 for type_, spec in self.archive_specs.iteritems():
780 ext = spec[2]
783 ext = spec[2]
781 if fn.endswith(ext):
784 if fn.endswith(ext):
782 req.form['node'] = [fn[:-len(ext)]]
785 req.form['node'] = [fn[:-len(ext)]]
783 req.form['type'] = [type_]
786 req.form['type'] = [type_]
784
787
785 def sessionvars(**map):
788 def sessionvars(**map):
786 fields = []
789 fields = []
787 if req.form.has_key('style'):
790 if req.form.has_key('style'):
788 style = req.form['style'][0]
791 style = req.form['style'][0]
789 if style != self.config('web', 'style', ''):
792 if style != self.config('web', 'style', ''):
790 fields.append(('style', style))
793 fields.append(('style', style))
791
794
792 separator = req.url[-1] == '?' and ';' or '?'
795 separator = req.url[-1] == '?' and ';' or '?'
793 for name, value in fields:
796 for name, value in fields:
794 yield dict(name=name, value=value, separator=separator)
797 yield dict(name=name, value=value, separator=separator)
795 separator = ';'
798 separator = ';'
796
799
797 self.refresh()
800 self.refresh()
798
801
799 expand_form(req.form)
802 expand_form(req.form)
800 rewrite_request(req)
803 rewrite_request(req)
801
804
802 style = self.config("web", "style", "")
805 style = self.config("web", "style", "")
803 if req.form.has_key('style'):
806 if req.form.has_key('style'):
804 style = req.form['style'][0]
807 style = req.form['style'][0]
805 mapfile = style_map(self.templatepath, style)
808 mapfile = style_map(self.templatepath, style)
806
809
807 proto = req.env.get('wsgi.url_scheme')
810 proto = req.env.get('wsgi.url_scheme')
808 if proto == 'https':
811 if proto == 'https':
809 proto = 'https'
812 proto = 'https'
810 default_port = "443"
813 default_port = "443"
811 else:
814 else:
812 proto = 'http'
815 proto = 'http'
813 default_port = "80"
816 default_port = "80"
814
817
815 port = req.env["SERVER_PORT"]
818 port = req.env["SERVER_PORT"]
816 port = port != default_port and (":" + port) or ""
819 port = port != default_port and (":" + port) or ""
817 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
820 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
818 staticurl = self.config("web", "staticurl") or req.url + 'static/'
821 staticurl = self.config("web", "staticurl") or req.url + 'static/'
819 if not staticurl.endswith('/'):
822 if not staticurl.endswith('/'):
820 staticurl += '/'
823 staticurl += '/'
821
824
822 if not self.reponame:
825 if not self.reponame:
823 self.reponame = (self.config("web", "name")
826 self.reponame = (self.config("web", "name")
824 or req.env.get('REPO_NAME')
827 or req.env.get('REPO_NAME')
825 or req.url.strip('/') or self.repo.root)
828 or req.url.strip('/') or self.repo.root)
826
829
827 self.t = templater.templater(mapfile, templater.common_filters,
830 self.t = templater.templater(mapfile, templater.common_filters,
828 defaults={"url": req.url,
831 defaults={"url": req.url,
829 "staticurl": staticurl,
832 "staticurl": staticurl,
830 "urlbase": urlbase,
833 "urlbase": urlbase,
831 "repo": self.reponame,
834 "repo": self.reponame,
832 "header": header,
835 "header": header,
833 "footer": footer,
836 "footer": footer,
834 "motd": motd,
837 "motd": motd,
835 "rawfileheader": rawfileheader,
838 "rawfileheader": rawfileheader,
836 "sessionvars": sessionvars
839 "sessionvars": sessionvars
837 })
840 })
838
841
839 try:
842 try:
840 if not req.form.has_key('cmd'):
843 if not req.form.has_key('cmd'):
841 req.form['cmd'] = [self.t.cache['default']]
844 req.form['cmd'] = [self.t.cache['default']]
842
845
843 cmd = req.form['cmd'][0]
846 cmd = req.form['cmd'][0]
844
847
845 method = getattr(self, 'do_' + cmd, None)
848 method = getattr(self, 'do_' + cmd, None)
846 if method:
849 if method:
847 try:
850 try:
848 method(req)
851 method(req)
849 except (hg.RepoError, revlog.RevlogError), inst:
852 except (hg.RepoError, revlog.RevlogError), inst:
850 req.write(self.t("error", error=str(inst)))
853 req.write(self.t("error", error=str(inst)))
851 else:
854 else:
852 req.write(self.t("error", error='No such method: ' + cmd))
855 req.write(self.t("error", error='No such method: ' + cmd))
853 finally:
856 finally:
854 self.t = None
857 self.t = None
855
858
856 def changectx(self, req):
859 def changectx(self, req):
857 if req.form.has_key('node'):
860 if req.form.has_key('node'):
858 changeid = req.form['node'][0]
861 changeid = req.form['node'][0]
859 elif req.form.has_key('manifest'):
862 elif req.form.has_key('manifest'):
860 changeid = req.form['manifest'][0]
863 changeid = req.form['manifest'][0]
861 else:
864 else:
862 changeid = self.repo.changelog.count() - 1
865 changeid = self.repo.changelog.count() - 1
863
866
864 try:
867 try:
865 ctx = self.repo.changectx(changeid)
868 ctx = self.repo.changectx(changeid)
866 except hg.RepoError:
869 except hg.RepoError:
867 man = self.repo.manifest
870 man = self.repo.manifest
868 mn = man.lookup(changeid)
871 mn = man.lookup(changeid)
869 ctx = self.repo.changectx(man.linkrev(mn))
872 ctx = self.repo.changectx(man.linkrev(mn))
870
873
871 return ctx
874 return ctx
872
875
873 def filectx(self, req):
876 def filectx(self, req):
874 path = self.cleanpath(req.form['file'][0])
877 path = self.cleanpath(req.form['file'][0])
875 if req.form.has_key('node'):
878 if req.form.has_key('node'):
876 changeid = req.form['node'][0]
879 changeid = req.form['node'][0]
877 else:
880 else:
878 changeid = req.form['filenode'][0]
881 changeid = req.form['filenode'][0]
879 try:
882 try:
880 ctx = self.repo.changectx(changeid)
883 ctx = self.repo.changectx(changeid)
881 fctx = ctx.filectx(path)
884 fctx = ctx.filectx(path)
882 except hg.RepoError:
885 except hg.RepoError:
883 fctx = self.repo.filectx(path, fileid=changeid)
886 fctx = self.repo.filectx(path, fileid=changeid)
884
887
885 return fctx
888 return fctx
886
889
887 def do_log(self, req):
890 def do_log(self, req):
888 if req.form.has_key('file') and req.form['file'][0]:
891 if req.form.has_key('file') and req.form['file'][0]:
889 self.do_filelog(req)
892 self.do_filelog(req)
890 else:
893 else:
891 self.do_changelog(req)
894 self.do_changelog(req)
892
895
893 def do_rev(self, req):
896 def do_rev(self, req):
894 self.do_changeset(req)
897 self.do_changeset(req)
895
898
896 def do_file(self, req):
899 def do_file(self, req):
897 path = self.cleanpath(req.form.get('file', [''])[0])
900 path = self.cleanpath(req.form.get('file', [''])[0])
898 if path:
901 if path:
899 try:
902 try:
900 req.write(self.filerevision(self.filectx(req)))
903 req.write(self.filerevision(self.filectx(req)))
901 return
904 return
902 except revlog.LookupError:
905 except revlog.LookupError:
903 pass
906 pass
904
907
905 req.write(self.manifest(self.changectx(req), path))
908 req.write(self.manifest(self.changectx(req), path))
906
909
907 def do_diff(self, req):
910 def do_diff(self, req):
908 self.do_filediff(req)
911 self.do_filediff(req)
909
912
910 def do_changelog(self, req, shortlog = False):
913 def do_changelog(self, req, shortlog = False):
911 if req.form.has_key('node'):
914 if req.form.has_key('node'):
912 ctx = self.changectx(req)
915 ctx = self.changectx(req)
913 else:
916 else:
914 if req.form.has_key('rev'):
917 if req.form.has_key('rev'):
915 hi = req.form['rev'][0]
918 hi = req.form['rev'][0]
916 else:
919 else:
917 hi = self.repo.changelog.count() - 1
920 hi = self.repo.changelog.count() - 1
918 try:
921 try:
919 ctx = self.repo.changectx(hi)
922 ctx = self.repo.changectx(hi)
920 except hg.RepoError:
923 except hg.RepoError:
921 req.write(self.search(hi)) # XXX redirect to 404 page?
924 req.write(self.search(hi)) # XXX redirect to 404 page?
922 return
925 return
923
926
924 req.write(self.changelog(ctx, shortlog = shortlog))
927 req.write(self.changelog(ctx, shortlog = shortlog))
925
928
926 def do_shortlog(self, req):
929 def do_shortlog(self, req):
927 self.do_changelog(req, shortlog = True)
930 self.do_changelog(req, shortlog = True)
928
931
929 def do_changeset(self, req):
932 def do_changeset(self, req):
930 req.write(self.changeset(self.changectx(req)))
933 req.write(self.changeset(self.changectx(req)))
931
934
932 def do_manifest(self, req):
935 def do_manifest(self, req):
933 req.write(self.manifest(self.changectx(req),
936 req.write(self.manifest(self.changectx(req),
934 self.cleanpath(req.form['path'][0])))
937 self.cleanpath(req.form['path'][0])))
935
938
936 def do_tags(self, req):
939 def do_tags(self, req):
937 req.write(self.tags())
940 req.write(self.tags())
938
941
939 def do_summary(self, req):
942 def do_summary(self, req):
940 req.write(self.summary())
943 req.write(self.summary())
941
944
942 def do_filediff(self, req):
945 def do_filediff(self, req):
943 req.write(self.filediff(self.filectx(req)))
946 req.write(self.filediff(self.filectx(req)))
944
947
945 def do_annotate(self, req):
948 def do_annotate(self, req):
946 req.write(self.fileannotate(self.filectx(req)))
949 req.write(self.fileannotate(self.filectx(req)))
947
950
948 def do_filelog(self, req):
951 def do_filelog(self, req):
949 req.write(self.filelog(self.filectx(req)))
952 req.write(self.filelog(self.filectx(req)))
950
953
951 def do_lookup(self, req):
954 def do_lookup(self, req):
952 try:
955 try:
953 r = hex(self.repo.lookup(req.form['key'][0]))
956 r = hex(self.repo.lookup(req.form['key'][0]))
954 success = 1
957 success = 1
955 except Exception,inst:
958 except Exception,inst:
956 r = str(inst)
959 r = str(inst)
957 success = 0
960 success = 0
958 resp = "%s %s\n" % (success, r)
961 resp = "%s %s\n" % (success, r)
959 req.httphdr("application/mercurial-0.1", length=len(resp))
962 req.httphdr("application/mercurial-0.1", length=len(resp))
960 req.write(resp)
963 req.write(resp)
961
964
962 def do_heads(self, req):
965 def do_heads(self, req):
963 resp = " ".join(map(hex, self.repo.heads())) + "\n"
966 resp = " ".join(map(hex, self.repo.heads())) + "\n"
964 req.httphdr("application/mercurial-0.1", length=len(resp))
967 req.httphdr("application/mercurial-0.1", length=len(resp))
965 req.write(resp)
968 req.write(resp)
966
969
967 def do_branches(self, req):
970 def do_branches(self, req):
968 nodes = []
971 nodes = []
969 if req.form.has_key('nodes'):
972 if req.form.has_key('nodes'):
970 nodes = map(bin, req.form['nodes'][0].split(" "))
973 nodes = map(bin, req.form['nodes'][0].split(" "))
971 resp = cStringIO.StringIO()
974 resp = cStringIO.StringIO()
972 for b in self.repo.branches(nodes):
975 for b in self.repo.branches(nodes):
973 resp.write(" ".join(map(hex, b)) + "\n")
976 resp.write(" ".join(map(hex, b)) + "\n")
974 resp = resp.getvalue()
977 resp = resp.getvalue()
975 req.httphdr("application/mercurial-0.1", length=len(resp))
978 req.httphdr("application/mercurial-0.1", length=len(resp))
976 req.write(resp)
979 req.write(resp)
977
980
978 def do_between(self, req):
981 def do_between(self, req):
979 if req.form.has_key('pairs'):
982 if req.form.has_key('pairs'):
980 pairs = [map(bin, p.split("-"))
983 pairs = [map(bin, p.split("-"))
981 for p in req.form['pairs'][0].split(" ")]
984 for p in req.form['pairs'][0].split(" ")]
982 resp = cStringIO.StringIO()
985 resp = cStringIO.StringIO()
983 for b in self.repo.between(pairs):
986 for b in self.repo.between(pairs):
984 resp.write(" ".join(map(hex, b)) + "\n")
987 resp.write(" ".join(map(hex, b)) + "\n")
985 resp = resp.getvalue()
988 resp = resp.getvalue()
986 req.httphdr("application/mercurial-0.1", length=len(resp))
989 req.httphdr("application/mercurial-0.1", length=len(resp))
987 req.write(resp)
990 req.write(resp)
988
991
989 def do_changegroup(self, req):
992 def do_changegroup(self, req):
990 req.httphdr("application/mercurial-0.1")
993 req.httphdr("application/mercurial-0.1")
991 nodes = []
994 nodes = []
992 if not self.allowpull:
995 if not self.allowpull:
993 return
996 return
994
997
995 if req.form.has_key('roots'):
998 if req.form.has_key('roots'):
996 nodes = map(bin, req.form['roots'][0].split(" "))
999 nodes = map(bin, req.form['roots'][0].split(" "))
997
1000
998 z = zlib.compressobj()
1001 z = zlib.compressobj()
999 f = self.repo.changegroup(nodes, 'serve')
1002 f = self.repo.changegroup(nodes, 'serve')
1000 while 1:
1003 while 1:
1001 chunk = f.read(4096)
1004 chunk = f.read(4096)
1002 if not chunk:
1005 if not chunk:
1003 break
1006 break
1004 req.write(z.compress(chunk))
1007 req.write(z.compress(chunk))
1005
1008
1006 req.write(z.flush())
1009 req.write(z.flush())
1007
1010
1008 def do_changegroupsubset(self, req):
1011 def do_changegroupsubset(self, req):
1009 req.httphdr("application/mercurial-0.1")
1012 req.httphdr("application/mercurial-0.1")
1010 bases = []
1013 bases = []
1011 heads = []
1014 heads = []
1012 if not self.allowpull:
1015 if not self.allowpull:
1013 return
1016 return
1014
1017
1015 if req.form.has_key('bases'):
1018 if req.form.has_key('bases'):
1016 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
1019 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
1017 if req.form.has_key('heads'):
1020 if req.form.has_key('heads'):
1018 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
1021 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
1019
1022
1020 z = zlib.compressobj()
1023 z = zlib.compressobj()
1021 f = self.repo.changegroupsubset(bases, heads, 'serve')
1024 f = self.repo.changegroupsubset(bases, heads, 'serve')
1022 while 1:
1025 while 1:
1023 chunk = f.read(4096)
1026 chunk = f.read(4096)
1024 if not chunk:
1027 if not chunk:
1025 break
1028 break
1026 req.write(z.compress(chunk))
1029 req.write(z.compress(chunk))
1027
1030
1028 req.write(z.flush())
1031 req.write(z.flush())
1029
1032
1030 def do_archive(self, req):
1033 def do_archive(self, req):
1031 type_ = req.form['type'][0]
1034 type_ = req.form['type'][0]
1032 allowed = self.configlist("web", "allow_archive")
1035 allowed = self.configlist("web", "allow_archive")
1033 if (type_ in self.archives and (type_ in allowed or
1036 if (type_ in self.archives and (type_ in allowed or
1034 self.configbool("web", "allow" + type_, False))):
1037 self.configbool("web", "allow" + type_, False))):
1035 self.archive(req, req.form['node'][0], type_)
1038 self.archive(req, req.form['node'][0], type_)
1036 return
1039 return
1037
1040
1038 req.write(self.t("error"))
1041 req.write(self.t("error"))
1039
1042
1040 def do_static(self, req):
1043 def do_static(self, req):
1041 fname = req.form['file'][0]
1044 fname = req.form['file'][0]
1042 # a repo owner may set web.static in .hg/hgrc to get any file
1045 # a repo owner may set web.static in .hg/hgrc to get any file
1043 # readable by the user running the CGI script
1046 # readable by the user running the CGI script
1044 static = self.config("web", "static",
1047 static = self.config("web", "static",
1045 os.path.join(self.templatepath, "static"),
1048 os.path.join(self.templatepath, "static"),
1046 untrusted=False)
1049 untrusted=False)
1047 req.write(staticfile(static, fname, req)
1050 req.write(staticfile(static, fname, req)
1048 or self.t("error", error="%r not found" % fname))
1051 or self.t("error", error="%r not found" % fname))
1049
1052
1050 def do_capabilities(self, req):
1053 def do_capabilities(self, req):
1051 caps = ['lookup', 'changegroupsubset']
1054 caps = ['lookup', 'changegroupsubset']
1052 if self.configbool('server', 'uncompressed'):
1055 if self.configbool('server', 'uncompressed'):
1053 caps.append('stream=%d' % self.repo.changelog.version)
1056 caps.append('stream=%d' % self.repo.changelog.version)
1054 # XXX: make configurable and/or share code with do_unbundle:
1057 # XXX: make configurable and/or share code with do_unbundle:
1055 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1058 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1056 if unbundleversions:
1059 if unbundleversions:
1057 caps.append('unbundle=%s' % ','.join(unbundleversions))
1060 caps.append('unbundle=%s' % ','.join(unbundleversions))
1058 resp = ' '.join(caps)
1061 resp = ' '.join(caps)
1059 req.httphdr("application/mercurial-0.1", length=len(resp))
1062 req.httphdr("application/mercurial-0.1", length=len(resp))
1060 req.write(resp)
1063 req.write(resp)
1061
1064
1062 def check_perm(self, req, op, default):
1065 def check_perm(self, req, op, default):
1063 '''check permission for operation based on user auth.
1066 '''check permission for operation based on user auth.
1064 return true if op allowed, else false.
1067 return true if op allowed, else false.
1065 default is policy to use if no config given.'''
1068 default is policy to use if no config given.'''
1066
1069
1067 user = req.env.get('REMOTE_USER')
1070 user = req.env.get('REMOTE_USER')
1068
1071
1069 deny = self.configlist('web', 'deny_' + op)
1072 deny = self.configlist('web', 'deny_' + op)
1070 if deny and (not user or deny == ['*'] or user in deny):
1073 if deny and (not user or deny == ['*'] or user in deny):
1071 return False
1074 return False
1072
1075
1073 allow = self.configlist('web', 'allow_' + op)
1076 allow = self.configlist('web', 'allow_' + op)
1074 return (allow and (allow == ['*'] or user in allow)) or default
1077 return (allow and (allow == ['*'] or user in allow)) or default
1075
1078
1076 def do_unbundle(self, req):
1079 def do_unbundle(self, req):
1077 def bail(response, headers={}):
1080 def bail(response, headers={}):
1078 length = int(req.env['CONTENT_LENGTH'])
1081 length = int(req.env['CONTENT_LENGTH'])
1079 for s in util.filechunkiter(req, limit=length):
1082 for s in util.filechunkiter(req, limit=length):
1080 # drain incoming bundle, else client will not see
1083 # drain incoming bundle, else client will not see
1081 # response when run outside cgi script
1084 # response when run outside cgi script
1082 pass
1085 pass
1083 req.httphdr("application/mercurial-0.1", headers=headers)
1086 req.httphdr("application/mercurial-0.1", headers=headers)
1084 req.write('0\n')
1087 req.write('0\n')
1085 req.write(response)
1088 req.write(response)
1086
1089
1087 # require ssl by default, auth info cannot be sniffed and
1090 # require ssl by default, auth info cannot be sniffed and
1088 # replayed
1091 # replayed
1089 ssl_req = self.configbool('web', 'push_ssl', True)
1092 ssl_req = self.configbool('web', 'push_ssl', True)
1090 if ssl_req:
1093 if ssl_req:
1091 if req.env.get('wsgi.url_scheme') != 'https':
1094 if req.env.get('wsgi.url_scheme') != 'https':
1092 bail(_('ssl required\n'))
1095 bail(_('ssl required\n'))
1093 return
1096 return
1094 proto = 'https'
1097 proto = 'https'
1095 else:
1098 else:
1096 proto = 'http'
1099 proto = 'http'
1097
1100
1098 # do not allow push unless explicitly allowed
1101 # do not allow push unless explicitly allowed
1099 if not self.check_perm(req, 'push', False):
1102 if not self.check_perm(req, 'push', False):
1100 bail(_('push not authorized\n'),
1103 bail(_('push not authorized\n'),
1101 headers={'status': '401 Unauthorized'})
1104 headers={'status': '401 Unauthorized'})
1102 return
1105 return
1103
1106
1104 their_heads = req.form['heads'][0].split(' ')
1107 their_heads = req.form['heads'][0].split(' ')
1105
1108
1106 def check_heads():
1109 def check_heads():
1107 heads = map(hex, self.repo.heads())
1110 heads = map(hex, self.repo.heads())
1108 return their_heads == [hex('force')] or their_heads == heads
1111 return their_heads == [hex('force')] or their_heads == heads
1109
1112
1110 # fail early if possible
1113 # fail early if possible
1111 if not check_heads():
1114 if not check_heads():
1112 bail(_('unsynced changes\n'))
1115 bail(_('unsynced changes\n'))
1113 return
1116 return
1114
1117
1115 req.httphdr("application/mercurial-0.1")
1118 req.httphdr("application/mercurial-0.1")
1116
1119
1117 # do not lock repo until all changegroup data is
1120 # do not lock repo until all changegroup data is
1118 # streamed. save to temporary file.
1121 # streamed. save to temporary file.
1119
1122
1120 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1123 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1121 fp = os.fdopen(fd, 'wb+')
1124 fp = os.fdopen(fd, 'wb+')
1122 try:
1125 try:
1123 length = int(req.env['CONTENT_LENGTH'])
1126 length = int(req.env['CONTENT_LENGTH'])
1124 for s in util.filechunkiter(req, limit=length):
1127 for s in util.filechunkiter(req, limit=length):
1125 fp.write(s)
1128 fp.write(s)
1126
1129
1127 try:
1130 try:
1128 lock = self.repo.lock()
1131 lock = self.repo.lock()
1129 try:
1132 try:
1130 if not check_heads():
1133 if not check_heads():
1131 req.write('0\n')
1134 req.write('0\n')
1132 req.write(_('unsynced changes\n'))
1135 req.write(_('unsynced changes\n'))
1133 return
1136 return
1134
1137
1135 fp.seek(0)
1138 fp.seek(0)
1136 header = fp.read(6)
1139 header = fp.read(6)
1137 if not header.startswith("HG"):
1140 if not header.startswith("HG"):
1138 # old client with uncompressed bundle
1141 # old client with uncompressed bundle
1139 def generator(f):
1142 def generator(f):
1140 yield header
1143 yield header
1141 for chunk in f:
1144 for chunk in f:
1142 yield chunk
1145 yield chunk
1143 elif not header.startswith("HG10"):
1146 elif not header.startswith("HG10"):
1144 req.write("0\n")
1147 req.write("0\n")
1145 req.write(_("unknown bundle version\n"))
1148 req.write(_("unknown bundle version\n"))
1146 return
1149 return
1147 elif header == "HG10GZ":
1150 elif header == "HG10GZ":
1148 def generator(f):
1151 def generator(f):
1149 zd = zlib.decompressobj()
1152 zd = zlib.decompressobj()
1150 for chunk in f:
1153 for chunk in f:
1151 yield zd.decompress(chunk)
1154 yield zd.decompress(chunk)
1152 elif header == "HG10BZ":
1155 elif header == "HG10BZ":
1153 def generator(f):
1156 def generator(f):
1154 zd = bz2.BZ2Decompressor()
1157 zd = bz2.BZ2Decompressor()
1155 zd.decompress("BZ")
1158 zd.decompress("BZ")
1156 for chunk in f:
1159 for chunk in f:
1157 yield zd.decompress(chunk)
1160 yield zd.decompress(chunk)
1158 elif header == "HG10UN":
1161 elif header == "HG10UN":
1159 def generator(f):
1162 def generator(f):
1160 for chunk in f:
1163 for chunk in f:
1161 yield chunk
1164 yield chunk
1162 else:
1165 else:
1163 req.write("0\n")
1166 req.write("0\n")
1164 req.write(_("unknown bundle compression type\n"))
1167 req.write(_("unknown bundle compression type\n"))
1165 return
1168 return
1166 gen = generator(util.filechunkiter(fp, 4096))
1169 gen = generator(util.filechunkiter(fp, 4096))
1167
1170
1168 # send addchangegroup output to client
1171 # send addchangegroup output to client
1169
1172
1170 old_stdout = sys.stdout
1173 old_stdout = sys.stdout
1171 sys.stdout = cStringIO.StringIO()
1174 sys.stdout = cStringIO.StringIO()
1172
1175
1173 try:
1176 try:
1174 url = 'remote:%s:%s' % (proto,
1177 url = 'remote:%s:%s' % (proto,
1175 req.env.get('REMOTE_HOST', ''))
1178 req.env.get('REMOTE_HOST', ''))
1176 try:
1179 try:
1177 ret = self.repo.addchangegroup(
1180 ret = self.repo.addchangegroup(
1178 util.chunkbuffer(gen), 'serve', url)
1181 util.chunkbuffer(gen), 'serve', url)
1179 except util.Abort, inst:
1182 except util.Abort, inst:
1180 sys.stdout.write("abort: %s\n" % inst)
1183 sys.stdout.write("abort: %s\n" % inst)
1181 ret = 0
1184 ret = 0
1182 finally:
1185 finally:
1183 val = sys.stdout.getvalue()
1186 val = sys.stdout.getvalue()
1184 sys.stdout = old_stdout
1187 sys.stdout = old_stdout
1185 req.write('%d\n' % ret)
1188 req.write('%d\n' % ret)
1186 req.write(val)
1189 req.write(val)
1187 finally:
1190 finally:
1188 del lock
1191 del lock
1189 except (OSError, IOError), inst:
1192 except (OSError, IOError), inst:
1190 req.write('0\n')
1193 req.write('0\n')
1191 filename = getattr(inst, 'filename', '')
1194 filename = getattr(inst, 'filename', '')
1192 # Don't send our filesystem layout to the client
1195 # Don't send our filesystem layout to the client
1193 if filename.startswith(self.repo.root):
1196 if filename.startswith(self.repo.root):
1194 filename = filename[len(self.repo.root)+1:]
1197 filename = filename[len(self.repo.root)+1:]
1195 else:
1198 else:
1196 filename = ''
1199 filename = ''
1197 error = getattr(inst, 'strerror', 'Unknown error')
1200 error = getattr(inst, 'strerror', 'Unknown error')
1198 req.write('%s: %s\n' % (error, filename))
1201 req.write('%s: %s\n' % (error, filename))
1199 finally:
1202 finally:
1200 fp.close()
1203 fp.close()
1201 os.unlink(tempname)
1204 os.unlink(tempname)
1202
1205
1203 def do_stream_out(self, req):
1206 def do_stream_out(self, req):
1204 req.httphdr("application/mercurial-0.1")
1207 req.httphdr("application/mercurial-0.1")
1205 streamclone.stream_out(self.repo, req, untrusted=True)
1208 streamclone.stream_out(self.repo, req, untrusted=True)
@@ -1,259 +1,260 b''
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
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 os, mimetools, cStringIO
9 import os, mimetools, cStringIO
10 from mercurial.i18n import gettext as _
10 from mercurial.i18n import gettext as _
11 from mercurial import ui, hg, util, templater
11 from mercurial import ui, hg, util, templater
12 from common import get_mtime, staticfile, style_map, paritygen
12 from common import get_mtime, staticfile, style_map, paritygen
13 from hgweb_mod import hgweb
13 from hgweb_mod import hgweb
14
14
15 # This is a stopgap
15 # This is a stopgap
16 class hgwebdir(object):
16 class hgwebdir(object):
17 def __init__(self, config, parentui=None):
17 def __init__(self, config, parentui=None):
18 def cleannames(items):
18 def cleannames(items):
19 return [(util.pconvert(name.strip(os.sep)), path)
19 return [(util.pconvert(name.strip(os.sep)), path)
20 for name, path in items]
20 for name, path in items]
21
21
22 self.parentui = parentui
22 self.parentui = parentui
23 self.motd = None
23 self.motd = None
24 self.style = None
24 self.style = None
25 self.stripecount = None
25 self.stripecount = None
26 self.repos_sorted = ('name', False)
26 self.repos_sorted = ('name', False)
27 if isinstance(config, (list, tuple)):
27 if isinstance(config, (list, tuple)):
28 self.repos = cleannames(config)
28 self.repos = cleannames(config)
29 self.repos_sorted = ('', False)
29 self.repos_sorted = ('', False)
30 elif isinstance(config, dict):
30 elif isinstance(config, dict):
31 self.repos = cleannames(config.items())
31 self.repos = cleannames(config.items())
32 self.repos.sort()
32 self.repos.sort()
33 else:
33 else:
34 if isinstance(config, util.configparser):
34 if isinstance(config, util.configparser):
35 cp = config
35 cp = config
36 else:
36 else:
37 cp = util.configparser()
37 cp = util.configparser()
38 cp.read(config)
38 cp.read(config)
39 self.repos = []
39 self.repos = []
40 if cp.has_section('web'):
40 if cp.has_section('web'):
41 if cp.has_option('web', 'motd'):
41 if cp.has_option('web', 'motd'):
42 self.motd = cp.get('web', 'motd')
42 self.motd = cp.get('web', 'motd')
43 if cp.has_option('web', 'style'):
43 if cp.has_option('web', 'style'):
44 self.style = cp.get('web', 'style')
44 self.style = cp.get('web', 'style')
45 if cp.has_option('web', 'stripes'):
45 if cp.has_option('web', 'stripes'):
46 self.stripecount = int(cp.get('web', 'stripes'))
46 self.stripecount = int(cp.get('web', 'stripes'))
47 if cp.has_section('paths'):
47 if cp.has_section('paths'):
48 self.repos.extend(cleannames(cp.items('paths')))
48 self.repos.extend(cleannames(cp.items('paths')))
49 if cp.has_section('collections'):
49 if cp.has_section('collections'):
50 for prefix, root in cp.items('collections'):
50 for prefix, root in cp.items('collections'):
51 for path in util.walkrepos(root):
51 for path in util.walkrepos(root):
52 repo = os.path.normpath(path)
52 repo = os.path.normpath(path)
53 name = repo
53 name = repo
54 if name.startswith(prefix):
54 if name.startswith(prefix):
55 name = name[len(prefix):]
55 name = name[len(prefix):]
56 self.repos.append((name.lstrip(os.sep), repo))
56 self.repos.append((name.lstrip(os.sep), repo))
57 self.repos.sort()
57 self.repos.sort()
58
58
59 def run(self):
59 def run(self):
60 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
60 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
61 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
61 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
62 import mercurial.hgweb.wsgicgi as wsgicgi
62 import mercurial.hgweb.wsgicgi as wsgicgi
63 from request import wsgiapplication
63 from request import wsgiapplication
64 def make_web_app():
64 def make_web_app():
65 return self
65 return self
66 wsgicgi.launch(wsgiapplication(make_web_app))
66 wsgicgi.launch(wsgiapplication(make_web_app))
67
67
68 def run_wsgi(self, req):
68 def run_wsgi(self, req):
69 def header(**map):
69 def header(**map):
70 header_file = cStringIO.StringIO(
70 header_file = cStringIO.StringIO(
71 ''.join(tmpl("header", encoding=util._encoding, **map)))
71 ''.join(tmpl("header", encoding=util._encoding, **map)))
72 msg = mimetools.Message(header_file, 0)
72 msg = mimetools.Message(header_file, 0)
73 req.header(msg.items())
73 req.header(msg.items())
74 yield header_file.read()
74 yield header_file.read()
75
75
76 def footer(**map):
76 def footer(**map):
77 yield tmpl("footer", **map)
77 yield tmpl("footer", **map)
78
78
79 def motd(**map):
79 def motd(**map):
80 if self.motd is not None:
80 if self.motd is not None:
81 yield self.motd
81 yield self.motd
82 else:
82 else:
83 yield config('web', 'motd', '')
83 yield config('web', 'motd', '')
84
84
85 parentui = self.parentui or ui.ui(report_untrusted=False,
85 parentui = self.parentui or ui.ui(report_untrusted=False,
86 interactive=False)
86 interactive=False)
87
87
88 def config(section, name, default=None, untrusted=True):
88 def config(section, name, default=None, untrusted=True):
89 return parentui.config(section, name, default, untrusted)
89 return parentui.config(section, name, default, untrusted)
90
90
91 url = req.env['REQUEST_URI'].split('?')[0]
91 url = req.env['REQUEST_URI'].split('?')[0]
92 if not url.endswith('/'):
92 if not url.endswith('/'):
93 url += '/'
93 url += '/'
94 pathinfo = req.env.get('PATH_INFO', '').strip('/') + '/'
94 pathinfo = req.env.get('PATH_INFO', '').strip('/') + '/'
95 base = url[:len(url) - len(pathinfo)]
95 base = url[:len(url) - len(pathinfo)]
96 if not base.endswith('/'):
96 if not base.endswith('/'):
97 base += '/'
97 base += '/'
98
98
99 staticurl = config('web', 'staticurl') or base + 'static/'
99 staticurl = config('web', 'staticurl') or base + 'static/'
100 if not staticurl.endswith('/'):
100 if not staticurl.endswith('/'):
101 staticurl += '/'
101 staticurl += '/'
102
102
103 style = self.style
103 style = self.style
104 if style is None:
104 if style is None:
105 style = config('web', 'style', '')
105 style = config('web', 'style', '')
106 if req.form.has_key('style'):
106 if req.form.has_key('style'):
107 style = req.form['style'][0]
107 style = req.form['style'][0]
108 if self.stripecount is None:
108 if self.stripecount is None:
109 self.stripecount = int(config('web', 'stripes', 1))
109 self.stripecount = int(config('web', 'stripes', 1))
110 mapfile = style_map(templater.templatepath(), style)
110 mapfile = style_map(templater.templatepath(), style)
111 tmpl = templater.templater(mapfile, templater.common_filters,
111 tmpl = templater.templater(mapfile, templater.common_filters,
112 defaults={"header": header,
112 defaults={"header": header,
113 "footer": footer,
113 "footer": footer,
114 "motd": motd,
114 "motd": motd,
115 "url": url,
115 "url": url,
116 "staticurl": staticurl})
116 "staticurl": staticurl})
117
117
118 def archivelist(ui, nodeid, url):
118 def archivelist(ui, nodeid, url):
119 allowed = ui.configlist("web", "allow_archive", untrusted=True)
119 allowed = ui.configlist("web", "allow_archive", untrusted=True)
120 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
120 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
121 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
121 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
122 untrusted=True):
122 untrusted=True):
123 yield {"type" : i[0], "extension": i[1],
123 yield {"type" : i[0], "extension": i[1],
124 "node": nodeid, "url": url}
124 "node": nodeid, "url": url}
125
125
126 def entries(sortcolumn="", descending=False, subdir="", **map):
126 def entries(sortcolumn="", descending=False, subdir="", **map):
127 def sessionvars(**map):
127 def sessionvars(**map):
128 fields = []
128 fields = []
129 if req.form.has_key('style'):
129 if req.form.has_key('style'):
130 style = req.form['style'][0]
130 style = req.form['style'][0]
131 if style != get('web', 'style', ''):
131 if style != get('web', 'style', ''):
132 fields.append(('style', style))
132 fields.append(('style', style))
133
133
134 separator = url[-1] == '?' and ';' or '?'
134 separator = url[-1] == '?' and ';' or '?'
135 for name, value in fields:
135 for name, value in fields:
136 yield dict(name=name, value=value, separator=separator)
136 yield dict(name=name, value=value, separator=separator)
137 separator = ';'
137 separator = ';'
138
138
139 rows = []
139 rows = []
140 parity = paritygen(self.stripecount)
140 parity = paritygen(self.stripecount)
141 for name, path in self.repos:
141 for name, path in self.repos:
142 if not name.startswith(subdir):
142 if not name.startswith(subdir):
143 continue
143 continue
144 name = name[len(subdir):]
144 name = name[len(subdir):]
145
145
146 u = ui.ui(parentui=parentui)
146 u = ui.ui(parentui=parentui)
147 try:
147 try:
148 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
148 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
149 except IOError:
149 except Exception, e:
150 pass
150 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
151 continue
151 def get(section, name, default=None):
152 def get(section, name, default=None):
152 return u.config(section, name, default, untrusted=True)
153 return u.config(section, name, default, untrusted=True)
153
154
154 if u.configbool("web", "hidden", untrusted=True):
155 if u.configbool("web", "hidden", untrusted=True):
155 continue
156 continue
156
157
157 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
158 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
158 .replace("//", "/")) + '/'
159 .replace("//", "/")) + '/'
159
160
160 # update time with local timezone
161 # update time with local timezone
161 try:
162 try:
162 d = (get_mtime(path), util.makedate()[1])
163 d = (get_mtime(path), util.makedate()[1])
163 except OSError:
164 except OSError:
164 continue
165 continue
165
166
166 contact = (get("ui", "username") or # preferred
167 contact = (get("ui", "username") or # preferred
167 get("web", "contact") or # deprecated
168 get("web", "contact") or # deprecated
168 get("web", "author", "")) # also
169 get("web", "author", "")) # also
169 description = get("web", "description", "")
170 description = get("web", "description", "")
170 name = get("web", "name", name)
171 name = get("web", "name", name)
171 row = dict(contact=contact or "unknown",
172 row = dict(contact=contact or "unknown",
172 contact_sort=contact.upper() or "unknown",
173 contact_sort=contact.upper() or "unknown",
173 name=name,
174 name=name,
174 name_sort=name,
175 name_sort=name,
175 url=url,
176 url=url,
176 description=description or "unknown",
177 description=description or "unknown",
177 description_sort=description.upper() or "unknown",
178 description_sort=description.upper() or "unknown",
178 lastchange=d,
179 lastchange=d,
179 lastchange_sort=d[1]-d[0],
180 lastchange_sort=d[1]-d[0],
180 sessionvars=sessionvars,
181 sessionvars=sessionvars,
181 archives=archivelist(u, "tip", url))
182 archives=archivelist(u, "tip", url))
182 if (not sortcolumn
183 if (not sortcolumn
183 or (sortcolumn, descending) == self.repos_sorted):
184 or (sortcolumn, descending) == self.repos_sorted):
184 # fast path for unsorted output
185 # fast path for unsorted output
185 row['parity'] = parity.next()
186 row['parity'] = parity.next()
186 yield row
187 yield row
187 else:
188 else:
188 rows.append((row["%s_sort" % sortcolumn], row))
189 rows.append((row["%s_sort" % sortcolumn], row))
189 if rows:
190 if rows:
190 rows.sort()
191 rows.sort()
191 if descending:
192 if descending:
192 rows.reverse()
193 rows.reverse()
193 for key, row in rows:
194 for key, row in rows:
194 row['parity'] = parity.next()
195 row['parity'] = parity.next()
195 yield row
196 yield row
196
197
197 def makeindex(req, subdir=""):
198 def makeindex(req, subdir=""):
198 sortable = ["name", "description", "contact", "lastchange"]
199 sortable = ["name", "description", "contact", "lastchange"]
199 sortcolumn, descending = self.repos_sorted
200 sortcolumn, descending = self.repos_sorted
200 if req.form.has_key('sort'):
201 if req.form.has_key('sort'):
201 sortcolumn = req.form['sort'][0]
202 sortcolumn = req.form['sort'][0]
202 descending = sortcolumn.startswith('-')
203 descending = sortcolumn.startswith('-')
203 if descending:
204 if descending:
204 sortcolumn = sortcolumn[1:]
205 sortcolumn = sortcolumn[1:]
205 if sortcolumn not in sortable:
206 if sortcolumn not in sortable:
206 sortcolumn = ""
207 sortcolumn = ""
207
208
208 sort = [("sort_%s" % column,
209 sort = [("sort_%s" % column,
209 "%s%s" % ((not descending and column == sortcolumn)
210 "%s%s" % ((not descending and column == sortcolumn)
210 and "-" or "", column))
211 and "-" or "", column))
211 for column in sortable]
212 for column in sortable]
212 req.write(tmpl("index", entries=entries, subdir=subdir,
213 req.write(tmpl("index", entries=entries, subdir=subdir,
213 sortcolumn=sortcolumn, descending=descending,
214 sortcolumn=sortcolumn, descending=descending,
214 **dict(sort)))
215 **dict(sort)))
215
216
216 try:
217 try:
217 virtual = req.env.get("PATH_INFO", "").strip('/')
218 virtual = req.env.get("PATH_INFO", "").strip('/')
218 if virtual.startswith('static/'):
219 if virtual.startswith('static/'):
219 static = os.path.join(templater.templatepath(), 'static')
220 static = os.path.join(templater.templatepath(), 'static')
220 fname = virtual[7:]
221 fname = virtual[7:]
221 req.write(staticfile(static, fname, req) or
222 req.write(staticfile(static, fname, req) or
222 tmpl('error', error='%r not found' % fname))
223 tmpl('error', error='%r not found' % fname))
223 elif virtual:
224 elif virtual:
224 repos = dict(self.repos)
225 repos = dict(self.repos)
225 while virtual:
226 while virtual:
226 real = repos.get(virtual)
227 real = repos.get(virtual)
227 if real:
228 if real:
228 req.env['REPO_NAME'] = virtual
229 req.env['REPO_NAME'] = virtual
229 try:
230 try:
230 repo = hg.repository(parentui, real)
231 repo = hg.repository(parentui, real)
231 hgweb(repo).run_wsgi(req)
232 hgweb(repo).run_wsgi(req)
232 except IOError, inst:
233 except IOError, inst:
233 req.write(tmpl("error", error=inst.strerror))
234 req.write(tmpl("error", error=inst.strerror))
234 except hg.RepoError, inst:
235 except hg.RepoError, inst:
235 req.write(tmpl("error", error=str(inst)))
236 req.write(tmpl("error", error=str(inst)))
236 return
237 return
237
238
238 # browse subdirectories
239 # browse subdirectories
239 subdir = virtual + '/'
240 subdir = virtual + '/'
240 if [r for r in repos if r.startswith(subdir)]:
241 if [r for r in repos if r.startswith(subdir)]:
241 makeindex(req, subdir)
242 makeindex(req, subdir)
242 return
243 return
243
244
244 up = virtual.rfind('/')
245 up = virtual.rfind('/')
245 if up < 0:
246 if up < 0:
246 break
247 break
247 virtual = virtual[:up]
248 virtual = virtual[:up]
248
249
249 req.write(tmpl("notfound", repo=virtual))
250 req.write(tmpl("notfound", repo=virtual))
250 else:
251 else:
251 if req.form.has_key('static'):
252 if req.form.has_key('static'):
252 static = os.path.join(templater.templatepath(), "static")
253 static = os.path.join(templater.templatepath(), "static")
253 fname = req.form['static'][0]
254 fname = req.form['static'][0]
254 req.write(staticfile(static, fname, req)
255 req.write(staticfile(static, fname, req)
255 or tmpl("error", error="%r not found" % fname))
256 or tmpl("error", error="%r not found" % fname))
256 else:
257 else:
257 makeindex(req)
258 makeindex(req)
258 finally:
259 finally:
259 tmpl = None
260 tmpl = None
@@ -1,456 +1,455 b''
1 # httprepo.py - HTTP repository proxy classes for mercurial
1 # httprepo.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 from node import *
9 from node import *
10 from remoterepo import *
10 from remoterepo import *
11 from i18n import _
11 from i18n import _
12 import repo, os, urllib, urllib2, urlparse, zlib, util, httplib
12 import repo, os, urllib, urllib2, urlparse, zlib, util, httplib
13 import errno, keepalive, tempfile, socket, changegroup
13 import errno, keepalive, tempfile, socket, changegroup
14
14
15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
16 def __init__(self, ui):
16 def __init__(self, ui):
17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
18 self.ui = ui
18 self.ui = ui
19
19
20 def find_user_password(self, realm, authuri):
20 def find_user_password(self, realm, authuri):
21 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
21 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
22 self, realm, authuri)
22 self, realm, authuri)
23 user, passwd = authinfo
23 user, passwd = authinfo
24 if user and passwd:
24 if user and passwd:
25 return (user, passwd)
25 return (user, passwd)
26
26
27 if not self.ui.interactive:
27 if not self.ui.interactive:
28 raise util.Abort(_('http authorization required'))
28 raise util.Abort(_('http authorization required'))
29
29
30 self.ui.write(_("http authorization required\n"))
30 self.ui.write(_("http authorization required\n"))
31 self.ui.status(_("realm: %s\n") % realm)
31 self.ui.status(_("realm: %s\n") % realm)
32 if user:
32 if user:
33 self.ui.status(_("user: %s\n") % user)
33 self.ui.status(_("user: %s\n") % user)
34 else:
34 else:
35 user = self.ui.prompt(_("user:"), default=None)
35 user = self.ui.prompt(_("user:"), default=None)
36
36
37 if not passwd:
37 if not passwd:
38 passwd = self.ui.getpass()
38 passwd = self.ui.getpass()
39
39
40 self.add_password(realm, authuri, user, passwd)
40 self.add_password(realm, authuri, user, passwd)
41 return (user, passwd)
41 return (user, passwd)
42
42
43 def netlocsplit(netloc):
43 def netlocsplit(netloc):
44 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
44 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
45
45
46 a = netloc.find('@')
46 a = netloc.find('@')
47 if a == -1:
47 if a == -1:
48 user, passwd = None, None
48 user, passwd = None, None
49 else:
49 else:
50 userpass, netloc = netloc[:a], netloc[a+1:]
50 userpass, netloc = netloc[:a], netloc[a+1:]
51 c = userpass.find(':')
51 c = userpass.find(':')
52 if c == -1:
52 if c == -1:
53 user, passwd = urllib.unquote(userpass), None
53 user, passwd = urllib.unquote(userpass), None
54 else:
54 else:
55 user = urllib.unquote(userpass[:c])
55 user = urllib.unquote(userpass[:c])
56 passwd = urllib.unquote(userpass[c+1:])
56 passwd = urllib.unquote(userpass[c+1:])
57 c = netloc.find(':')
57 c = netloc.find(':')
58 if c == -1:
58 if c == -1:
59 host, port = netloc, None
59 host, port = netloc, None
60 else:
60 else:
61 host, port = netloc[:c], netloc[c+1:]
61 host, port = netloc[:c], netloc[c+1:]
62 return host, port, user, passwd
62 return host, port, user, passwd
63
63
64 def netlocunsplit(host, port, user=None, passwd=None):
64 def netlocunsplit(host, port, user=None, passwd=None):
65 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
65 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
66 if port:
66 if port:
67 hostport = host + ':' + port
67 hostport = host + ':' + port
68 else:
68 else:
69 hostport = host
69 hostport = host
70 if user:
70 if user:
71 if passwd:
71 if passwd:
72 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
72 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
73 else:
73 else:
74 userpass = urllib.quote(user)
74 userpass = urllib.quote(user)
75 return userpass + '@' + hostport
75 return userpass + '@' + hostport
76 return hostport
76 return hostport
77
77
78 # work around a bug in Python < 2.4.2
78 # work around a bug in Python < 2.4.2
79 # (it leaves a "\n" at the end of Proxy-authorization headers)
79 # (it leaves a "\n" at the end of Proxy-authorization headers)
80 class request(urllib2.Request):
80 class request(urllib2.Request):
81 def add_header(self, key, val):
81 def add_header(self, key, val):
82 if key.lower() == 'proxy-authorization':
82 if key.lower() == 'proxy-authorization':
83 val = val.strip()
83 val = val.strip()
84 return urllib2.Request.add_header(self, key, val)
84 return urllib2.Request.add_header(self, key, val)
85
85
86 class httpsendfile(file):
86 class httpsendfile(file):
87 def __len__(self):
87 def __len__(self):
88 return os.fstat(self.fileno()).st_size
88 return os.fstat(self.fileno()).st_size
89
89
90 def _gen_sendfile(connection):
90 def _gen_sendfile(connection):
91 def _sendfile(self, data):
91 def _sendfile(self, data):
92 # send a file
92 # send a file
93 if isinstance(data, httpsendfile):
93 if isinstance(data, httpsendfile):
94 # if auth required, some data sent twice, so rewind here
94 # if auth required, some data sent twice, so rewind here
95 data.seek(0)
95 data.seek(0)
96 for chunk in util.filechunkiter(data):
96 for chunk in util.filechunkiter(data):
97 connection.send(self, chunk)
97 connection.send(self, chunk)
98 else:
98 else:
99 connection.send(self, data)
99 connection.send(self, data)
100 return _sendfile
100 return _sendfile
101
101
102 class httpconnection(keepalive.HTTPConnection):
102 class httpconnection(keepalive.HTTPConnection):
103 # must be able to send big bundle as stream.
103 # must be able to send big bundle as stream.
104 send = _gen_sendfile(keepalive.HTTPConnection)
104 send = _gen_sendfile(keepalive.HTTPConnection)
105
105
106 class basehttphandler(keepalive.HTTPHandler):
106 class basehttphandler(keepalive.HTTPHandler):
107 def http_open(self, req):
107 def http_open(self, req):
108 return self.do_open(httpconnection, req)
108 return self.do_open(httpconnection, req)
109
109
110 has_https = hasattr(urllib2, 'HTTPSHandler')
110 has_https = hasattr(urllib2, 'HTTPSHandler')
111 if has_https:
111 if has_https:
112 class httpsconnection(httplib.HTTPSConnection):
112 class httpsconnection(httplib.HTTPSConnection):
113 response_class = keepalive.HTTPResponse
113 response_class = keepalive.HTTPResponse
114 # must be able to send big bundle as stream.
114 # must be able to send big bundle as stream.
115 send = _gen_sendfile(httplib.HTTPSConnection)
115 send = _gen_sendfile(httplib.HTTPSConnection)
116
116
117 class httphandler(basehttphandler, urllib2.HTTPSHandler):
117 class httphandler(basehttphandler, urllib2.HTTPSHandler):
118 def https_open(self, req):
118 def https_open(self, req):
119 return self.do_open(httpsconnection, req)
119 return self.do_open(httpsconnection, req)
120 else:
120 else:
121 class httphandler(basehttphandler):
121 class httphandler(basehttphandler):
122 pass
122 pass
123
123
124 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
124 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
125 # it doesn't know about the auth type requested. This can happen if
125 # it doesn't know about the auth type requested. This can happen if
126 # somebody is using BasicAuth and types a bad password.
126 # somebody is using BasicAuth and types a bad password.
127 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
127 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
128 def http_error_auth_reqed(self, auth_header, host, req, headers):
128 def http_error_auth_reqed(self, auth_header, host, req, headers):
129 try:
129 try:
130 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
130 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
131 self, auth_header, host, req, headers)
131 self, auth_header, host, req, headers)
132 except ValueError, inst:
132 except ValueError, inst:
133 arg = inst.args[0]
133 arg = inst.args[0]
134 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
134 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
135 return
135 return
136 raise
136 raise
137
137
138 def zgenerator(f):
138 def zgenerator(f):
139 zd = zlib.decompressobj()
139 zd = zlib.decompressobj()
140 try:
140 try:
141 for chunk in util.filechunkiter(f):
141 for chunk in util.filechunkiter(f):
142 yield zd.decompress(chunk)
142 yield zd.decompress(chunk)
143 except httplib.HTTPException, inst:
143 except httplib.HTTPException, inst:
144 raise IOError(None, _('connection ended unexpectedly'))
144 raise IOError(None, _('connection ended unexpectedly'))
145 yield zd.flush()
145 yield zd.flush()
146
146
147 _safe = ('abcdefghijklmnopqrstuvwxyz'
147 _safe = ('abcdefghijklmnopqrstuvwxyz'
148 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
148 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
149 '0123456789' '_.-/')
149 '0123456789' '_.-/')
150 _safeset = None
150 _safeset = None
151 _hex = None
151 _hex = None
152 def quotepath(path):
152 def quotepath(path):
153 '''quote the path part of a URL
153 '''quote the path part of a URL
154
154
155 This is similar to urllib.quote, but it also tries to avoid
155 This is similar to urllib.quote, but it also tries to avoid
156 quoting things twice (inspired by wget):
156 quoting things twice (inspired by wget):
157
157
158 >>> quotepath('abc def')
158 >>> quotepath('abc def')
159 'abc%20def'
159 'abc%20def'
160 >>> quotepath('abc%20def')
160 >>> quotepath('abc%20def')
161 'abc%20def'
161 'abc%20def'
162 >>> quotepath('abc%20 def')
162 >>> quotepath('abc%20 def')
163 'abc%20%20def'
163 'abc%20%20def'
164 >>> quotepath('abc def%20')
164 >>> quotepath('abc def%20')
165 'abc%20def%20'
165 'abc%20def%20'
166 >>> quotepath('abc def%2')
166 >>> quotepath('abc def%2')
167 'abc%20def%252'
167 'abc%20def%252'
168 >>> quotepath('abc def%')
168 >>> quotepath('abc def%')
169 'abc%20def%25'
169 'abc%20def%25'
170 '''
170 '''
171 global _safeset, _hex
171 global _safeset, _hex
172 if _safeset is None:
172 if _safeset is None:
173 _safeset = util.set(_safe)
173 _safeset = util.set(_safe)
174 _hex = util.set('abcdefABCDEF0123456789')
174 _hex = util.set('abcdefABCDEF0123456789')
175 l = list(path)
175 l = list(path)
176 for i in xrange(len(l)):
176 for i in xrange(len(l)):
177 c = l[i]
177 c = l[i]
178 if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex):
178 if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex):
179 pass
179 pass
180 elif c not in _safeset:
180 elif c not in _safeset:
181 l[i] = '%%%02X' % ord(c)
181 l[i] = '%%%02X' % ord(c)
182 return ''.join(l)
182 return ''.join(l)
183
183
184 class httprepository(remoterepository):
184 class httprepository(remoterepository):
185 def __init__(self, ui, path):
185 def __init__(self, ui, path):
186 self.path = path
186 self.path = path
187 self.caps = None
187 self.caps = None
188 self.handler = None
188 self.handler = None
189 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
189 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
190 if query or frag:
190 if query or frag:
191 raise util.Abort(_('unsupported URL component: "%s"') %
191 raise util.Abort(_('unsupported URL component: "%s"') %
192 (query or frag))
192 (query or frag))
193 if not urlpath:
193 if not urlpath:
194 urlpath = '/'
194 urlpath = '/'
195 urlpath = quotepath(urlpath)
195 urlpath = quotepath(urlpath)
196 host, port, user, passwd = netlocsplit(netloc)
196 host, port, user, passwd = netlocsplit(netloc)
197
197
198 # urllib cannot handle URLs with embedded user or passwd
198 # urllib cannot handle URLs with embedded user or passwd
199 self._url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
199 self._url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
200 urlpath, '', ''))
200 urlpath, '', ''))
201 self.ui = ui
201 self.ui = ui
202 self.ui.debug(_('using %s\n') % self._url)
202 self.ui.debug(_('using %s\n') % self._url)
203
203
204 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
204 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
205 # XXX proxyauthinfo = None
205 # XXX proxyauthinfo = None
206 self.handler = httphandler()
206 self.handler = httphandler()
207 handlers = [self.handler]
207 handlers = [self.handler]
208
208
209 if proxyurl:
209 if proxyurl:
210 # proxy can be proper url or host[:port]
210 # proxy can be proper url or host[:port]
211 if not (proxyurl.startswith('http:') or
211 if not (proxyurl.startswith('http:') or
212 proxyurl.startswith('https:')):
212 proxyurl.startswith('https:')):
213 proxyurl = 'http://' + proxyurl + '/'
213 proxyurl = 'http://' + proxyurl + '/'
214 snpqf = urlparse.urlsplit(proxyurl)
214 snpqf = urlparse.urlsplit(proxyurl)
215 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
215 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
216 hpup = netlocsplit(proxynetloc)
216 hpup = netlocsplit(proxynetloc)
217
217
218 proxyhost, proxyport, proxyuser, proxypasswd = hpup
218 proxyhost, proxyport, proxyuser, proxypasswd = hpup
219 if not proxyuser:
219 if not proxyuser:
220 proxyuser = ui.config("http_proxy", "user")
220 proxyuser = ui.config("http_proxy", "user")
221 proxypasswd = ui.config("http_proxy", "passwd")
221 proxypasswd = ui.config("http_proxy", "passwd")
222
222
223 # see if we should use a proxy for this url
223 # see if we should use a proxy for this url
224 no_list = [ "localhost", "127.0.0.1" ]
224 no_list = [ "localhost", "127.0.0.1" ]
225 no_list.extend([p.lower() for
225 no_list.extend([p.lower() for
226 p in ui.configlist("http_proxy", "no")])
226 p in ui.configlist("http_proxy", "no")])
227 no_list.extend([p.strip().lower() for
227 no_list.extend([p.strip().lower() for
228 p in os.getenv("no_proxy", '').split(',')
228 p in os.getenv("no_proxy", '').split(',')
229 if p.strip()])
229 if p.strip()])
230 # "http_proxy.always" config is for running tests on localhost
230 # "http_proxy.always" config is for running tests on localhost
231 if (not ui.configbool("http_proxy", "always") and
231 if (not ui.configbool("http_proxy", "always") and
232 host.lower() in no_list):
232 host.lower() in no_list):
233 ui.debug(_('disabling proxy for %s\n') % host)
233 ui.debug(_('disabling proxy for %s\n') % host)
234 else:
234 else:
235 proxyurl = urlparse.urlunsplit((
235 proxyurl = urlparse.urlunsplit((
236 proxyscheme, netlocunsplit(proxyhost, proxyport,
236 proxyscheme, netlocunsplit(proxyhost, proxyport,
237 proxyuser, proxypasswd or ''),
237 proxyuser, proxypasswd or ''),
238 proxypath, proxyquery, proxyfrag))
238 proxypath, proxyquery, proxyfrag))
239 handlers.append(urllib2.ProxyHandler({scheme: proxyurl}))
239 handlers.append(urllib2.ProxyHandler({scheme: proxyurl}))
240 ui.debug(_('proxying through http://%s:%s\n') %
240 ui.debug(_('proxying through http://%s:%s\n') %
241 (proxyhost, proxyport))
241 (proxyhost, proxyport))
242
242
243 # urllib2 takes proxy values from the environment and those
243 # urllib2 takes proxy values from the environment and those
244 # will take precedence if found, so drop them
244 # will take precedence if found, so drop them
245 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
245 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
246 try:
246 try:
247 if os.environ.has_key(env):
247 if os.environ.has_key(env):
248 del os.environ[env]
248 del os.environ[env]
249 except OSError:
249 except OSError:
250 pass
250 pass
251
251
252 passmgr = passwordmgr(ui)
252 passmgr = passwordmgr(ui)
253 if user:
253 if user:
254 ui.debug(_('http auth: user %s, password %s\n') %
254 ui.debug(_('http auth: user %s, password %s\n') %
255 (user, passwd and '*' * len(passwd) or 'not set'))
255 (user, passwd and '*' * len(passwd) or 'not set'))
256 passmgr.add_password(None, self._url, user, passwd or '')
256 passmgr.add_password(None, self._url, user, passwd or '')
257
257
258 handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
258 handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
259 httpdigestauthhandler(passmgr)))
259 httpdigestauthhandler(passmgr)))
260 opener = urllib2.build_opener(*handlers)
260 opener = urllib2.build_opener(*handlers)
261
261
262 # 1.0 here is the _protocol_ version
262 # 1.0 here is the _protocol_ version
263 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
263 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
264 urllib2.install_opener(opener)
264 urllib2.install_opener(opener)
265
265
266 def __del__(self):
266 def __del__(self):
267 if self.handler:
267 if self.handler:
268 self.handler.close_all()
268 self.handler.close_all()
269 self.handler = None
269 self.handler = None
270
270
271 def url(self):
271 def url(self):
272 return self.path
272 return self.path
273
273
274 # look up capabilities only when needed
274 # look up capabilities only when needed
275
275
276 def get_caps(self):
276 def get_caps(self):
277 if self.caps is None:
277 if self.caps is None:
278 try:
278 try:
279 self.caps = util.set(self.do_read('capabilities').split())
279 self.caps = util.set(self.do_read('capabilities').split())
280 except repo.RepoError:
280 except repo.RepoError:
281 self.caps = util.set()
281 self.caps = util.set()
282 self.ui.debug(_('capabilities: %s\n') %
282 self.ui.debug(_('capabilities: %s\n') %
283 (' '.join(self.caps or ['none'])))
283 (' '.join(self.caps or ['none'])))
284 return self.caps
284 return self.caps
285
285
286 capabilities = property(get_caps)
286 capabilities = property(get_caps)
287
287
288 def lock(self):
288 def lock(self):
289 raise util.Abort(_('operation not supported over http'))
289 raise util.Abort(_('operation not supported over http'))
290
290
291 def do_cmd(self, cmd, **args):
291 def do_cmd(self, cmd, **args):
292 data = args.pop('data', None)
292 data = args.pop('data', None)
293 headers = args.pop('headers', {})
293 headers = args.pop('headers', {})
294 self.ui.debug(_("sending %s command\n") % cmd)
294 self.ui.debug(_("sending %s command\n") % cmd)
295 q = {"cmd": cmd}
295 q = {"cmd": cmd}
296 q.update(args)
296 q.update(args)
297 qs = '?%s' % urllib.urlencode(q)
297 qs = '?%s' % urllib.urlencode(q)
298 cu = "%s%s" % (self._url, qs)
298 cu = "%s%s" % (self._url, qs)
299 try:
299 try:
300 if data:
300 if data:
301 self.ui.debug(_("sending %s bytes\n") %
301 self.ui.debug(_("sending %s bytes\n") % len(data))
302 headers.get('content-length', 'X'))
303 resp = urllib2.urlopen(request(cu, data, headers))
302 resp = urllib2.urlopen(request(cu, data, headers))
304 except urllib2.HTTPError, inst:
303 except urllib2.HTTPError, inst:
305 if inst.code == 401:
304 if inst.code == 401:
306 raise util.Abort(_('authorization failed'))
305 raise util.Abort(_('authorization failed'))
307 raise
306 raise
308 except httplib.HTTPException, inst:
307 except httplib.HTTPException, inst:
309 self.ui.debug(_('http error while sending %s command\n') % cmd)
308 self.ui.debug(_('http error while sending %s command\n') % cmd)
310 self.ui.print_exc()
309 self.ui.print_exc()
311 raise IOError(None, inst)
310 raise IOError(None, inst)
312 except IndexError:
311 except IndexError:
313 # this only happens with Python 2.3, later versions raise URLError
312 # this only happens with Python 2.3, later versions raise URLError
314 raise util.Abort(_('http error, possibly caused by proxy setting'))
313 raise util.Abort(_('http error, possibly caused by proxy setting'))
315 # record the url we got redirected to
314 # record the url we got redirected to
316 resp_url = resp.geturl()
315 resp_url = resp.geturl()
317 if resp_url.endswith(qs):
316 if resp_url.endswith(qs):
318 resp_url = resp_url[:-len(qs)]
317 resp_url = resp_url[:-len(qs)]
319 if self._url != resp_url:
318 if self._url != resp_url:
320 self.ui.status(_('real URL is %s\n') % resp_url)
319 self.ui.status(_('real URL is %s\n') % resp_url)
321 self._url = resp_url
320 self._url = resp_url
322 try:
321 try:
323 proto = resp.getheader('content-type')
322 proto = resp.getheader('content-type')
324 except AttributeError:
323 except AttributeError:
325 proto = resp.headers['content-type']
324 proto = resp.headers['content-type']
326
325
327 # accept old "text/plain" and "application/hg-changegroup" for now
326 # accept old "text/plain" and "application/hg-changegroup" for now
328 if not (proto.startswith('application/mercurial-') or
327 if not (proto.startswith('application/mercurial-') or
329 proto.startswith('text/plain') or
328 proto.startswith('text/plain') or
330 proto.startswith('application/hg-changegroup')):
329 proto.startswith('application/hg-changegroup')):
331 self.ui.debug(_("Requested URL: '%s'\n") % cu)
330 self.ui.debug(_("Requested URL: '%s'\n") % cu)
332 raise repo.RepoError(_("'%s' does not appear to be an hg repository")
331 raise repo.RepoError(_("'%s' does not appear to be an hg repository")
333 % self._url)
332 % self._url)
334
333
335 if proto.startswith('application/mercurial-'):
334 if proto.startswith('application/mercurial-'):
336 try:
335 try:
337 version = proto.split('-', 1)[1]
336 version = proto.split('-', 1)[1]
338 version_info = tuple([int(n) for n in version.split('.')])
337 version_info = tuple([int(n) for n in version.split('.')])
339 except ValueError:
338 except ValueError:
340 raise repo.RepoError(_("'%s' sent a broken Content-type "
339 raise repo.RepoError(_("'%s' sent a broken Content-type "
341 "header (%s)") % (self._url, proto))
340 "header (%s)") % (self._url, proto))
342 if version_info > (0, 1):
341 if version_info > (0, 1):
343 raise repo.RepoError(_("'%s' uses newer protocol %s") %
342 raise repo.RepoError(_("'%s' uses newer protocol %s") %
344 (self._url, version))
343 (self._url, version))
345
344
346 return resp
345 return resp
347
346
348 def do_read(self, cmd, **args):
347 def do_read(self, cmd, **args):
349 fp = self.do_cmd(cmd, **args)
348 fp = self.do_cmd(cmd, **args)
350 try:
349 try:
351 return fp.read()
350 return fp.read()
352 finally:
351 finally:
353 # if using keepalive, allow connection to be reused
352 # if using keepalive, allow connection to be reused
354 fp.close()
353 fp.close()
355
354
356 def lookup(self, key):
355 def lookup(self, key):
357 self.requirecap('lookup', _('look up remote revision'))
356 self.requirecap('lookup', _('look up remote revision'))
358 d = self.do_cmd("lookup", key = key).read()
357 d = self.do_cmd("lookup", key = key).read()
359 success, data = d[:-1].split(' ', 1)
358 success, data = d[:-1].split(' ', 1)
360 if int(success):
359 if int(success):
361 return bin(data)
360 return bin(data)
362 raise repo.RepoError(data)
361 raise repo.RepoError(data)
363
362
364 def heads(self):
363 def heads(self):
365 d = self.do_read("heads")
364 d = self.do_read("heads")
366 try:
365 try:
367 return map(bin, d[:-1].split(" "))
366 return map(bin, d[:-1].split(" "))
368 except:
367 except:
369 raise util.UnexpectedOutput(_("unexpected response:"), d)
368 raise util.UnexpectedOutput(_("unexpected response:"), d)
370
369
371 def branches(self, nodes):
370 def branches(self, nodes):
372 n = " ".join(map(hex, nodes))
371 n = " ".join(map(hex, nodes))
373 d = self.do_read("branches", nodes=n)
372 d = self.do_read("branches", nodes=n)
374 try:
373 try:
375 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
374 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
376 return br
375 return br
377 except:
376 except:
378 raise util.UnexpectedOutput(_("unexpected response:"), d)
377 raise util.UnexpectedOutput(_("unexpected response:"), d)
379
378
380 def between(self, pairs):
379 def between(self, pairs):
381 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
380 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
382 d = self.do_read("between", pairs=n)
381 d = self.do_read("between", pairs=n)
383 try:
382 try:
384 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
383 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
385 return p
384 return p
386 except:
385 except:
387 raise util.UnexpectedOutput(_("unexpected response:"), d)
386 raise util.UnexpectedOutput(_("unexpected response:"), d)
388
387
389 def changegroup(self, nodes, kind):
388 def changegroup(self, nodes, kind):
390 n = " ".join(map(hex, nodes))
389 n = " ".join(map(hex, nodes))
391 f = self.do_cmd("changegroup", roots=n)
390 f = self.do_cmd("changegroup", roots=n)
392 return util.chunkbuffer(zgenerator(f))
391 return util.chunkbuffer(zgenerator(f))
393
392
394 def changegroupsubset(self, bases, heads, source):
393 def changegroupsubset(self, bases, heads, source):
395 self.requirecap('changegroupsubset', _('look up remote changes'))
394 self.requirecap('changegroupsubset', _('look up remote changes'))
396 baselst = " ".join([hex(n) for n in bases])
395 baselst = " ".join([hex(n) for n in bases])
397 headlst = " ".join([hex(n) for n in heads])
396 headlst = " ".join([hex(n) for n in heads])
398 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
397 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
399 return util.chunkbuffer(zgenerator(f))
398 return util.chunkbuffer(zgenerator(f))
400
399
401 def unbundle(self, cg, heads, source):
400 def unbundle(self, cg, heads, source):
402 # have to stream bundle to a temp file because we do not have
401 # have to stream bundle to a temp file because we do not have
403 # http 1.1 chunked transfer.
402 # http 1.1 chunked transfer.
404
403
405 type = ""
404 type = ""
406 types = self.capable('unbundle')
405 types = self.capable('unbundle')
407 # servers older than d1b16a746db6 will send 'unbundle' as a
406 # servers older than d1b16a746db6 will send 'unbundle' as a
408 # boolean capability
407 # boolean capability
409 try:
408 try:
410 types = types.split(',')
409 types = types.split(',')
411 except AttributeError:
410 except AttributeError:
412 types = [""]
411 types = [""]
413 if types:
412 if types:
414 for x in types:
413 for x in types:
415 if x in changegroup.bundletypes:
414 if x in changegroup.bundletypes:
416 type = x
415 type = x
417 break
416 break
418
417
419 tempname = changegroup.writebundle(cg, None, type)
418 tempname = changegroup.writebundle(cg, None, type)
420 fp = httpsendfile(tempname, "rb")
419 fp = httpsendfile(tempname, "rb")
421 try:
420 try:
422 try:
421 try:
423 rfp = self.do_cmd(
422 rfp = self.do_cmd(
424 'unbundle', data=fp,
423 'unbundle', data=fp,
425 headers={'content-type': 'application/octet-stream'},
424 headers={'content-type': 'application/octet-stream'},
426 heads=' '.join(map(hex, heads)))
425 heads=' '.join(map(hex, heads)))
427 try:
426 try:
428 ret = int(rfp.readline())
427 ret = int(rfp.readline())
429 self.ui.write(rfp.read())
428 self.ui.write(rfp.read())
430 return ret
429 return ret
431 finally:
430 finally:
432 rfp.close()
431 rfp.close()
433 except socket.error, err:
432 except socket.error, err:
434 if err[0] in (errno.ECONNRESET, errno.EPIPE):
433 if err[0] in (errno.ECONNRESET, errno.EPIPE):
435 raise util.Abort(_('push failed: %s') % err[1])
434 raise util.Abort(_('push failed: %s') % err[1])
436 raise util.Abort(err[1])
435 raise util.Abort(err[1])
437 finally:
436 finally:
438 fp.close()
437 fp.close()
439 os.unlink(tempname)
438 os.unlink(tempname)
440
439
441 def stream_out(self):
440 def stream_out(self):
442 return self.do_cmd('stream_out')
441 return self.do_cmd('stream_out')
443
442
444 class httpsrepository(httprepository):
443 class httpsrepository(httprepository):
445 def __init__(self, ui, path):
444 def __init__(self, ui, path):
446 if not has_https:
445 if not has_https:
447 raise util.Abort(_('Python support for SSL and HTTPS '
446 raise util.Abort(_('Python support for SSL and HTTPS '
448 'is not installed'))
447 'is not installed'))
449 httprepository.__init__(self, ui, path)
448 httprepository.__init__(self, ui, path)
450
449
451 def instance(ui, path, create):
450 def instance(ui, path, create):
452 if create:
451 if create:
453 raise util.Abort(_('cannot create new http repository'))
452 raise util.Abort(_('cannot create new http repository'))
454 if path.startswith('https:'):
453 if path.startswith('https:'):
455 return httpsrepository(ui, path)
454 return httpsrepository(ui, path)
456 return httprepository(ui, path)
455 return httprepository(ui, path)
@@ -1,19 +1,46 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 "$TESTDIR/hghave" git || exit 80
3 "$TESTDIR/hghave" git || exit 80
4
4
5 echo "[extensions]" >> $HGRCPATH
5 echo "[extensions]" >> $HGRCPATH
6 echo "convert=" >> $HGRCPATH
6 echo "convert=" >> $HGRCPATH
7
7
8 GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
9 GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
10 GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
11 GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
12 GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
13 GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
14
15 count=10
16 commit()
17 {
18 GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000"
19 GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
20 git commit "$@" >/dev/null 2>/dev/null || echo "git commit error"
21 count=`expr $count + 1`
22 }
23
8 mkdir git-repo
24 mkdir git-repo
9 cd git-repo
25 cd git-repo
10 git init-db >/dev/null 2>/dev/null
26 git init-db >/dev/null 2>/dev/null
11 echo a > a
27 echo a > a
12 git add a
28 git add a
13 git commit -m t1 >/dev/null 2>/dev/null || echo "git commit error"
29 commit -m t1
30
14 echo b >> a
31 echo b >> a
15 git commit -a -m t2 >/dev/null || echo "git commit error"
32 commit -a -m t2.1
33
34 git checkout -b other HEAD^ >/dev/null 2>/dev/null
35 echo c > a
36 echo a >> a
37 commit -a -m t2.2
38
39 git checkout master >/dev/null 2>/dev/null
40 git pull --no-commit . other > /dev/null 2>/dev/null
41 commit -m 'Merge branch other'
16 cd ..
42 cd ..
17
43
18 hg convert git-repo
44 hg convert --datesort git-repo
19
45
46 hg -R git-repo-hg tip -v
@@ -1,7 +1,22 b''
1 assuming destination git-repo-hg
1 assuming destination git-repo-hg
2 initializing destination git-repo-hg repository
2 initializing destination git-repo-hg repository
3 scanning source...
3 scanning source...
4 sorting...
4 sorting...
5 converting...
5 converting...
6 1 t1
6 3 t1
7 0 t2
7 2 t2.1
8 1 t2.2
9 0 Merge branch other
10 changeset: 3:69b3a302b4a1
11 tag: tip
12 parent: 1:0de2a40e261b
13 parent: 2:8815d3b33506
14 user: test <test@example.org>
15 date: Mon Jan 01 00:00:13 2007 +0000
16 files: a
17 description:
18 Merge branch other
19
20 committer: test <test@example.org>
21
22
@@ -1,50 +1,55 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 # Environement setup for MQ
3 # Environement setup for MQ
4 echo "[extensions]" >> $HGRCPATH
4 echo "[extensions]" >> $HGRCPATH
5 echo "mq=" >> $HGRCPATH
5 echo "mq=" >> $HGRCPATH
6
6
7 #Repo init
7 #Repo init
8 hg init
8 hg init
9 hg qinit
9 hg qinit
10
10
11 echo =======================
12 echo "Should fail if no patches applied"
13 hg qrefresh
14 hg qrefresh -e
15
11 hg qnew -m "First commit message" first-patch
16 hg qnew -m "First commit message" first-patch
12 echo aaaa > file
17 echo aaaa > file
13 hg add file
18 hg add file
14 hg qrefresh
19 hg qrefresh
15 echo =======================
20 echo =======================
16 echo "Should display 'First commit message'"
21 echo "Should display 'First commit message'"
17 hg log -l1 -v | sed -n '/description/,$p'
22 hg log -l1 -v | sed -n '/description/,$p'
18 echo
23 echo
19
24
20 # Testing changing message with -m
25 # Testing changing message with -m
21 echo bbbb > file
26 echo bbbb > file
22 hg qrefresh -m "Second commit message"
27 hg qrefresh -m "Second commit message"
23 echo =======================
28 echo =======================
24 echo "Should display 'Second commit message'"
29 echo "Should display 'Second commit message'"
25 hg log -l1 -v | sed -n '/description/,$p'
30 hg log -l1 -v | sed -n '/description/,$p'
26 echo
31 echo
27
32
28
33
29 # Testing changing message with -l
34 # Testing changing message with -l
30 echo "Third commit message" > logfile
35 echo "Third commit message" > logfile
31 echo " This is the 3rd log message" >> logfile
36 echo " This is the 3rd log message" >> logfile
32 echo bbbb > file
37 echo bbbb > file
33 hg qrefresh -l logfile
38 hg qrefresh -l logfile
34 echo =======================
39 echo =======================
35 printf "Should display 'Third commit message\\\n This is the 3rd log message'\n"
40 printf "Should display 'Third commit message\\\n This is the 3rd log message'\n"
36 hg log -l1 -v | sed -n '/description/,$p'
41 hg log -l1 -v | sed -n '/description/,$p'
37 echo
42 echo
38
43
39 # Testing changing message with -l-
44 # Testing changing message with -l-
40 hg qnew -m "First commit message" second-patch
45 hg qnew -m "First commit message" second-patch
41 echo aaaa > file2
46 echo aaaa > file2
42 hg add file2
47 hg add file2
43 echo bbbb > file2
48 echo bbbb > file2
44 (echo "Fifth commit message"
49 (echo "Fifth commit message"
45 echo " This is the 5th log message" >> logfile) |\
50 echo " This is the 5th log message" >> logfile) |\
46 hg qrefresh -l-
51 hg qrefresh -l-
47 echo =======================
52 echo =======================
48 printf "Should display 'Fifth commit message\\\n This is the 5th log message'\n"
53 printf "Should display 'Fifth commit message\\\n This is the 5th log message'\n"
49 hg log -l1 -v | sed -n '/description/,$p'
54 hg log -l1 -v | sed -n '/description/,$p'
50 echo
55 echo
@@ -1,29 +1,33 b''
1 =======================
2 Should fail if no patches applied
3 No patches applied
4 No patches applied
1 =======================
5 =======================
2 Should display 'First commit message'
6 Should display 'First commit message'
3 description:
7 description:
4 First commit message
8 First commit message
5
9
6
10
7
11
8 =======================
12 =======================
9 Should display 'Second commit message'
13 Should display 'Second commit message'
10 description:
14 description:
11 Second commit message
15 Second commit message
12
16
13
17
14
18
15 =======================
19 =======================
16 Should display 'Third commit message\n This is the 3rd log message'
20 Should display 'Third commit message\n This is the 3rd log message'
17 description:
21 description:
18 Third commit message
22 Third commit message
19 This is the 3rd log message
23 This is the 3rd log message
20
24
21
25
22
26
23 =======================
27 =======================
24 Should display 'Fifth commit message\n This is the 5th log message'
28 Should display 'Fifth commit message\n This is the 5th log message'
25 description:
29 description:
26 Fifth commit message
30 Fifth commit message
27
31
28
32
29
33
@@ -1,210 +1,215 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 hg init
3 hg init
4 mkdir d1 d1/d11 d2
4 mkdir d1 d1/d11 d2
5 echo d1/a > d1/a
5 echo d1/a > d1/a
6 echo d1/ba > d1/ba
6 echo d1/ba > d1/ba
7 echo d1/a1 > d1/d11/a1
7 echo d1/a1 > d1/d11/a1
8 echo d1/b > d1/b
8 echo d1/b > d1/b
9 echo d2/b > d2/b
9 echo d2/b > d2/b
10 hg add d1/a d1/b d1/ba d1/d11/a1 d2/b
10 hg add d1/a d1/b d1/ba d1/d11/a1 d2/b
11 hg commit -m "1" -d "1000000 0"
11 hg commit -m "1" -d "1000000 0"
12
12
13 echo "# rename a single file"
13 echo "# rename a single file"
14 hg rename d1/d11/a1 d2/c
14 hg rename d1/d11/a1 d2/c
15 hg status -C
15 hg status -C
16 hg update -C
16 hg update -C
17
17
18 echo "# rename --after a single file"
18 echo "# rename --after a single file"
19 mv d1/d11/a1 d2/c
19 mv d1/d11/a1 d2/c
20 hg rename --after d1/d11/a1 d2/c
20 hg rename --after d1/d11/a1 d2/c
21 hg status -C
21 hg status -C
22 hg update -C
22 hg update -C
23
23
24 echo "# move a single file to an existing directory"
24 echo "# move a single file to an existing directory"
25 hg rename d1/d11/a1 d2
25 hg rename d1/d11/a1 d2
26 hg status -C
26 hg status -C
27 hg update -C
27 hg update -C
28
28
29 echo "# move --after a single file to an existing directory"
29 echo "# move --after a single file to an existing directory"
30 mv d1/d11/a1 d2
30 mv d1/d11/a1 d2
31 hg rename --after d1/d11/a1 d2
31 hg rename --after d1/d11/a1 d2
32 hg status -C
32 hg status -C
33 hg update -C
33 hg update -C
34
34
35 echo "# rename a file using a relative path"
35 echo "# rename a file using a relative path"
36 (cd d1/d11; hg rename ../../d2/b e)
36 (cd d1/d11; hg rename ../../d2/b e)
37 hg status -C
37 hg status -C
38 hg update -C
38 hg update -C
39
39
40 echo "# rename --after a file using a relative path"
40 echo "# rename --after a file using a relative path"
41 (cd d1/d11; mv ../../d2/b e; hg rename --after ../../d2/b e)
41 (cd d1/d11; mv ../../d2/b e; hg rename --after ../../d2/b e)
42 hg status -C
42 hg status -C
43 hg update -C
43 hg update -C
44
44
45 echo "# rename directory d1 as d3"
45 echo "# rename directory d1 as d3"
46 hg rename d1/ d3
46 hg rename d1/ d3
47 hg status -C
47 hg status -C
48 hg update -C
48 hg update -C
49
49
50 echo "# rename --after directory d1 as d3"
50 echo "# rename --after directory d1 as d3"
51 mv d1 d3
51 mv d1 d3
52 hg rename --after d1 d3
52 hg rename --after d1 d3
53 hg status -C
53 hg status -C
54 hg update -C
54 hg update -C
55
55
56 echo "# move a directory using a relative path"
56 echo "# move a directory using a relative path"
57 (cd d2; mkdir d3; hg rename ../d1/d11 d3)
57 (cd d2; mkdir d3; hg rename ../d1/d11 d3)
58 hg status -C
58 hg status -C
59 hg update -C
59 hg update -C
60
60
61 echo "# move --after a directory using a relative path"
61 echo "# move --after a directory using a relative path"
62 (cd d2; mkdir d3; mv ../d1/d11 d3; hg rename --after ../d1/d11 d3)
62 (cd d2; mkdir d3; mv ../d1/d11 d3; hg rename --after ../d1/d11 d3)
63 hg status -C
63 hg status -C
64 hg update -C
64 hg update -C
65
65
66 echo "# move directory d1/d11 to an existing directory d2 (removes empty d1)"
66 echo "# move directory d1/d11 to an existing directory d2 (removes empty d1)"
67 hg rename d1/d11/ d2
67 hg rename d1/d11/ d2
68 hg status -C
68 hg status -C
69 hg update -C
69 hg update -C
70
70
71 echo "# move directories d1 and d2 to a new directory d3"
71 echo "# move directories d1 and d2 to a new directory d3"
72 mkdir d3
72 mkdir d3
73 hg rename d1 d2 d3
73 hg rename d1 d2 d3
74 hg status -C
74 hg status -C
75 hg update -C
75 hg update -C
76
76
77 echo "# move --after directories d1 and d2 to a new directory d3"
77 echo "# move --after directories d1 and d2 to a new directory d3"
78 mkdir d3
78 mkdir d3
79 mv d1 d2 d3
79 mv d1 d2 d3
80 hg rename --after d1 d2 d3
80 hg rename --after d1 d2 d3
81 hg status -C
81 hg status -C
82 hg update -C
82 hg update -C
83
83
84 echo "# move everything under directory d1 to existing directory d2, do not"
84 echo "# move everything under directory d1 to existing directory d2, do not"
85 echo "# overwrite existing files (d2/b)"
85 echo "# overwrite existing files (d2/b)"
86 hg rename d1/* d2
86 hg rename d1/* d2
87 hg status -C
87 hg status -C
88 diff d1/b d2/b
88 diff d1/b d2/b
89 hg update -C
89 hg update -C
90
90
91 echo "# attempt to move one file into a non-existent directory"
92 hg rename d1/a dx/
93 hg status -C
94 hg update -C
95
91 echo "# attempt to move potentially more than one file into a non-existent"
96 echo "# attempt to move potentially more than one file into a non-existent"
92 echo "# directory"
97 echo "# directory"
93 hg rename 'glob:d1/**' dx
98 hg rename 'glob:d1/**' dx
94
99
95 echo "# move every file under d1 to d2/d21 (glob)"
100 echo "# move every file under d1 to d2/d21 (glob)"
96 mkdir d2/d21
101 mkdir d2/d21
97 hg rename 'glob:d1/**' d2/d21
102 hg rename 'glob:d1/**' d2/d21
98 hg status -C
103 hg status -C
99 hg update -C
104 hg update -C
100
105
101 echo "# move --after some files under d1 to d2/d21 (glob)"
106 echo "# move --after some files under d1 to d2/d21 (glob)"
102 mkdir d2/d21
107 mkdir d2/d21
103 mv d1/a d1/d11/a1 d2/d21
108 mv d1/a d1/d11/a1 d2/d21
104 hg rename --after 'glob:d1/**' d2/d21
109 hg rename --after 'glob:d1/**' d2/d21
105 hg status -C
110 hg status -C
106 hg update -C
111 hg update -C
107
112
108 echo "# move every file under d1 starting with an 'a' to d2/d21 (regexp)"
113 echo "# move every file under d1 starting with an 'a' to d2/d21 (regexp)"
109 mkdir d2/d21
114 mkdir d2/d21
110 hg rename 're:d1/([^a][^/]*/)*a.*' d2/d21
115 hg rename 're:d1/([^a][^/]*/)*a.*' d2/d21
111 hg status -C
116 hg status -C
112 hg update -C
117 hg update -C
113
118
114 echo "# attempt to overwrite an existing file"
119 echo "# attempt to overwrite an existing file"
115 echo "ca" > d1/ca
120 echo "ca" > d1/ca
116 hg rename d1/ba d1/ca
121 hg rename d1/ba d1/ca
117 hg status -C
122 hg status -C
118 hg update -C
123 hg update -C
119
124
120 echo "# forced overwrite of an existing file"
125 echo "# forced overwrite of an existing file"
121 echo "ca" > d1/ca
126 echo "ca" > d1/ca
122 hg rename --force d1/ba d1/ca
127 hg rename --force d1/ba d1/ca
123 hg status -C
128 hg status -C
124 hg update -C
129 hg update -C
125
130
126 echo "# replace a symlink with a file"
131 echo "# replace a symlink with a file"
127 ln -s ba d1/ca
132 ln -s ba d1/ca
128 hg rename --force d1/ba d1/ca
133 hg rename --force d1/ba d1/ca
129 hg status -C
134 hg status -C
130 hg update -C
135 hg update -C
131
136
132 echo "# do not copy more than one source file to the same destination file"
137 echo "# do not copy more than one source file to the same destination file"
133 mkdir d3
138 mkdir d3
134 hg rename d1/* d2/* d3
139 hg rename d1/* d2/* d3
135 hg status -C
140 hg status -C
136 hg update -C
141 hg update -C
137
142
138 echo "# move a whole subtree with \"hg rename .\""
143 echo "# move a whole subtree with \"hg rename .\""
139 mkdir d3
144 mkdir d3
140 (cd d1; hg rename . ../d3)
145 (cd d1; hg rename . ../d3)
141 hg status -C
146 hg status -C
142 hg update -C
147 hg update -C
143
148
144 echo "# move a whole subtree with \"hg rename --after .\""
149 echo "# move a whole subtree with \"hg rename --after .\""
145 mkdir d3
150 mkdir d3
146 mv d1/* d3
151 mv d1/* d3
147 (cd d1; hg rename --after . ../d3)
152 (cd d1; hg rename --after . ../d3)
148 hg status -C
153 hg status -C
149 hg update -C
154 hg update -C
150
155
151 echo "# move the parent tree with \"hg rename ..\""
156 echo "# move the parent tree with \"hg rename ..\""
152 (cd d1/d11; hg rename .. ../../d3)
157 (cd d1/d11; hg rename .. ../../d3)
153 hg status -C
158 hg status -C
154 hg update -C
159 hg update -C
155
160
156 echo "# skip removed files"
161 echo "# skip removed files"
157 hg remove d1/b
162 hg remove d1/b
158 hg rename d1 d3
163 hg rename d1 d3
159 hg status -C
164 hg status -C
160 hg update -C
165 hg update -C
161
166
162 echo "# transitive rename"
167 echo "# transitive rename"
163 hg rename d1/b d1/bb
168 hg rename d1/b d1/bb
164 hg rename d1/bb d1/bc
169 hg rename d1/bb d1/bc
165 hg status -C
170 hg status -C
166 hg update -C
171 hg update -C
167
172
168 echo "# transitive rename --after"
173 echo "# transitive rename --after"
169 hg rename d1/b d1/bb
174 hg rename d1/b d1/bb
170 mv d1/bb d1/bc
175 mv d1/bb d1/bc
171 hg rename --after d1/bb d1/bc
176 hg rename --after d1/bb d1/bc
172 hg status -C
177 hg status -C
173 hg update -C
178 hg update -C
174
179
175 echo "# idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)"
180 echo "# idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)"
176 hg rename d1/b d1/bb
181 hg rename d1/b d1/bb
177 echo "some stuff added to d1/bb" >> d1/bb
182 echo "some stuff added to d1/bb" >> d1/bb
178 hg rename d1/bb d1/b
183 hg rename d1/bb d1/b
179 hg status -C
184 hg status -C
180 hg update -C
185 hg update -C
181
186
182 echo "# check illegal path components"
187 echo "# check illegal path components"
183
188
184 hg rename d1/d11/a1 .hg/foo
189 hg rename d1/d11/a1 .hg/foo
185 hg status -C
190 hg status -C
186 hg rename d1/d11/a1 ../foo
191 hg rename d1/d11/a1 ../foo
187 hg status -C
192 hg status -C
188
193
189 mv d1/d11/a1 .hg/foo
194 mv d1/d11/a1 .hg/foo
190 hg rename --after d1/d11/a1 .hg/foo
195 hg rename --after d1/d11/a1 .hg/foo
191 hg status -C
196 hg status -C
192 hg update -C
197 hg update -C
193 rm .hg/foo
198 rm .hg/foo
194
199
195 hg rename d1/d11/a1 .hg
200 hg rename d1/d11/a1 .hg
196 hg status -C
201 hg status -C
197 hg rename d1/d11/a1 ..
202 hg rename d1/d11/a1 ..
198 hg status -C
203 hg status -C
199
204
200 mv d1/d11/a1 .hg
205 mv d1/d11/a1 .hg
201 hg rename --after d1/d11/a1 .hg
206 hg rename --after d1/d11/a1 .hg
202 hg status -C
207 hg status -C
203 hg update -C
208 hg update -C
204 rm .hg/a1
209 rm .hg/a1
205
210
206 (cd d1/d11; hg rename ../../d2/b ../../.hg/foo)
211 (cd d1/d11; hg rename ../../d2/b ../../.hg/foo)
207 hg status -C
212 hg status -C
208 (cd d1/d11; hg rename ../../d2/b ../../../foo)
213 (cd d1/d11; hg rename ../../d2/b ../../../foo)
209 hg status -C
214 hg status -C
210
215
@@ -1,358 +1,361 b''
1 # rename a single file
1 # rename a single file
2 A d2/c
2 A d2/c
3 d1/d11/a1
3 d1/d11/a1
4 R d1/d11/a1
4 R d1/d11/a1
5 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
5 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
6 # rename --after a single file
6 # rename --after a single file
7 A d2/c
7 A d2/c
8 d1/d11/a1
8 d1/d11/a1
9 R d1/d11/a1
9 R d1/d11/a1
10 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
10 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
11 # move a single file to an existing directory
11 # move a single file to an existing directory
12 A d2/a1
12 A d2/a1
13 d1/d11/a1
13 d1/d11/a1
14 R d1/d11/a1
14 R d1/d11/a1
15 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
15 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
16 # move --after a single file to an existing directory
16 # move --after a single file to an existing directory
17 A d2/a1
17 A d2/a1
18 d1/d11/a1
18 d1/d11/a1
19 R d1/d11/a1
19 R d1/d11/a1
20 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
20 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
21 # rename a file using a relative path
21 # rename a file using a relative path
22 A d1/d11/e
22 A d1/d11/e
23 d2/b
23 d2/b
24 R d2/b
24 R d2/b
25 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
25 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
26 # rename --after a file using a relative path
26 # rename --after a file using a relative path
27 A d1/d11/e
27 A d1/d11/e
28 d2/b
28 d2/b
29 R d2/b
29 R d2/b
30 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
30 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
31 # rename directory d1 as d3
31 # rename directory d1 as d3
32 copying d1/a to d3/a
32 copying d1/a to d3/a
33 copying d1/b to d3/b
33 copying d1/b to d3/b
34 copying d1/ba to d3/ba
34 copying d1/ba to d3/ba
35 copying d1/d11/a1 to d3/d11/a1
35 copying d1/d11/a1 to d3/d11/a1
36 removing d1/a
36 removing d1/a
37 removing d1/b
37 removing d1/b
38 removing d1/ba
38 removing d1/ba
39 removing d1/d11/a1
39 removing d1/d11/a1
40 A d3/a
40 A d3/a
41 d1/a
41 d1/a
42 A d3/b
42 A d3/b
43 d1/b
43 d1/b
44 A d3/ba
44 A d3/ba
45 d1/ba
45 d1/ba
46 A d3/d11/a1
46 A d3/d11/a1
47 d1/d11/a1
47 d1/d11/a1
48 R d1/a
48 R d1/a
49 R d1/b
49 R d1/b
50 R d1/ba
50 R d1/ba
51 R d1/d11/a1
51 R d1/d11/a1
52 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
52 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
53 # rename --after directory d1 as d3
53 # rename --after directory d1 as d3
54 copying d1/a to d3/a
54 copying d1/a to d3/a
55 copying d1/b to d3/b
55 copying d1/b to d3/b
56 copying d1/ba to d3/ba
56 copying d1/ba to d3/ba
57 copying d1/d11/a1 to d3/d11/a1
57 copying d1/d11/a1 to d3/d11/a1
58 removing d1/a
58 removing d1/a
59 removing d1/b
59 removing d1/b
60 removing d1/ba
60 removing d1/ba
61 removing d1/d11/a1
61 removing d1/d11/a1
62 A d3/a
62 A d3/a
63 d1/a
63 d1/a
64 A d3/b
64 A d3/b
65 d1/b
65 d1/b
66 A d3/ba
66 A d3/ba
67 d1/ba
67 d1/ba
68 A d3/d11/a1
68 A d3/d11/a1
69 d1/d11/a1
69 d1/d11/a1
70 R d1/a
70 R d1/a
71 R d1/b
71 R d1/b
72 R d1/ba
72 R d1/ba
73 R d1/d11/a1
73 R d1/d11/a1
74 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
74 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
75 # move a directory using a relative path
75 # move a directory using a relative path
76 copying ../d1/d11/a1 to d3/d11/a1
76 copying ../d1/d11/a1 to d3/d11/a1
77 removing ../d1/d11/a1
77 removing ../d1/d11/a1
78 A d2/d3/d11/a1
78 A d2/d3/d11/a1
79 d1/d11/a1
79 d1/d11/a1
80 R d1/d11/a1
80 R d1/d11/a1
81 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
81 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
82 # move --after a directory using a relative path
82 # move --after a directory using a relative path
83 copying ../d1/d11/a1 to d3/d11/a1
83 copying ../d1/d11/a1 to d3/d11/a1
84 removing ../d1/d11/a1
84 removing ../d1/d11/a1
85 A d2/d3/d11/a1
85 A d2/d3/d11/a1
86 d1/d11/a1
86 d1/d11/a1
87 R d1/d11/a1
87 R d1/d11/a1
88 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
88 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
89 # move directory d1/d11 to an existing directory d2 (removes empty d1)
89 # move directory d1/d11 to an existing directory d2 (removes empty d1)
90 copying d1/d11/a1 to d2/d11/a1
90 copying d1/d11/a1 to d2/d11/a1
91 removing d1/d11/a1
91 removing d1/d11/a1
92 A d2/d11/a1
92 A d2/d11/a1
93 d1/d11/a1
93 d1/d11/a1
94 R d1/d11/a1
94 R d1/d11/a1
95 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
95 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
96 # move directories d1 and d2 to a new directory d3
96 # move directories d1 and d2 to a new directory d3
97 copying d1/a to d3/d1/a
97 copying d1/a to d3/d1/a
98 copying d1/b to d3/d1/b
98 copying d1/b to d3/d1/b
99 copying d1/ba to d3/d1/ba
99 copying d1/ba to d3/d1/ba
100 copying d1/d11/a1 to d3/d1/d11/a1
100 copying d1/d11/a1 to d3/d1/d11/a1
101 copying d2/b to d3/d2/b
101 copying d2/b to d3/d2/b
102 removing d1/a
102 removing d1/a
103 removing d1/b
103 removing d1/b
104 removing d1/ba
104 removing d1/ba
105 removing d1/d11/a1
105 removing d1/d11/a1
106 removing d2/b
106 removing d2/b
107 A d3/d1/a
107 A d3/d1/a
108 d1/a
108 d1/a
109 A d3/d1/b
109 A d3/d1/b
110 d1/b
110 d1/b
111 A d3/d1/ba
111 A d3/d1/ba
112 d1/ba
112 d1/ba
113 A d3/d1/d11/a1
113 A d3/d1/d11/a1
114 d1/d11/a1
114 d1/d11/a1
115 A d3/d2/b
115 A d3/d2/b
116 d2/b
116 d2/b
117 R d1/a
117 R d1/a
118 R d1/b
118 R d1/b
119 R d1/ba
119 R d1/ba
120 R d1/d11/a1
120 R d1/d11/a1
121 R d2/b
121 R d2/b
122 5 files updated, 0 files merged, 5 files removed, 0 files unresolved
122 5 files updated, 0 files merged, 5 files removed, 0 files unresolved
123 # move --after directories d1 and d2 to a new directory d3
123 # move --after directories d1 and d2 to a new directory d3
124 copying d1/a to d3/d1/a
124 copying d1/a to d3/d1/a
125 copying d1/b to d3/d1/b
125 copying d1/b to d3/d1/b
126 copying d1/ba to d3/d1/ba
126 copying d1/ba to d3/d1/ba
127 copying d1/d11/a1 to d3/d1/d11/a1
127 copying d1/d11/a1 to d3/d1/d11/a1
128 copying d2/b to d3/d2/b
128 copying d2/b to d3/d2/b
129 removing d1/a
129 removing d1/a
130 removing d1/b
130 removing d1/b
131 removing d1/ba
131 removing d1/ba
132 removing d1/d11/a1
132 removing d1/d11/a1
133 removing d2/b
133 removing d2/b
134 A d3/d1/a
134 A d3/d1/a
135 d1/a
135 d1/a
136 A d3/d1/b
136 A d3/d1/b
137 d1/b
137 d1/b
138 A d3/d1/ba
138 A d3/d1/ba
139 d1/ba
139 d1/ba
140 A d3/d1/d11/a1
140 A d3/d1/d11/a1
141 d1/d11/a1
141 d1/d11/a1
142 A d3/d2/b
142 A d3/d2/b
143 d2/b
143 d2/b
144 R d1/a
144 R d1/a
145 R d1/b
145 R d1/b
146 R d1/ba
146 R d1/ba
147 R d1/d11/a1
147 R d1/d11/a1
148 R d2/b
148 R d2/b
149 5 files updated, 0 files merged, 5 files removed, 0 files unresolved
149 5 files updated, 0 files merged, 5 files removed, 0 files unresolved
150 # move everything under directory d1 to existing directory d2, do not
150 # move everything under directory d1 to existing directory d2, do not
151 # overwrite existing files (d2/b)
151 # overwrite existing files (d2/b)
152 d2/b: not overwriting - file exists
152 d2/b: not overwriting - file exists
153 copying d1/d11/a1 to d2/d11/a1
153 copying d1/d11/a1 to d2/d11/a1
154 removing d1/d11/a1
154 removing d1/d11/a1
155 A d2/a
155 A d2/a
156 d1/a
156 d1/a
157 A d2/ba
157 A d2/ba
158 d1/ba
158 d1/ba
159 A d2/d11/a1
159 A d2/d11/a1
160 d1/d11/a1
160 d1/d11/a1
161 R d1/a
161 R d1/a
162 R d1/ba
162 R d1/ba
163 R d1/d11/a1
163 R d1/d11/a1
164 1c1
164 1c1
165 < d1/b
165 < d1/b
166 ---
166 ---
167 > d2/b
167 > d2/b
168 3 files updated, 0 files merged, 3 files removed, 0 files unresolved
168 3 files updated, 0 files merged, 3 files removed, 0 files unresolved
169 # attempt to move one file into a non-existent directory
170 abort: destination dx/ is not a directory
171 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
169 # attempt to move potentially more than one file into a non-existent
172 # attempt to move potentially more than one file into a non-existent
170 # directory
173 # directory
171 abort: with multiple sources, destination must be an existing directory
174 abort: with multiple sources, destination must be an existing directory
172 # move every file under d1 to d2/d21 (glob)
175 # move every file under d1 to d2/d21 (glob)
173 copying d1/a to d2/d21/a
176 copying d1/a to d2/d21/a
174 copying d1/b to d2/d21/b
177 copying d1/b to d2/d21/b
175 copying d1/ba to d2/d21/ba
178 copying d1/ba to d2/d21/ba
176 copying d1/d11/a1 to d2/d21/a1
179 copying d1/d11/a1 to d2/d21/a1
177 removing d1/a
180 removing d1/a
178 removing d1/b
181 removing d1/b
179 removing d1/ba
182 removing d1/ba
180 removing d1/d11/a1
183 removing d1/d11/a1
181 A d2/d21/a
184 A d2/d21/a
182 d1/a
185 d1/a
183 A d2/d21/a1
186 A d2/d21/a1
184 d1/d11/a1
187 d1/d11/a1
185 A d2/d21/b
188 A d2/d21/b
186 d1/b
189 d1/b
187 A d2/d21/ba
190 A d2/d21/ba
188 d1/ba
191 d1/ba
189 R d1/a
192 R d1/a
190 R d1/b
193 R d1/b
191 R d1/ba
194 R d1/ba
192 R d1/d11/a1
195 R d1/d11/a1
193 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
196 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
194 # move --after some files under d1 to d2/d21 (glob)
197 # move --after some files under d1 to d2/d21 (glob)
195 copying d1/a to d2/d21/a
198 copying d1/a to d2/d21/a
196 copying d1/d11/a1 to d2/d21/a1
199 copying d1/d11/a1 to d2/d21/a1
197 removing d1/a
200 removing d1/a
198 removing d1/d11/a1
201 removing d1/d11/a1
199 A d2/d21/a
202 A d2/d21/a
200 d1/a
203 d1/a
201 A d2/d21/a1
204 A d2/d21/a1
202 d1/d11/a1
205 d1/d11/a1
203 R d1/a
206 R d1/a
204 R d1/d11/a1
207 R d1/d11/a1
205 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
208 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
206 # move every file under d1 starting with an 'a' to d2/d21 (regexp)
209 # move every file under d1 starting with an 'a' to d2/d21 (regexp)
207 copying d1/a to d2/d21/a
210 copying d1/a to d2/d21/a
208 copying d1/d11/a1 to d2/d21/a1
211 copying d1/d11/a1 to d2/d21/a1
209 removing d1/a
212 removing d1/a
210 removing d1/d11/a1
213 removing d1/d11/a1
211 A d2/d21/a
214 A d2/d21/a
212 d1/a
215 d1/a
213 A d2/d21/a1
216 A d2/d21/a1
214 d1/d11/a1
217 d1/d11/a1
215 R d1/a
218 R d1/a
216 R d1/d11/a1
219 R d1/d11/a1
217 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
220 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
218 # attempt to overwrite an existing file
221 # attempt to overwrite an existing file
219 d1/ca: not overwriting - file exists
222 d1/ca: not overwriting - file exists
220 ? d1/ca
223 ? d1/ca
221 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
224 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 # forced overwrite of an existing file
225 # forced overwrite of an existing file
223 A d1/ca
226 A d1/ca
224 d1/ba
227 d1/ba
225 R d1/ba
228 R d1/ba
226 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
229 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
227 # replace a symlink with a file
230 # replace a symlink with a file
228 A d1/ca
231 A d1/ca
229 d1/ba
232 d1/ba
230 R d1/ba
233 R d1/ba
231 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
234 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
232 # do not copy more than one source file to the same destination file
235 # do not copy more than one source file to the same destination file
233 copying d1/d11/a1 to d3/d11/a1
236 copying d1/d11/a1 to d3/d11/a1
234 d3/b: not overwriting - d2/b collides with d1/b
237 d3/b: not overwriting - d2/b collides with d1/b
235 removing d1/d11/a1
238 removing d1/d11/a1
236 A d3/a
239 A d3/a
237 d1/a
240 d1/a
238 A d3/b
241 A d3/b
239 d1/b
242 d1/b
240 A d3/ba
243 A d3/ba
241 d1/ba
244 d1/ba
242 A d3/d11/a1
245 A d3/d11/a1
243 d1/d11/a1
246 d1/d11/a1
244 R d1/a
247 R d1/a
245 R d1/b
248 R d1/b
246 R d1/ba
249 R d1/ba
247 R d1/d11/a1
250 R d1/d11/a1
248 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
251 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
249 # move a whole subtree with "hg rename ."
252 # move a whole subtree with "hg rename ."
250 copying a to ../d3/d1/a
253 copying a to ../d3/d1/a
251 copying b to ../d3/d1/b
254 copying b to ../d3/d1/b
252 copying ba to ../d3/d1/ba
255 copying ba to ../d3/d1/ba
253 copying d11/a1 to ../d3/d1/d11/a1
256 copying d11/a1 to ../d3/d1/d11/a1
254 removing a
257 removing a
255 removing b
258 removing b
256 removing ba
259 removing ba
257 removing d11/a1
260 removing d11/a1
258 A d3/d1/a
261 A d3/d1/a
259 d1/a
262 d1/a
260 A d3/d1/b
263 A d3/d1/b
261 d1/b
264 d1/b
262 A d3/d1/ba
265 A d3/d1/ba
263 d1/ba
266 d1/ba
264 A d3/d1/d11/a1
267 A d3/d1/d11/a1
265 d1/d11/a1
268 d1/d11/a1
266 R d1/a
269 R d1/a
267 R d1/b
270 R d1/b
268 R d1/ba
271 R d1/ba
269 R d1/d11/a1
272 R d1/d11/a1
270 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
273 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
271 # move a whole subtree with "hg rename --after ."
274 # move a whole subtree with "hg rename --after ."
272 copying a to ../d3/a
275 copying a to ../d3/a
273 copying b to ../d3/b
276 copying b to ../d3/b
274 copying ba to ../d3/ba
277 copying ba to ../d3/ba
275 copying d11/a1 to ../d3/d11/a1
278 copying d11/a1 to ../d3/d11/a1
276 removing a
279 removing a
277 removing b
280 removing b
278 removing ba
281 removing ba
279 removing d11/a1
282 removing d11/a1
280 A d3/a
283 A d3/a
281 d1/a
284 d1/a
282 A d3/b
285 A d3/b
283 d1/b
286 d1/b
284 A d3/ba
287 A d3/ba
285 d1/ba
288 d1/ba
286 A d3/d11/a1
289 A d3/d11/a1
287 d1/d11/a1
290 d1/d11/a1
288 R d1/a
291 R d1/a
289 R d1/b
292 R d1/b
290 R d1/ba
293 R d1/ba
291 R d1/d11/a1
294 R d1/d11/a1
292 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
295 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
293 # move the parent tree with "hg rename .."
296 # move the parent tree with "hg rename .."
294 copying ../a to ../../d3/a
297 copying ../a to ../../d3/a
295 copying ../b to ../../d3/b
298 copying ../b to ../../d3/b
296 copying ../ba to ../../d3/ba
299 copying ../ba to ../../d3/ba
297 copying a1 to ../../d3/d11/a1
300 copying a1 to ../../d3/d11/a1
298 removing ../a
301 removing ../a
299 removing ../b
302 removing ../b
300 removing ../ba
303 removing ../ba
301 removing a1
304 removing a1
302 A d3/a
305 A d3/a
303 d1/a
306 d1/a
304 A d3/b
307 A d3/b
305 d1/b
308 d1/b
306 A d3/ba
309 A d3/ba
307 d1/ba
310 d1/ba
308 A d3/d11/a1
311 A d3/d11/a1
309 d1/d11/a1
312 d1/d11/a1
310 R d1/a
313 R d1/a
311 R d1/b
314 R d1/b
312 R d1/ba
315 R d1/ba
313 R d1/d11/a1
316 R d1/d11/a1
314 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
317 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
315 # skip removed files
318 # skip removed files
316 copying d1/a to d3/a
319 copying d1/a to d3/a
317 copying d1/ba to d3/ba
320 copying d1/ba to d3/ba
318 copying d1/d11/a1 to d3/d11/a1
321 copying d1/d11/a1 to d3/d11/a1
319 removing d1/a
322 removing d1/a
320 removing d1/ba
323 removing d1/ba
321 removing d1/d11/a1
324 removing d1/d11/a1
322 A d3/a
325 A d3/a
323 d1/a
326 d1/a
324 A d3/ba
327 A d3/ba
325 d1/ba
328 d1/ba
326 A d3/d11/a1
329 A d3/d11/a1
327 d1/d11/a1
330 d1/d11/a1
328 R d1/a
331 R d1/a
329 R d1/b
332 R d1/b
330 R d1/ba
333 R d1/ba
331 R d1/d11/a1
334 R d1/d11/a1
332 4 files updated, 0 files merged, 3 files removed, 0 files unresolved
335 4 files updated, 0 files merged, 3 files removed, 0 files unresolved
333 # transitive rename
336 # transitive rename
334 A d1/bc
337 A d1/bc
335 d1/b
338 d1/b
336 R d1/b
339 R d1/b
337 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
340 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
338 # transitive rename --after
341 # transitive rename --after
339 A d1/bc
342 A d1/bc
340 d1/b
343 d1/b
341 R d1/b
344 R d1/b
342 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
345 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
343 # idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)
346 # idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)
344 M d1/b
347 M d1/b
345 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
348 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
346 # check illegal path components
349 # check illegal path components
347 abort: path contains illegal component: .hg/foo
350 abort: path contains illegal component: .hg/foo
348 abort: ../foo not under root
351 abort: ../foo not under root
349 abort: path contains illegal component: .hg/foo
352 abort: path contains illegal component: .hg/foo
350 ! d1/d11/a1
353 ! d1/d11/a1
351 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
354 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
352 abort: path contains illegal component: .hg/a1
355 abort: path contains illegal component: .hg/a1
353 abort: ../a1 not under root
356 abort: ../a1 not under root
354 abort: path contains illegal component: .hg/a1
357 abort: path contains illegal component: .hg/a1
355 ! d1/d11/a1
358 ! d1/d11/a1
356 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
359 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
357 abort: path contains illegal component: .hg/foo
360 abort: path contains illegal component: .hg/foo
358 abort: ../../../foo not under root
361 abort: ../../../foo not under root
General Comments 0
You need to be logged in to leave comments. Login now