Mercurial > public > mercurial-scm > hg
diff hgext/mq.py @ 6042:2da5b19a6460
Merge with crew
author | Bryan O'Sullivan <bos@serpentine.com> |
---|---|
date | Wed, 06 Feb 2008 19:57:52 -0800 |
parents | dd714452c26e 30d2fecaab76 |
children | 6283316bcfd4 |
line wrap: on
line diff
--- a/hgext/mq.py Thu Jul 26 07:56:27 2007 -0400 +++ b/hgext/mq.py Wed Feb 06 19:57:52 2008 -0800 @@ -34,7 +34,7 @@ from mercurial import repair import os, sys, re, errno -commands.norepo += " qclone qversion" +commands.norepo += " qclone" # Patch names looks like unix-file names. # They must be joinable with queue directory and result in the patch path. @@ -224,7 +224,7 @@ def write_list(items, path): fp = self.opener(path, 'w') for i in items: - print >> fp, i + fp.write("%s\n" % 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) @@ -455,7 +455,8 @@ repo.dirstate.invalidate() raise finally: - del lock, wlock, tr + del tr, lock, wlock + self.removeundo(repo) def _apply(self, repo, series, list=False, update_status=True, strict=False, patchdir=None, merge=None, all_files={}): @@ -527,7 +528,6 @@ self.ui.warn("fuzz found when applying patch, stopping\n") err = 1 break - self.removeundo(repo) return (err, n) def delete(self, repo, patches, opts): @@ -587,7 +587,7 @@ 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")) + raise util.Abort(_("working directory revision is not qtip")) return top return None def check_localchanges(self, repo, force=False, refresh=True): @@ -600,9 +600,19 @@ raise util.Abort(_("local changes found")) return m, a, r, d + _reserved = ('series', 'status', 'guards') + def check_reserved_name(self, name): + if (name in self._reserved or name.startswith('.hg') + or name.startswith('.mq')): + raise util.Abort(_('"%s" cannot be used as the name of a patch') + % name) + def new(self, repo, patch, *pats, **opts): msg = opts.get('msg') force = opts.get('force') + user = opts.get('user') + date = opts.get('date') + self.check_reserved_name(patch) if os.path.exists(self.join(patch)): raise util.Abort(_('patch "%s" already exists') % patch) if opts.get('include') or opts.get('exclude') or pats: @@ -610,15 +620,14 @@ m, a, r, d = repo.status(files=fns, match=match)[:4] else: m, a, r, d = self.check_localchanges(repo, force) + fns, match, anypats = cmdutil.matchpats(repo, m + a + r) commitfiles = m + a + r self.check_toppatch(repo) wlock = repo.wlock() try: insert = self.full_series_end() - if msg: - n = repo.commit(commitfiles, msg, force=True) - else: - n = repo.commit(commitfiles, "[mq]: %s" % patch, force=True) + commitmsg = msg and msg or ("[mq]: %s" % patch) + n = repo.commit(commitfiles, commitmsg, user, date, match=match, force=True) if n == None: raise util.Abort(_("repo commit failed")) self.full_series[insert:insert] = [patch] @@ -627,6 +636,15 @@ self.series_dirty = 1 self.applied_dirty = 1 p = self.opener(patch, "w") + if date: + p.write("# HG changeset patch\n") + if user: + p.write("# User " + user + "\n") + p.write("# Date " + date + "\n") + p.write("\n") + elif user: + p.write("From: " + user + "\n") + p.write("\n") if msg: msg = msg + "\n" p.write(msg) @@ -635,7 +653,7 @@ r = self.qrepo() if r: r.add([patch]) if commitfiles: - self.refresh(repo, short=True) + self.refresh(repo, short=True, git=opts.get('git')) self.removeundo(repo) finally: del wlock @@ -654,6 +672,9 @@ self.removeundo(repo) repair.strip(self.ui, repo, rev, backup) + # strip may have unbundled a set of backed up revisions after + # the actual strip + self.removeundo(repo) finally: del lock, wlock @@ -810,9 +831,9 @@ del wlock def pop(self, repo, patch=None, force=False, update=True, all=False): - def getfile(f, rev): + def getfile(f, rev, flags): t = repo.file(f).read(rev) - repo.wfile(f, "w").write(t) + repo.wwrite(f, t, flags) wlock = repo.wlock() try: @@ -859,10 +880,16 @@ start = info[0] rev = revlog.bin(info[1]) + if update: + top = self.check_toppatch(repo) + + if repo.changelog.heads(rev) != [revlog.bin(self.applied[-1].rev)]: + raise util.Abort("popping would remove a revision not " + "managed by this patch queue") + # we know there are no local changes, so we can make a simplified # form of hg.update. if update: - top = self.check_toppatch(repo) qp = self.qparents(repo, rev) changes = repo.changelog.read(qp) mmap = repo.manifest.read(changes[0]) @@ -870,10 +897,9 @@ if d: raise util.Abort("deletions found between repo revs") for f in m: - getfile(f, mmap[f]) + getfile(f, mmap[f], mmap.flags(f)) for f in r: - getfile(f, mmap[f]) - util.set_exec(repo.wjoin(f), mmap.execf(f)) + getfile(f, mmap[f], mmap.flags(f)) for f in m + r: repo.dirstate.normal(f) for f in a: @@ -886,8 +912,8 @@ except: pass repo.dirstate.forget(f) repo.dirstate.setparents(qp, revlog.nullid) + del self.applied[start:end] self.strip(repo, rev, update=False, backup='strip') - del self.applied[start:end] if len(self.applied): self.ui.write("Now at: %s\n" % self.applied[-1].name) else: @@ -914,6 +940,8 @@ self.check_toppatch(repo) (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name) top = revlog.bin(top) + if repo.changelog.heads(top) != [top]: + raise util.Abort("cannot refresh a revision with children") cparents = repo.changelog.parents(top) patchparent = self.qparents(repo, top) message, comments, user, date, patchfound = self.readheaders(patchfn) @@ -925,22 +953,59 @@ if line.startswith('diff --git'): self.diffopts().git = True break + + msg = opts.get('msg', '').rstrip() + if msg and comments: + # Remove existing message, keeping the rest of the comments + # fields. + # If comments contains 'subject: ', message will prepend + # the field and a blank line. + if message: + subj = 'subject: ' + message[0].lower() + for i in xrange(len(comments)): + if subj == comments[i].lower(): + del comments[i] + message = message[2:] + break + ci = 0 + for mi in xrange(len(message)): + while message[mi] != comments[ci]: + ci += 1 + del comments[ci] + + def setheaderfield(comments, prefixes, new): + # Update all references to a field in the patch header. + # If none found, add it email style. + res = False + for prefix in prefixes: + for i in xrange(len(comments)): + if comments[i].startswith(prefix): + comments[i] = prefix + new + res = True + break + return res + + newuser = opts.get('user') + if newuser: + if not setheaderfield(comments, ['From: ', '# User '], newuser): + try: + patchheaderat = comments.index('# HG changeset patch') + comments.insert(patchheaderat + 1,'# User ' + newuser) + except ValueError: + comments = ['From: ' + newuser, ''] + comments + user = newuser + + newdate = opts.get('date') + if newdate: + if setheaderfield(comments, ['# Date '], newdate): + date = newdate + + if msg: + comments.append(msg) + patchf.seek(0) patchf.truncate() - msg = opts.get('msg', '').rstrip() - if msg: - if comments: - # Remove existing message. - ci = 0 - subj = None - for mi in xrange(len(message)): - if comments[ci].lower().startswith('subject: '): - subj = comments[ci][9:] - while message[mi] != comments[ci] and message[mi] != subj: - ci += 1 - del comments[ci] - comments.append(msg) if comments: comments = "\n".join(comments) + '\n\n' patchf.write(comments) @@ -1017,9 +1082,8 @@ copies = {} for dst in a: src = repo.dirstate.copied(dst) - if src is None: - continue - copies.setdefault(src, []).append(dst) + if src is not None: + copies.setdefault(src, []).append(dst) repo.dirstate.add(dst) # remember the copies between patchparent and tip # this may be slow, so don't do it if we're not tracking copies @@ -1049,7 +1113,7 @@ for f in m: repo.dirstate.normal(f) for f in mm: - repo.dirstate.normaldirty(f) + repo.dirstate.normallookup(f) for f in forget: repo.dirstate.forget(f) @@ -1061,12 +1125,16 @@ else: message = msg + if not user: + user = changes[1] + + self.applied.pop() + self.applied_dirty = 1 self.strip(repo, top, update=False, backup='strip') - n = repo.commit(filelist, message, changes[1], match=matchfn, + n = repo.commit(filelist, message, user, date, match=matchfn, force=1) - self.applied[-1] = statusentry(revlog.hex(n), patchfn) - self.applied_dirty = 1 + self.applied.append(statusentry(revlog.hex(n), patchfn)) self.removeundo(repo) else: self.printdiff(repo, patchparent, fp=patchf) @@ -1216,7 +1284,7 @@ self.ui.warn("saved queue repository parents: %s %s\n" % (hg.short(qpp[0]), hg.short(qpp[1]))) if qupdate: - print "queue directory updating" + self.ui.status(_("queue directory updating\n")) r = self.qrepo() if not r: self.ui.warn("Unable to load queue repository\n") @@ -1355,6 +1423,7 @@ if not patchname: patchname = normname('%d.diff' % r) + self.check_reserved_name(patchname) checkseries(patchname) checkfile(patchname) self.full_series.insert(0, patchname) @@ -1377,6 +1446,7 @@ raise util.Abort(_('-e is incompatible with import from -')) if not patchname: patchname = normname(filename) + self.check_reserved_name(patchname) if not os.path.isfile(self.join(patchname)): raise util.Abort(_("patch %s does not exist") % patchname) else: @@ -1391,6 +1461,7 @@ raise util.Abort(_("unable to read %s") % patchname) if not patchname: patchname = normname(os.path.basename(filename)) + self.check_reserved_name(patchname) checkfile(patchname) patchf = self.opener(patchname, "w") patchf.write(text) @@ -1508,13 +1579,18 @@ The patch directory must be a nested mercurial repository, as would be created by qinit -c. ''' + def patchdir(repo): + url = repo.url() + if url.endswith('/'): + url = url[:-1] + return url + '/.hg/patches' cmdutil.setremoteconfig(ui, opts) if dest is None: dest = hg.defaultdest(source) sr = hg.repository(ui, ui.expandpath(source)) - patchdir = opts['patches'] or (sr.url() + '/.hg/patches') + patchespath = opts['patches'] or patchdir(sr) try: - pr = hg.repository(ui, patchdir) + pr = hg.repository(ui, patchespath) except hg.RepoError: raise util.Abort(_('versioned patch repository not found' ' (see qinit -c)')) @@ -1535,10 +1611,8 @@ update=False, stream=opts['uncompressed']) ui.note(_('cloning patch repo\n')) - spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'), - dr.url() + '/.hg/patches', - pull=opts['pull'], - update=not opts['noupdate'], + spr, dpr = hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr), + pull=opts['pull'], update=not opts['noupdate'], stream=opts['uncompressed']) if dr.local(): if qbase: @@ -1593,6 +1667,13 @@ return q.qseries(repo, start=l-2, length=1, status='A', summary=opts.get('summary')) +def setupheaderopts(ui, opts): + def do(opt,val): + if not opts[opt] and opts['current' + opt]: + opts[opt] = val + do('user', ui.username()) + do('date', "%d %d" % util.makedate()) + def new(ui, repo, patch, *args, **opts): """create a new patch @@ -1611,6 +1692,7 @@ if opts['edit']: message = ui.edit(message, ui.username()) opts['msg'] = message + setupheaderopts(ui, opts) q.new(repo, patch, *args, **opts) q.save_dirty() return 0 @@ -1628,11 +1710,15 @@ q = repo.mq message = cmdutil.logmessage(opts) if opts['edit']: + if not q.applied: + ui.write(_("No patches applied\n")) + return 1 if message: raise util.Abort(_('option "-e" incompatible with "-m" or "-l"')) patch = q.applied[-1].name (message, comment, user, date, hasdiff) = q.readheaders(patch) message = ui.edit('\n'.join(message), user or ui.username()) + setupheaderopts(ui, opts) ret = q.refresh(repo, pats, msg=message, **opts) q.save_dirty() return ret @@ -2081,6 +2167,12 @@ return tagscache mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied] + + if mqtags[-1][0] not in self.changelog.nodemap: + self.ui.warn('mq status file refers to unknown node %s\n' + % revlog.short(mqtags[-1][0])) + return tagscache + mqtags.append((mqtags[-1][0], 'qtip')) mqtags.append((mqtags[0][0], 'qbase')) mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent')) @@ -2097,11 +2189,17 @@ if not q.applied: return super(mqrepo, self)._branchtags() + cl = self.changelog + qbasenode = revlog.bin(q.applied[0].rev) + if qbasenode not in cl.nodemap: + self.ui.warn('mq status file refers to unknown node %s\n' + % revlog.short(qbasenode)) + return super(mqrepo, self)._branchtags() + self.branchcache = {} # avoid recursion in changectx - cl = self.changelog partial, last, lrev = self._readbranchcache() - qbase = cl.rev(revlog.bin(q.applied[0].rev)) + qbase = cl.rev(qbasenode) start = lrev + 1 if start < qbase: # update the cache (excluding the patches) and save it @@ -2123,6 +2221,12 @@ seriesopts = [('s', 'summary', None, _('print first line of patch header'))] +headeropts = [ + ('U', 'currentuser', None, _('add "From: <current user>" to patch')), + ('u', 'user', '', _('add "From: <given user>" to patch')), + ('D', 'currentdate', None, _('add "Date: <current date>" to patch')), + ('d', 'date', '', _('add "Date: <given date>" to patch'))] + cmdtable = { "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')), "qclone": @@ -2131,10 +2235,8 @@ ('U', 'noupdate', None, _('do not update the new working directories')), ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')), - ('e', 'ssh', '', _('specify ssh command to use')), ('p', 'patches', '', _('location of source patch repo')), - ('', 'remotecmd', '', - _('specify hg command to run on the remote side'))], + ] + commands.remoteopts, _('hg qclone [OPTION]... SOURCE [DEST]')), "qcommit|qci": (commit, @@ -2143,9 +2245,8 @@ "^qdiff": (diff, [('g', 'git', None, _('use git extended diff format')), - ('I', 'include', [], _('include names matching the given patterns')), - ('X', 'exclude', [], _('exclude names matching the given patterns')), - ('U', 'unified', 3, _('number of lines of context to show'))], + ('U', 'unified', 3, _('number of lines of context to show')), + ] + commands.walkopts, _('hg qdiff [-I] [-X] [-U NUM] [-g] [FILE]...')), "qdelete|qremove|qrm": (delete, @@ -2184,9 +2285,8 @@ (new, [('e', 'edit', None, _('edit commit message')), ('f', 'force', None, _('import uncommitted changes into patch')), - ('I', 'include', [], _('include names matching the given patterns')), - ('X', 'exclude', [], _('exclude names matching the given patterns')), - ] + commands.commitopts, + ('g', 'git', None, _('use git extended diff format')), + ] + commands.walkopts + commands.commitopts + headeropts, _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')), "qnext": (next, [] + seriesopts, _('hg qnext [-s]')), "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')), @@ -2209,9 +2309,7 @@ [('e', 'edit', None, _('edit commit message')), ('g', 'git', None, _('use git extended diff format')), ('s', 'short', None, _('refresh only files already in the patch')), - ('I', 'include', [], _('include names matching the given patterns')), - ('X', 'exclude', [], _('exclude names matching the given patterns')), - ] + commands.commitopts, + ] + commands.walkopts + commands.commitopts + headeropts, _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')), 'qrename|qmv': (rename, [], _('hg qrename PATCH1 [PATCH2]')),