# HG changeset patch # User Vadim Gelfer # Date 1154713097 25200 # Node ID 0148869345f7ffb64693148e47af69bffc32b1c2 # Parent a9fadbef21e9287c1668ef2894600781bda75bbe# Parent ae726521717c4f4333c5562995e064e1b4230a52 merge with self. diff --git a/hgext/mq.py b/hgext/mq.py --- a/hgext/mq.py +++ b/hgext/mq.py @@ -39,6 +39,16 @@ versionstr = "0.45" commands.norepo += " qclone qversion" +class StatusEntry: + def __init__(self, rev, name=None): + if not name: + self.rev, self.name = rev.split(':') + else: + self.rev, self.name = rev, name + + def __str__(self): + return self.rev + ':' + self.name + class queue: def __init__(self, ui, path, patchdir=None): self.basepath = path @@ -57,10 +67,11 @@ class queue: if os.path.exists(os.path.join(self.path, self.series_path)): self.full_series = self.opener(self.series_path).read().splitlines() - self.read_series(self.full_series) + self.parse_series() if os.path.exists(os.path.join(self.path, self.status_path)): - self.applied = self.opener(self.status_path).read().splitlines() + self.applied = [StatusEntry(l) + for l in self.opener(self.status_path).read().splitlines()] def find_series(self, patch): pre = re.compile("(\s*)([^#]+)") @@ -75,34 +86,21 @@ class queue: index += 1 return None - def read_series(self, list): - def matcher(list): - pre = re.compile("(\s*)([^#]+)") - for l in list: - m = pre.match(l) - if m: - s = m.group(2) - s = s.rstrip() - if len(s) > 0: - yield s + def parse_series(self): self.series = [] - self.series = [ x for x in matcher(list) ] + for l in self.full_series: + s = l.split('#', 1)[0].strip() + if s: + self.series.append(s) def save_dirty(self): - if self.applied_dirty: - if len(self.applied) > 0: - nl = "\n" - else: - nl = "" - f = self.opener(self.status_path, "w") - f.write("\n".join(self.applied) + nl) - if self.series_dirty: - if len(self.full_series) > 0: - nl = "\n" - else: - nl = "" - f = self.opener(self.series_path, "w") - f.write("\n".join(self.full_series) + nl) + def write_list(items, path): + fp = self.opener(path, 'w') + for i in items: + print >> fp, i + fp.close() + if self.applied_dirty: write_list(map(str, self.applied), self.status_path) + if self.series_dirty: write_list(self.full_series, self.series_path) def readheaders(self, patch): def eatdiff(lines): @@ -222,12 +220,10 @@ class queue: return p1 if len(self.applied) == 0: return None - (top, patch) = self.applied[-1].split(':') - top = revlog.bin(top) - return top + return revlog.bin(self.applied[-1].rev) pp = repo.changelog.parents(rev) if pp[1] != revlog.nullid: - arevs = [ x.split(':')[0] for x in self.applied ] + arevs = [ x.rev for x in self.applied ] p0 = revlog.hex(pp[0]) p1 = revlog.hex(pp[1]) if p0 in arevs: @@ -247,7 +243,7 @@ class queue: pname = ".hg.patches.merge.marker" n = repo.commit(None, '[mq]: merge marker', user=None, force=1, wlock=wlock) - self.applied.append(revlog.hex(n) + ":" + pname) + self.applied.append(StatusEntry(revlog.hex(n), pname)) self.applied_dirty = 1 head = self.qparents(repo) @@ -265,7 +261,7 @@ class queue: rev = revlog.bin(info[1]) (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock) if head: - self.applied.append(revlog.hex(head) + ":" + patch) + self.applied.append(StatusEntry(revlog.hex(head), patch)) self.applied_dirty = 1 if err: return (err, head) @@ -364,7 +360,7 @@ class queue: raise util.Abort(_("repo commit failed")) if update_status: - self.applied.append(revlog.hex(n) + ":" + patch) + self.applied.append(StatusEntry(revlog.hex(n), patch)) if patcherr: if not patchfound: @@ -397,13 +393,12 @@ class queue: os.unlink(os.path.join(self.path, patch)) i = self.find_series(patch) del self.full_series[i] - self.read_series(self.full_series) + self.parse_series() self.series_dirty = 1 def check_toppatch(self, repo): if len(self.applied) > 0: - (top, patch) = self.applied[-1].split(':') - top = revlog.bin(top) + top = revlog.bin(self.applied[-1].rev) pp = repo.dirstate.parents() if top not in pp: raise util.Abort(_("queue top not at same revision as working directory")) @@ -434,8 +429,8 @@ class queue: if n == None: raise util.Abort(_("repo commit failed")) self.full_series[insert:insert] = [patch] - self.applied.append(revlog.hex(n) + ":" + patch) - self.read_series(self.full_series) + self.applied.append(StatusEntry(revlog.hex(n), patch)) + self.parse_series() self.series_dirty = 1 self.applied_dirty = 1 p = self.opener(patch, "w") @@ -600,10 +595,9 @@ class queue: def isapplied(self, patch): """returns (index, rev, patch)""" for i in xrange(len(self.applied)): - p = self.applied[i] - a = p.split(':') - if a[1] == patch: - return (i, a[0], a[1]) + a = self.applied[i] + if a.name == patch: + return (i, a.rev, a.name) return None # if the exact patch name does not exist, we try a few @@ -706,7 +700,7 @@ class queue: ret = self.mergepatch(repo, mergeq, s, wlock) else: ret = self.apply(repo, s, list, wlock=wlock) - top = self.applied[-1].split(':')[1] + top = self.applied[-1].name if ret[0]: self.ui.write("Errors during apply, please fix and refresh %s\n" % top) @@ -743,7 +737,7 @@ class queue: if not update: parents = repo.dirstate.parents() - rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ] + rr = [ revlog.bin(x.rev) for x in self.applied ] for p in parents: if p in rr: self.ui.warn("qpop: forcing dirstate update\n") @@ -764,7 +758,7 @@ class queue: if popi >= end: self.ui.warn("qpop: %s is already at the top\n" % patch) return - info = [ popi ] + self.applied[popi].split(':') + info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name] start = info[0] rev = revlog.bin(info[1]) @@ -797,7 +791,7 @@ class queue: self.strip(repo, rev, update=False, backup='strip', wlock=wlock) del self.applied[start:end] if len(self.applied): - self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1]) + self.ui.write("Now at: %s\n" % self.applied[-1].name) else: self.ui.write("Patch queue now empty\n") @@ -816,7 +810,7 @@ class queue: wlock = repo.wlock() self.check_toppatch(repo) qp = self.qparents(repo) - (top, patch) = self.applied[-1].split(':') + (top, patch) = (self.applied[-1].rev, self.applied[-1].name) top = revlog.bin(top) cparents = repo.changelog.parents(top) patchparent = self.qparents(repo, top) @@ -912,7 +906,7 @@ class queue: self.strip(repo, top, update=False, backup='strip', wlock=wlock) n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock) - self.applied[-1] = revlog.hex(n) + ':' + patch + self.applied[-1] = StatusEntry(revlog.hex(n), patch) self.applied_dirty = 1 else: commands.dodiff(patchf, self.ui, repo, patchparent, None) @@ -934,10 +928,7 @@ class queue: start = self.series_end() else: start = self.series.index(patch) + 1 - for p in self.series[start:]: - if self.ui.verbose: - self.ui.write("%d " % self.series.index(p)) - self.ui.write("%s\n" % p) + return [(i, self.series[i]) for i in xrange(start, len(self.series))] def qseries(self, repo, missing=None, summary=False): start = self.series_end() @@ -1000,11 +991,11 @@ class queue: qpp = [ hg.bin(x) for x in l ] elif datastart != None: l = lines[i].rstrip() - index = l.index(':') - id = l[:index] - file = l[index + 1:] + se = StatusEntry(l) + id = se.rev + file = se.name if id: - applied.append(l) + applied.append(se) series.append(file) if datastart == None: self.ui.warn("No saved patch data found\n") @@ -1012,7 +1003,7 @@ class queue: self.ui.warn("restoring status: %s\n" % lines[0]) self.full_series = series self.applied = applied - self.read_series(self.full_series) + self.parse_series() self.series_dirty = 1 self.applied_dirty = 1 heads = repo.changelog.heads() @@ -1056,18 +1047,18 @@ class queue: pp = r.dirstate.parents() msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1])) msg += "\n\nPatch Data:\n" - text = msg + "\n".join(self.applied) + '\n' + (ar and "\n".join(ar) + text = msg + "\n".join(str(self.applied)) + '\n' + (ar and "\n".join(ar) + '\n' or "") n = repo.commit(None, text, user=None, force=1) if not n: self.ui.warn("repo commit failed\n") return 1 - self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line') + self.applied.append(StatusEntry(revlog.hex(n),'.hg.patches.save.line')) self.applied_dirty = 1 def full_series_end(self): if len(self.applied) > 0: - (top, p) = self.applied[-1].split(':') + p = self.applied[-1].name end = self.find_series(p) if end == None: return len(self.full_series) @@ -1077,7 +1068,7 @@ class queue: def series_end(self): end = 0 if len(self.applied) > 0: - (top, p) = self.applied[-1].split(':') + p = self.applied[-1].name try: end = self.series.index(p) except ValueError: @@ -1097,8 +1088,7 @@ class queue: self.ui.write("%s\n" % p) def appliedname(self, index): - p = self.applied[index] - pname = p.split(':')[1] + pname = self.applied[index].name if not self.ui.verbose: p = pname else: @@ -1159,7 +1149,7 @@ class queue: % patch) index = self.full_series_end() + i self.full_series[index:index] = [patch] - self.read_series(self.full_series) + self.parse_series() self.ui.warn("adding %s to series file\n" % patch) i += 1 added.append(patch) @@ -1186,8 +1176,10 @@ def applied(ui, repo, patch=None, **opts def unapplied(ui, repo, patch=None, **opts): """print the patches not yet applied""" - repo.mq.unapplied(repo, patch) - return 0 + for i, p in repo.mq.unapplied(repo, patch): + if ui.verbose: + ui.write("%d " % i) + ui.write("%s\n" % p) def qimport(ui, repo, *filename, **opts): """import a patch""" @@ -1228,7 +1220,7 @@ def clone(ui, source, dest=None, **opts) Source patch repository is looked for in /.hg/patches by default. Use -p to change. ''' - commands.setremoteconfig(**opts) + commands.setremoteconfig(ui, opts) if dest is None: dest = hg.defaultdest(source) sr = hg.repository(ui, ui.expandpath(source)) @@ -1236,7 +1228,7 @@ def clone(ui, source, dest=None, **opts) if sr.local(): reposetup(ui, sr) if sr.mq.applied: - qbase = revlog.bin(sr.mq.applied[0].split(':')[0]) + qbase = revlog.bin(sr.mq.applied[0].rev) if not hg.islocal(dest): destrev = sr.parents(qbase)[0] ui.note(_('cloning main repo\n')) @@ -1297,10 +1289,7 @@ def new(ui, repo, patch, **opts): -m or -l set the patch header as well as the commit message. If neither is specified, the patch header is empty and the - commit message is 'New patch: PATCH' - - If -f is specified, the patch will be initialized with any - uncommitted changes. Otherwise, if there outsta""" + commit message is 'New patch: PATCH'""" q = repo.mq message=commands.logmessage(**opts) q.new(repo, patch, msg=message, force=opts['force']) @@ -1314,7 +1303,7 @@ def refresh(ui, repo, **opts): if opts['edit']: if message: raise util.Abort(_('option "-e" incompatible with "-m" or "-l"')) - patch = q.applied[-1].split(':')[1] + patch = q.applied[-1].name (message, comment, user, date, hasdiff) = q.readheaders(patch) message = ui.edit('\n'.join(message), user or ui.username()) q.refresh(repo, msg=message, short=opts['short']) @@ -1330,7 +1319,13 @@ def diff(ui, repo, *files, **opts): def fold(ui, repo, *files, **opts): """fold the named patches into the current patch - Patches must not yet be applied. + Patches must not yet be applied. Each patch will be successively + applied to the current patch in the order given. If all the + patches apply successfully, the current patch will be refreshed + with the new cumulative patch, and the folded patches will + be deleted. With -f/--force, the folded patch files will + be removed afterwards. + The header for each folded patch will be concatenated with the current patch header, separated by a line of '* * *'.""" @@ -1378,7 +1373,7 @@ def fold(ui, repo, *files, **opts): q.refresh(repo, msg=message) for patch in patches: - q.delete(repo, patch) + q.delete(repo, patch, force=opts['force']) q.save_dirty() @@ -1487,12 +1482,12 @@ def rename(ui, repo, patch, name=None, * ui.write('Renaming %s to %s\n' % (patch, name)) i = q.find_series(patch) q.full_series[i] = name - q.read_series(q.full_series) + q.parse_series() q.series_dirty = 1 info = q.isapplied(patch) if info: - q.applied[info[0]] = info[1] + ':' + name + q.applied[info[0]] = StatusEntry(info[1], name) q.applied_dirty = 1 util.rename(os.path.join(q.path, patch), absdest) @@ -1573,7 +1568,7 @@ def reposetup(ui, repo): if not q.applied: return tagscache - mqtags = [patch.split(':') for patch in q.applied] + mqtags = [(patch.rev, patch.name) for patch in q.applied] mqtags.append((mqtags[-1][0], 'qtip')) mqtags.append((mqtags[0][0], 'qbase')) for patch in mqtags: @@ -1611,6 +1606,7 @@ cmdtable = { 'qfold': (fold, [('e', 'edit', None, _('edit patch header')), + ('f', 'force', None, _('delete folded patch files')), ('m', 'message', '', _('set patch header to ')), ('l', 'logfile', '', _('set patch header to contents of '))], 'hg qfold [-e] [-m ] [-l a/a +echo line 1 > a/d1/d2/a hg --cwd a ci -d '0 0' -Ama echo line 2 >> a/a @@ -79,3 +82,19 @@ python mkmsg.py | hg --cwd b import - hg --cwd b tip | grep second rm -rf b +# bug non regression test +# importing a patch in a subdirectory failed at the commit stage +echo line 2 >> a/d1/d2/a +hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change' +echo % hg import in a subdirectory +hg clone -r0 a b +hg --cwd a export tip | sed -e 's/d1\/d2\///' > tip.patch +pushd b/d1/d2 2>&1 > /dev/null +hg import ../../../tip.patch +popd 2>&1 > /dev/null +echo "% message should be 'subdir change'" +hg --cwd b tip | grep 'subdir change' +echo "% committer should be 'someoneelse'" +hg --cwd b tip | grep someoneelse +echo "% should be empty" +hg --cwd b status diff --git a/tests/test-import.out b/tests/test-import.out --- a/tests/test-import.out +++ b/tests/test-import.out @@ -1,11 +1,12 @@ adding a +adding d1/d2/a % import exported patch requesting all changes adding changesets adding manifests adding file changes -added 1 changesets with 1 changes to 1 files -1 files updated, 0 files merged, 0 files removed, 0 files unresolved +added 1 changesets with 2 changes to 2 files +2 files updated, 0 files merged, 0 files removed, 0 files unresolved applying ../tip.patch patching file a % message should be same @@ -17,8 +18,8 @@ requesting all changes adding changesets adding manifests adding file changes -added 1 changesets with 1 changes to 1 files -1 files updated, 0 files merged, 0 files removed, 0 files unresolved +added 1 changesets with 2 changes to 2 files +2 files updated, 0 files merged, 0 files removed, 0 files unresolved applying ../tip.patch patching file a transaction abort! @@ -28,8 +29,8 @@ requesting all changes adding changesets adding manifests adding file changes -added 1 changesets with 1 changes to 1 files -1 files updated, 0 files merged, 0 files removed, 0 files unresolved +added 1 changesets with 2 changes to 2 files +2 files updated, 0 files merged, 0 files removed, 0 files unresolved applying ../tip.patch patching file a % import from stdin @@ -37,8 +38,8 @@ requesting all changes adding changesets adding manifests adding file changes -added 1 changesets with 1 changes to 1 files -1 files updated, 0 files merged, 0 files removed, 0 files unresolved +added 1 changesets with 2 changes to 2 files +2 files updated, 0 files merged, 0 files removed, 0 files unresolved applying patch from stdin patching file a % override commit message @@ -46,8 +47,8 @@ requesting all changes adding changesets adding manifests adding file changes -added 1 changesets with 1 changes to 1 files -1 files updated, 0 files merged, 0 files removed, 0 files unresolved +added 1 changesets with 2 changes to 2 files +2 files updated, 0 files merged, 0 files removed, 0 files unresolved applying patch from stdin patching file a summary: override @@ -56,8 +57,8 @@ requesting all changes adding changesets adding manifests adding file changes -added 1 changesets with 1 changes to 1 files -1 files updated, 0 files merged, 0 files removed, 0 files unresolved +added 1 changesets with 2 changes to 2 files +2 files updated, 0 files merged, 0 files removed, 0 files unresolved applying ../msg.patch patching file a user: email patcher @@ -67,8 +68,8 @@ requesting all changes adding changesets adding manifests adding file changes -added 1 changesets with 1 changes to 1 files -1 files updated, 0 files merged, 0 files removed, 0 files unresolved +added 1 changesets with 2 changes to 2 files +2 files updated, 0 files merged, 0 files removed, 0 files unresolved applying patch from stdin patching file a % plain diff in email, subject, no message body @@ -76,8 +77,8 @@ requesting all changes adding changesets adding manifests adding file changes -added 1 changesets with 1 changes to 1 files -1 files updated, 0 files merged, 0 files removed, 0 files unresolved +added 1 changesets with 2 changes to 2 files +2 files updated, 0 files merged, 0 files removed, 0 files unresolved applying patch from stdin patching file a % plain diff in email, no subject, no message body, should fail @@ -85,8 +86,8 @@ requesting all changes adding changesets adding manifests adding file changes -added 1 changesets with 1 changes to 1 files -1 files updated, 0 files merged, 0 files removed, 0 files unresolved +added 1 changesets with 2 changes to 2 files +2 files updated, 0 files merged, 0 files removed, 0 files unresolved applying patch from stdin patching file a transaction abort! @@ -96,8 +97,22 @@ requesting all changes adding changesets adding manifests adding file changes -added 1 changesets with 1 changes to 1 files -1 files updated, 0 files merged, 0 files removed, 0 files unresolved +added 1 changesets with 2 changes to 2 files +2 files updated, 0 files merged, 0 files removed, 0 files unresolved applying patch from stdin patching file a summary: second change +% hg import in a subdirectory +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 2 changes to 2 files +2 files updated, 0 files merged, 0 files removed, 0 files unresolved +applying ../../../tip.patch +patching file a +% message should be 'subdir change' +summary: subdir change +% committer should be 'someoneelse' +user: someoneelse +% should be empty