def dohgdiff(): difftext = StringIO.StringIO() try: if len(files) != 0: wfiles = [self.repo.wjoin(x) for x in files] fns, matchfn, anypats = cmdutil.matchpats(self.repo, wfiles, self.opts) patch.diff(self.repo, self._node1, self._node2, fns, match=matchfn, fp=difftext, opts=patch.diffopts(self.ui, self.opts)) buffer = gtk.TextBuffer() buffer.create_tag('removed', foreground='#900000') buffer.create_tag('added', foreground='#006400') buffer.create_tag('position', foreground='#FF8000') buffer.create_tag('header', foreground='#000090') difftext.seek(0) iter = buffer.get_start_iter() for line in difftext: line = toutf(line) if line.startswith('---') or line.startswith('+++'): buffer.insert_with_tags_by_name(iter, line, 'header') elif line.startswith('-'): buffer.insert_with_tags_by_name(iter, line, 'removed') elif line.startswith('+'): buffer.insert_with_tags_by_name(iter, line, 'added') elif line.startswith('@@'): buffer.insert_with_tags_by_name(iter, line, 'position') else: buffer.insert(iter, line) self.diff_text.set_buffer(buffer) finally: difftext.close()
def generate_text_diffs(self, row): wfile = self.filemodel[row][FM_PATH] pfile = util.pconvert(wfile) lines = chunks.check_max_diff(self.get_ctx(), pfile) if lines: return self.diff_highlight_buffer(lines) matcher = cmdutil.matchfiles(self.repo, [pfile]) opts = patch.diffopts(self.ui, self.opts) opts.git = True difftext = [] if self.is_merge(): wctx = self.repo[None] pctx1, pctx2 = wctx.parents() difftext = [_('===== Diff to first parent %d:%s =====\n') % ( pctx1.rev(), str(pctx1))] try: for s in patch.diff(self.repo, pctx1.node(), None, match=matcher, opts=opts): difftext.extend(s.splitlines(True)) difftext.append(_('\n===== Diff to second parent %d:%s =====\n') % ( pctx2.rev(), str(pctx2))) for s in patch.diff(self.repo, pctx2.node(), None, match=matcher, opts=opts): difftext.extend(s.splitlines(True)) except (IOError, error.RepoError, error.LookupError, util.Abort), e: self.stbar.set_text(str(e))
def recordfunc(ui, repo, files, message, match, opts): """This is generic record driver. It's job is to interactively filter local changes, and accordingly prepare working dir into a state, where the job can be delegated to non-interactive commit command such as 'commit' or 'qrefresh'. After the actual job is done by non-interactive command, working dir state is restored to original. In the end we'll record intresting changes, and everything else will be left in place, so the user can continue his work. """ if files: changes = None else: changes = repo.status(files=files, match=match)[:5] modified, added, removed = changes[:3] files = modified + added + removed diffopts = mdiff.diffopts(git=True, nodates=True) fp = cStringIO.StringIO() patch.diff(repo, repo.dirstate.parents()[0], files=files, match=match, changes=changes, opts=diffopts, fp=fp) fp.seek(0) # 1. filter patch, so we have intending-to apply subset of it chunks = filterpatch(ui, parsepatch(fp)) del fp contenders = {} for h in chunks: try: contenders.update(dict.fromkeys(h.files())) except AttributeError: pass newfiles = [f for f in files if f in contenders] if not newfiles: ui.status(_('no changes to record\n')) return 0 if changes is None: changes = repo.status(files=newfiles, match=match)[:5] modified = dict.fromkeys(changes[0]) # 2. backup changed files, so we can restore them in the end backups = {} backupdir = repo.join('record-backups') try: os.mkdir(backupdir) except OSError, err: if err.errno != errno.EEXIST: raise
def difftree(ui, repo, node1=None, node2=None, *files, **opts): """diff trees from two commits""" def __difftree(repo, node1, node2, files=[]): assert node2 is not None mmap = repo.changectx(node1).manifest() mmap2 = repo.changectx(node2).manifest() status = repo.status(node1, node2, files=files)[:5] modified, added, removed, deleted, unknown = status empty = short(nullid) for f in modified: # TODO get file permissions ui.write(":100664 100664 %s %s M\t%s\t%s\n" % (short(mmap[f]), short(mmap2[f]), f, f)) for f in added: ui.write(":000000 100664 %s %s N\t%s\t%s\n" % (empty, short(mmap2[f]), f, f)) for f in removed: ui.write(":100664 000000 %s %s D\t%s\t%s\n" % (short(mmap[f]), empty, f, f)) ## while True: if opts['stdin']: try: line = raw_input().split(' ') node1 = line[0] if len(line) > 1: node2 = line[1] else: node2 = None except EOFError: break node1 = repo.lookup(node1) if node2: node2 = repo.lookup(node2) else: node2 = node1 node1 = repo.changelog.parents(node1)[0] if opts['patch']: if opts['pretty']: catcommit(ui, repo, node2, "") patch.diff(repo, node1, node2, files=files, opts=patch.diffopts(ui, {'git': True})) else: __difftree(repo, node1, node2, files=files) if not opts['stdin']: break
def get_lines_and_files(ui, repo, ctx1, ctx2, fns): # Returns a list of dicts: # [{'filename': <file>, 'added': nn, 'removed': nn}, # {....}, # ] files = [] currentfile = {} fmatch = scmutil.matchfiles(repo, fns) diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch)) for l in diff.split('\n'): if l.startswith("diff -r"): # If we have anything in currentfile, append to list if currentfile: files.append(currentfile) currentfile = {} # This is the first line of a file set current file currentfile['filename'] = l.split(' ')[-1] currentfile['added'] = 0 currentfile['removed'] = 0 if l.startswith("+") and not l.startswith("+++ "): currentfile['added'] += 1 elif l.startswith("-") and not l.startswith("--- "): currentfile['removed'] += 1 # The last file won't have been added to files, so add it now files.append(currentfile) return files
def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges): parent = ctx.parents()[0].node() hg.update(repo, parent) fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-') fp = os.fdopen(fd, 'w') diffopts = patch.diffopts(ui, opts) diffopts.git = True gen = patch.diff(repo, parent, newnode, opts=diffopts) for chunk in gen: fp.write(chunk) fp.close() files = {} try: patch.patch(patchfile, ui, cwd=repo.root, files=files, eolmode=None) finally: files = patch.updatedir(ui, repo, files) os.unlink(patchfile) newmessage = '\n***\n'.join( [ctx.description(), ] + [repo[r].description() for r in internalchanges] + [oldctx.description(), ]) newmessage = ui.edit(newmessage, ui.username()) n = repo.commit(text=newmessage, user=ui.username(), date=max(ctx.date(), oldctx.date()), extra=oldctx.extra()) return repo[n], [n, ], [oldctx.node(), ctx.node() ], [newnode, ] # xxx
def pick(ui, repo, ctx, ha, opts): oldctx = repo[ha] if oldctx.parents()[0] == ctx: ui.debug('node %s unchanged\n' % ha) return oldctx, [], [], [] hg.update(repo, ctx.node()) fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-') fp = os.fdopen(fd, 'w') diffopts = patch.diffopts(ui, opts) diffopts.git = True gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts) for chunk in gen: fp.write(chunk) fp.close() try: files = {} try: patch.patch(patchfile, ui, cwd=repo.root, files=files, eolmode=None) if not files: ui.warn(_('%s: empty changeset') % node.hex(ha)) return ctx, [], [], [] finally: files = patch.updatedir(ui, repo, files) os.unlink(patchfile) except Exception, inst: raise util.Abort(_('Fix up the change and run ' 'hg histedit --continue'))
def createpatch(self, repo, name, msg, user, date, pats=[], opts={}): """creates a patch from the current state of the working copy""" fp = self.opener(name, 'w') ctx = repo[None] fp.write('# HG changeset patch\n') if user: fp.write('# User %s\n' % user) if date: fp.write('# Date %d %d\n' % date) parents = [p.node() for p in ctx.parents() if p] if parents and parents[0]: fp.write('# Parent %s\n' % hex(parents[0])) if msg: if not isinstance(msg, str): msg = '\n'.join(msg) if msg and msg[-1] != '\n': msg += '\n' fp.write(msg) m = cmdutil.match(repo, pats, opts) chunks = patch.diff(repo, match = m, opts = self.diffopts(opts)) for chunk in chunks: fp.write(chunk) fp.close() self.currentpatch=name self.persiststate()
def get_gitdiff(filenode_old, filenode_new): """Returns mercurial style git diff between given ``filenode_old`` and ``filenode_new``. """ for filenode in (filenode_old, filenode_new): if not isinstance(filenode, FileNode): raise VCSError("Given object should be FileNode object, not %s" % filenode.__class__) repo = filenode_new.changeset.repository old_raw_id = getattr(filenode_old.changeset, 'raw_id', '0' * 40) new_raw_id = getattr(filenode_new.changeset, 'raw_id', '0' * 40) root = filenode_new.changeset.repository.path file_filter = match(root, '', [filenode_new.path]) if isinstance(repo, MercurialRepository): vcs_gitdiff = patch.diff(repo._repo, old_raw_id, new_raw_id, match=file_filter, opts=diffopts(git=True)) else: vcs_gitdiff = repo._get_diff(old_raw_id, new_raw_id, filenode_new.path) return vcs_gitdiff
def savediff(): opts = {'git': True} fp = opener('.saved', 'w') for chunk in patch.diff(repo, head, None, opts=patch.diffopts(self.ui, opts)): fp.write(chunk) fp.close()
def new_commit(orig_commit, ui, repo, *pats, **opts): if opts['message'] or opts['logfile']: # don't act if user already specified a message return orig_commit(ui, repo, *pats, **opts) # check if changelog changed logname = ui.config('changelog', 'filename', 'CHANGES') if pats: match = cmdutil.match(repo, pats, opts) if logname not in match: # changelog is not mentioned return orig_commit(ui, repo, *pats, **opts) logmatch = cmdutil.match(repo, [logname], {}) logmatch.bad = lambda f, msg: None # don't complain if file is missing # get diff of changelog log = [] for chunk in patch.diff(repo, None, None, match=logmatch): for line in chunk.splitlines(): # naive: all added lines are the changelog if line.startswith('+') and not line.startswith('+++'): log.append(line[1:].rstrip().expandtabs()) log = normalize_log(log) # always let the user edit the message opts['force_editor'] = True opts['message'] = log return orig_commit(ui, repo, *pats, **opts)
def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges): parent = ctx.parents()[0].node() hg.update(repo, parent) fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-') fp = os.fdopen(fd, 'w') diffopts = patch.diffopts(ui, opts) diffopts.git = True diffopts.ignorews = False diffopts.ignorewsamount = False diffopts.ignoreblanklines = False gen = patch.diff(repo, parent, newnode, opts=diffopts) for chunk in gen: fp.write(chunk) fp.close() files = set() try: applypatch(ui, repo, patchfile, files=files, eolmode=None) finally: os.unlink(patchfile) newmessage = '\n***\n'.join( [ctx.description(), ] + [repo[r].description() for r in internalchanges] + [oldctx.description(), ]) # If the changesets are from the same author, keep it. if ctx.user() == oldctx.user(): username = ctx.user() else: username = ui.username() newmessage = ui.edit(newmessage, username) n = repo.commit(text=newmessage, user=username, date=max(ctx.date(), oldctx.date()), extra=oldctx.extra()) return repo[n], [n, ], [oldctx.node(), ctx.node() ], [newnode, ] # xxx
def apply_change(node, reverse, push_patch=True, name=None): p1, p2 = repo.changelog.parents(node) if p2 != nullid: raise util.Abort('cannot %s a merge changeset' % desc['action']) opts = mdiff.defaultopts opts.git = True rpatch = StringIO.StringIO() orig, mod = (node, p1) if reverse else (p1, node) for chunk in patch.diff(repo, node1=orig, node2=mod, opts=opts): rpatch.write(chunk) rpatch.seek(0) saved_stdin = None try: save_fin = ui.fin ui.fin = rpatch except: # Old versions of hg did not use the ui.fin mechanism saved_stdin = sys.stdin sys.stdin = rpatch handle_change(desc, node, qimport=(use_mq and new_opts.get('nopush'))) if saved_stdin is None: ui.fin = save_fin else: sys.stdin = saved_stdin
def diff(self, ctx, ref=None): maxdiff = int(self.ui.config('notify', 'maxdiff', 300)) prev = ctx.p1().node() if ref: ref = ref.node() else: ref = ctx.node() chunks = patch.diff(self.repo, prev, ref, opts=patch.diffallopts(self.ui)) difflines = ''.join(chunks).splitlines() if self.ui.configbool('notify', 'diffstat', True): s = patch.diffstat(difflines) # s may be nil, don't include the header if it is if s: self.ui.write('\ndiffstat:\n\n%s' % s) if maxdiff == 0: return elif maxdiff > 0 and len(difflines) > maxdiff: msg = _('\ndiffs (truncated from %d to %d lines):\n\n') self.ui.write(msg % (len(difflines), maxdiff)) difflines = difflines[:maxdiff] elif difflines: self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines)) self.ui.write("\n".join(difflines))
def fold(ui, repo, ctx, ha, opts): oldctx = repo[ha] hg.update(repo, ctx.node()) fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-') fp = os.fdopen(fd, 'w') diffopts = patch.diffopts(ui, opts) diffopts.git = True diffopts.ignorews = False diffopts.ignorewsamount = False diffopts.ignoreblanklines = False gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts) for chunk in gen: fp.write(chunk) fp.close() try: files = set() try: applypatch(ui, repo, patchfile, files=files, eolmode=None) if not files: ui.warn(_('%s: empty changeset') % node.hex(ha)) return ctx, [], [], [] finally: os.unlink(patchfile) except Exception, inst: raise util.Abort(_('Fix up the change and run ' 'hg histedit --continue'))
def apply_change(node, reverse, push_patch=True, name=None): p1, p2 = repo.changelog.parents(node) if p2 != nullid: raise util.Abort('cannot %s a merge changeset' % desc['action']) opts = mdiff.defaultopts opts.git = True rpatch = StringIO.StringIO() orig, mod = (node, p1) if reverse else (p1, node) for chunk in patch.diff(repo, node1=orig, node2=mod, opts=opts): rpatch.write(chunk) rpatch.seek(0) saved_stdin = None try: save_fin = ui.fin ui.fin = rpatch except: # Old versions of hg did not use the ui.fin mechanism saved_stdin = sys.stdin sys.stdin = rpatch if push_patch: commands.import_(ui, repo, '-', force=True, no_commit=True, strip=1, base='') else: mq.qimport(ui, repo, '-', name=name, rev=[], git=True) if saved_stdin is None: ui.fin = save_fin else: sys.stdin = saved_stdin
def diff(orig, ui, repo, *args, **opts): """show a diff of the most recent revision against its parent from svn """ if not opts.get('svn', False) or opts.get('change', None): return orig(ui, repo, *args, **opts) meta = repo.svnmeta() hashes = meta.revmap.hashes() if not opts.get('rev', None): parent = repo.parents()[0] o_r = util.outgoing_revisions(repo, hashes, parent.node()) if o_r: parent = repo[o_r[-1]].parents()[0] opts['rev'] = ['%s:.' % node.hex(parent.node()), ] node1, node2 = cmdutil.revpair(repo, opts['rev']) baserev, _junk = hashes.get(node1, (-1, 'junk')) newrev, _junk = hashes.get(node2, (-1, 'junk')) it = patch.diff(repo, node1, node2, opts=patch.diffopts(ui, opts={'git': True, 'show_function': False, 'ignore_all_space': False, 'ignore_space_change': False, 'ignore_blank_lines': False, 'unified': True, 'text': False, })) ui.write(util.filterdiff(''.join(it), baserev, newrev))
def recordfunc(ui, repo, message, match, opts): """This is generic record driver. Its job is to interactively filter local changes, and accordingly prepare working directory into a state in which the job can be delegated to a non-interactive commit command such as 'commit' or 'qrefresh'. After the actual job is done by non-interactive command, the working directory is restored to its original state. In the end we'll record interesting changes, and everything else will be left in place, so the user can continue working. """ cmdutil.checkunfinished(repo, commit=True) merge = len(repo[None].parents()) > 1 if merge: raise util.Abort(_("cannot partially commit a merge " '(use "hg commit" instead)')) status = repo.status(match=match) diffopts = opts.copy() diffopts["nodates"] = True diffopts["git"] = True diffopts = patch.diffopts(ui, opts=diffopts) chunks = patch.diff(repo, changes=status, opts=diffopts) fp = cStringIO.StringIO() fp.write("".join(chunks)) fp.seek(0) # 1. filter patch, so we have intending-to apply subset of it try: chunks = filterpatch(ui, parsepatch(fp)) except patch.PatchError, err: raise util.Abort(_("error parsing patch: %s") % err)
def autodiff(ui, repo, *pats, **opts): diffopts = patch.diffopts(ui, opts) git = opts.get('git', 'no') brokenfiles = set() losedatafn = None if git in ('yes', 'no'): diffopts.git = git == 'yes' diffopts.upgrade = False elif git == 'auto': diffopts.git = False diffopts.upgrade = True elif git == 'warn': diffopts.git = False diffopts.upgrade = True def losedatafn(fn=None, **kwargs): brokenfiles.add(fn) return True elif git == 'abort': diffopts.git = False diffopts.upgrade = True def losedatafn(fn=None, **kwargs): raise util.Abort('losing data for %s' % fn) else: raise util.Abort('--git must be yes, no or auto') node1, node2 = scmutil.revpair(repo, []) m = scmutil.match(repo[node2], pats, opts) it = patch.diff(repo, node1, node2, match=m, opts=diffopts, losedatafn=losedatafn) for chunk in it: ui.write(chunk) for fn in sorted(brokenfiles): ui.write(('data lost for: %s\n' % fn))
def __iter__(self): for item in self.stream: ctx = item.ctx if len(ctx.parents()) < 2: p = ''.join(patch.diff(ctx._repo, ctx.p1().node(), ctx.node())) lines = p.split('\n') stats = sum(map(self.delta_function, patch.diffstatdata(lines))) yield item.child(x=ctx.date()[0], y=stats)
def update_commit_preview(self): if self.is_merge(): opts = patch.diffopts(self.ui, self.opts) opts.git = True wctx = self.repo[None] pctx1, pctx2 = wctx.parents() difftext = [_('===== Diff to first parent %d:%s =====\n') % ( pctx1.rev(), str(pctx1))] try: for s in patch.diff(self.repo, pctx1.node(), None, opts=opts): difftext.extend(s.splitlines(True)) difftext.append(_('\n===== Diff to second parent %d:%s =====\n') % ( pctx2.rev(), str(pctx2))) for s in patch.diff(self.repo, pctx2.node(), None, opts=opts): difftext.extend(s.splitlines(True)) except (IOError, error.RepoError, error.LookupError, util.Abort), e: self.stbar.set_text(str(e))
def _file_changes(hg_ui, local_repo_path, commit): "Return FileChange instances for a commit" repo = hg.repository(hg_ui, path=local_repo_path) node2 = repo.lookup(commit.hash) node1 = repo[node2].parents()[0].node() lines = util.iterlines(patch.diff(repo, node1, node2)) for f in patch.diffstatdata(lines): yield FileChange(f[0], commit.file_changes[f[0]], f[1], f[2], f[3])
def changedlines(ui, repo, ctx1, ctx2, fns): lines = 0 fmatch = cmdutil.matchfiles(repo, fns) diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch)) for l in diff.split('\n'): if (l.startswith("+") and not l.startswith("+++ ") or l.startswith("-") and not l.startswith("--- ")): lines += 1 return lines
def recordfunc(ui, repo, message, match, opts): """This is generic record driver. Its job is to interactively filter local changes, and accordingly prepare working dir into a state, where the job can be delegated to non-interactive commit command such as 'commit' or 'qrefresh'. After the actual job is done by non-interactive command, working dir state is restored to original. In the end we'll record interesting changes, and everything else will be left in place, so the user can continue his work. """ merge = len(repo[None].parents()) > 1 if merge: raise util.Abort(_('cannot partially commit a merge ' '(use hg commit instead)')) changes = repo.status(match=match)[:3] diffopts = mdiff.diffopts(git=True, nodates=True) chunks = patch.diff(repo, changes=changes, opts=diffopts) fp = cStringIO.StringIO() fp.write(''.join(chunks)) fp.seek(0) # 1. filter patch, so we have intending-to apply subset of it chunks = crpatch.filterpatch(opts, crpatch.parsepatch(changes, fp), chunk_selector.chunkselector, ui) del fp contenders = set() for h in chunks: try: contenders.update(set(h.files())) except AttributeError: pass changed = changes[0] + changes[1] + changes[2] newfiles = [f for f in changed if f in contenders] if not newfiles: ui.status(_('no changes to record\n')) return 0 modified = set(changes[0]) # 2. backup changed files, so we can restore them in the end backups = {} backupdir = repo.join('record-backups') try: os.mkdir(backupdir) except OSError, err: if err.errno != errno.EEXIST: raise
def changedlines(ui, repo, ctx1, ctx2, fns): added, removed = 0, 0 fmatch = scmutil.matchfiles(repo, fns) diff = "".join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch)) for l in diff.split("\n"): if l.startswith("+") and not l.startswith("+++ "): added += 1 elif l.startswith("-") and not l.startswith("--- "): removed += 1 return (added, removed)
def diffs(repo, tmpl, ctx, basectx, files, parity, style): def countgen(): start = 1 while True: yield start start += 1 blockcount = countgen() def prettyprintlines(diff, blockno): for lineno, l in enumerate(diff.splitlines(True)): difflineno = "%d.%d" % (blockno, lineno + 1) if l.startswith('+'): ltype = "difflineplus" elif l.startswith('-'): ltype = "difflineminus" elif l.startswith('@'): ltype = "difflineat" else: ltype = "diffline" yield tmpl(ltype, line=l, lineno=lineno + 1, lineid="l%s" % difflineno, linenumber="% 8s" % difflineno) if files: m = match.exact(repo.root, repo.getcwd(), files) else: m = match.always(repo.root, repo.getcwd()) diffopts = patch.diffopts(repo.ui, untrusted=True) if basectx is None: parents = ctx.parents() if parents: node1 = parents[0].node() else: node1 = nullid else: node1 = basectx.node() node2 = ctx.node() block = [] for chunk in patch.diff(repo, node1, node2, m, opts=diffopts): if chunk.startswith('diff') and block: blockno = blockcount.next() yield tmpl('diffblock', parity=parity.next(), blockno=blockno, lines=prettyprintlines(''.join(block), blockno)) block = [] if chunk.startswith('diff') and style != 'raw': chunk = ''.join(chunk.splitlines(True)[1:]) block.append(chunk) blockno = blockcount.next() yield tmpl('diffblock', parity=parity.next(), blockno=blockno, lines=prettyprintlines(''.join(block), blockno))
def diff(self, node, ref): maxdiff = int(self.ui.config('notify', 'maxdiff', 300)) prev = self.repo.changelog.parents(node)[0] self.ui.pushbuffer() patch.diff(self.repo, prev, ref) difflines = self.ui.popbuffer().splitlines(1) if self.ui.configbool('notify', 'diffstat', True): s = patch.diffstat(difflines) # s may be nil, don't include the header if it is if s: self.ui.write('\ndiffstat:\n\n%s' % s) if maxdiff == 0: return if maxdiff > 0 and len(difflines) > maxdiff: self.ui.write(_('\ndiffs (truncated from %d to %d lines):\n\n') % (len(difflines), maxdiff)) difflines = difflines[:maxdiff] elif difflines: self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines)) self.ui.write(*difflines)
def difftree(ui, repo, node1=None, node2=None, *files, **opts): """diff trees from two commits""" def __difftree(repo, node1, node2, files=[]): assert node2 is not None mmap = repo[node1].manifest() mmap2 = repo[node2].manifest() m = scmutil.match(repo[node1], files) modified, added, removed = repo.status(node1, node2, m)[:3] empty = short(nullid) for f in modified: # TODO get file permissions ui.write(":100664 100664 %s %s M\t%s\t%s\n" % (short(mmap[f]), short(mmap2[f]), f, f)) for f in added: ui.write(":000000 100664 %s %s N\t%s\t%s\n" % (empty, short(mmap2[f]), f, f)) for f in removed: ui.write(":100664 000000 %s %s D\t%s\t%s\n" % (short(mmap[f]), empty, f, f)) ## while True: if opts['stdin']: try: line = raw_input().split(' ') node1 = line[0] if len(line) > 1: node2 = line[1] else: node2 = None except EOFError: break node1 = repo.lookup(node1) if node2: node2 = repo.lookup(node2) else: node2 = node1 node1 = repo.changelog.parents(node1)[0] if opts['patch']: if opts['pretty']: catcommit(ui, repo, node2, "") m = scmutil.match(repo[node1], files) diffopts = patch.difffeatureopts(ui) diffopts.git = True chunks = patch.diff(repo, node1, node2, match=m, opts=diffopts) for chunk in chunks: ui.write(chunk) else: __difftree(repo, node1, node2, files=files) if not opts['stdin']: break
def shelvefunc(ui, repo, message, match, opts): parents = repo.dirstate.parents() changes = repo.status(match=match)[:3] modified, added, removed = changes diffopts = patch.diffopts(ui, opts={'git': True, 'nodates': True}) chunks = patch.diff(repo, changes=changes, opts=diffopts) fp = cStringIO.StringIO(''.join(chunks)) try: ac = parsepatch(fp) except patch.PatchError, err: raise util.Abort(_('error parsing patch: %s') % err)
def getdiff(ui, repo, r, parent, opts): '''return diff for the specified revision''' output = "" if opts.get('git') or ui.configbool('diff', 'git'): # Git diffs don't include the revision numbers with each file, so # we have to put them in the header instead. output += "# Node ID " + node.hex(r.node()) + "\n" output += "# Parent " + node.hex(parent.node()) + "\n" diffopts = patch.diffopts(ui, opts) for chunk in patch.diff(repo, parent.node(), r.node(), opts=diffopts): output += chunk return output
def recordfunc(ui, repo, message, match, opts): """This is generic record driver. Its job is to interactively filter local changes, and accordingly prepare working dir into a state, where the job can be delegated to non-interactive commit command such as 'commit' or 'qrefresh'. After the actual job is done by non-interactive command, working dir state is restored to original. In the end we'll record interesting changes, and everything else will be left in place, so the user can continue his work. """ merge = len(repo[None].parents()) > 1 if merge: raise util.Abort( _('cannot partially commit a merge ' '(use hg commit instead)')) # status gives back # modified, added, removed, deleted, unknown, ignored, clean # we take only the first 3 of these changes = repo.status(match=match)[:3] modified, added, removed = changes try: # Mercurial >= 3.3 allow disabling format-changing diffopts diffopts = patch.difffeatureopts(ui, opts=opts, section='crecord', whitespace=True) except AttributeError: diffopts = patch.diffopts(ui, opts=opts, section='crecord') diffopts.nodates = True diffopts.git = True chunks = patch.diff(repo, changes=changes, opts=diffopts) fp = cStringIO.StringIO() fp.write(''.join(chunks)) fp.seek(0) # 1. filter patch, so we have intending-to apply subset of it chunks = crpatch.filterpatch(opts, crpatch.parsepatch(changes, fp), chunk_selector.chunkselector, ui) del fp contenders = set() for h in chunks: try: contenders.update(set(h.files())) except AttributeError: pass changed = changes[0] + changes[1] + changes[2] newfiles = [f for f in changed if f in contenders] if not newfiles: ui.status(_('no changes to record\n')) return 0 # 2. backup changed files, so we can restore them in the end backups = {} newly_added_backups = {} backupdir = repo.join('record-backups') try: os.mkdir(backupdir) except OSError, err: if err.errno != errno.EEXIST: raise
def haschanges (self, repo, pats=[], opts={}): """checks if repository has changes or not""" return list(patch.diff(repo, match = matchutil(repo, pats, opts), opts = self.diffopts(opts))) != []
def shelvefunc(ui, repo, message, match, opts): files = [] if match.files(): changes = None else: changes = repo.status(match = match)[:3] modified, added, removed = changes files = modified + added + removed match = matchfilesutil(repo, files) diffopts = repo.attic.diffopts( {'git':True, 'nodates':True}) chunks = patch.diff(repo, repo.dirstate.parents()[0], match = match, changes = changes, opts = diffopts) fp = cStringIO.StringIO() fp.write(''.join(chunks)) fp.seek(0) # 1. filter patch, so we have intending-to apply subset of it ac = record.parsepatch(fp) chunks = record.filterpatch(ui, ac) # and a not-intending-to apply subset of it rc = refilterpatch(ac, chunks) del fp contenders = {} for h in chunks: try: contenders.update(dict.fromkeys(h.files())) except AttributeError: pass newfiles = [f for f in files if f in contenders] if not newfiles: ui.status(_('no changes to shelve\n')) return 0 modified = dict.fromkeys(changes[0]) backups = {} backupdir = repo.join('shelve-backups') try: bkfiles = [f for f in newfiles if f in modified] backups = makebackup(ui, repo, backupdir, bkfiles) # patch to shelve sp = cStringIO.StringIO() for c in chunks: if c.filename() in backups: c.write(sp) doshelve = sp.tell() sp.seek(0) # patch to apply to shelved files fp = cStringIO.StringIO() for c in rc: if c.filename() in backups: c.write(fp) dopatch = fp.tell() fp.seek(0) try: # 3a. apply filtered patch to clean repo (clean) if backups: hg.revert(repo, repo.dirstate.parents()[0], backups.has_key) # 3b. apply filtered patch to clean repo (apply) if dopatch: ui.debug(_('applying patch\n')) ui.debug(fp.getvalue()) patch.internalpatch(fp, ui, 1, repo.root) del fp # 3c. apply filtered patch to clean repo (shelve) if doshelve: ui.debug(_("saving patch to %s\n") % (name)) s = repo.attic f = s.opener(name, 'w') f.write(sp.getvalue()) del f s.currentpatch = name s.persiststate() del sp except: try: for realname, tmpname in backups.iteritems(): ui.debug(_('restoring %r to %r\n') % (tmpname, realname)) util.copyfile(tmpname, repo.wjoin(realname)) except OSError: pass return 0 finally: try: for realname, tmpname in backups.iteritems(): ui.debug(_('removing backup for %r : %r\n') % (realname, tmpname)) os.unlink(tmpname) os.rmdir(backupdir) except OSError: pass
def diff(self): """Difference between working copy and repository.""" opts = patch.mdiff.diffopts(nodates=True) diff = patch.diff(self.repository._repository, opts=opts) return "".join(diff)
def _processpushreview(repo, req, ldap_username): """Handle a request to turn changesets into review requests. ``ldap_username`` is the LDAP username to associate with the MozReview account whose credentials are passed as part of the request. We implicitly trust the passed LDAP username has been authenticated to belong to the MozReview account. """ bzusername = req.get('bzusername') bzapikey = req.get('bzapikey') if not bzusername or not bzapikey: return errorresponse( 'Bugzilla API keys not configured; see ' 'https://mozilla-version-control-tools.readthedocs.io/en/latest/mozreview/install.html#obtaining-accounts-credentials-and-privileges ' 'for instructions on how to configure your client') identifier = req['identifier'] nodes = [] precursors = {} for cset in req['changesets']: node = cset['node'] nodes.append(node) if 'precursors' in cset: precursors[node] = cset['precursors'] diffopts = mdiff.diffopts(context=8, showfunc=True, git=True) commits = { 'individual': [], 'squashed': {}, 'obsolescence': req.get('obsolescence', False), } # We do multiple passes over the changesets requested for review because # some operations could be slow or may involve queries to external # resources. We want to run the fast checks first so we don't waste # resources before finding the error. The drawback here is the client # will not see the full set of errors. We may revisit this decision # later. for node in nodes: ctx = repo[node] # Reviewing merge commits doesn't make much sense and only makes # situations more complicated. So disallow the practice. if len(ctx.parents()) > 1: msg = 'cannot review merge commits (%s)' % short(ctx.node()) return errorresponse(msg) # Invalid or confidental bugs will raise errors in the Review Board # interface later. Fail fast to minimize wasted time and resources. try: reviewid = ReviewID(identifier) except error.Abort as e: return errorresponse(str(e)) # We use xmlrpc here because the Bugsy REST client doesn't currently handle # errors in responses. # We don't use available Bugzilla credentials because that's the # easiest way to test for confidential bugs. If/when we support posting # reviews to confidential bugs, we'll need to change this. xmlrpc_url = repo.ui.config('bugzilla', 'url').rstrip('/') + '/xmlrpc.cgi' proxy = xmlrpclib.ServerProxy(xmlrpc_url) try: proxy.Bug.get({'ids': [reviewid.bug]}) except xmlrpclib.Fault as f: if f.faultCode == 101: return errorresponse('bug %s does not exist; ' 'please change the review id (%s)' % (reviewid.bug, reviewid.full)) elif f.faultCode == 102: return errorresponse( 'bug %s could not be accessed ' '(we do not currently allow posting of reviews to ' 'confidential bugs)' % reviewid.bug) return errorresponse('server error verifying bug %s exists; ' 'please retry or report a bug' % reviewid.bug) # Find the first public node in the ancestry of this series. This is # used by MozReview to query the upstream repo for additional context. first_public_ancestor = None for node in repo[nodes[0]].ancestors(): ctx = repo[node] if ctx.phase() == phases.public: first_public_ancestor = ctx.hex() break commits['squashed']['first_public_ancestor'] = first_public_ancestor # Note patch.diff() appears to accept anything that can be fed into # repo[]. However, it blindly does a hex() on the argument as opposed # to the changectx, so we need to pass in the binary node. base_ctx = repo[nodes[0]].p1() base_parent_node = base_ctx.node() for i, node in enumerate(nodes): ctx = repo[node] p1 = ctx.p1().node() diff = ''.join( patch.diff(repo, node1=p1, node2=ctx.node(), opts=diffopts)) + '\n' if i: base_commit_id = nodes[i - 1] else: base_commit_id = base_ctx.hex() summary = encoding.fromlocal(ctx.description().splitlines()[0]) if req.get('deduce-reviewers', True): reviewers = list(commitparser.parse_rquestion_reviewers(summary)) requal_reviewers = list( commitparser.parse_requal_reviewers(summary)) else: reviewers = [] requal_reviewers = [] commits['individual'].append({ 'id': node, 'author': encoding.fromlocal(ctx.user()), 'precursors': precursors.get(node, []), 'message': encoding.fromlocal(ctx.description()), # Diffs are arbitrary byte sequences. json.dump() will try to # interpret str as UTF-8, which could fail. Instead of trying # to coerce the str to a unicode or use ensure_ascii=False (which # is a giant pain), just base64 encode the diff in the JSON. 'diff_b64': diff.encode('base64'), 'bug': str(reviewid.bug), 'base_commit_id': base_commit_id, 'first_public_ancestor': first_public_ancestor, 'reviewers': reviewers, 'requal_reviewers': requal_reviewers }) squashed_diff = b''.join( patch.diff(repo, node1=base_parent_node, node2=repo[nodes[-1]].node(), opts=diffopts)) + '\n' commits['squashed']['diff_b64'] = squashed_diff.encode('base64') commits['squashed']['base_commit_id'] = base_ctx.hex() rburl = repo.ui.config('reviewboard', 'url', None).rstrip('/') repoid = repo.ui.configint('reviewboard', 'repoid', None) privileged_rb_username = repo.ui.config('reviewboard', 'username', None) privileged_rb_password = repo.ui.config('reviewboard', 'password', None) if ldap_username: associate_ldap_username(rburl, ldap_username, privileged_rb_username, privileged_rb_password, username=bzusername, apikey=bzapikey) res = { 'rburl': rburl, 'reviewid': identifier, 'reviewrequests': {}, 'display': [], } try: parentrid, commitmap, reviews, warnings = \ post_reviews(rburl, repoid, identifier, commits, privileged_rb_username, privileged_rb_password, username=bzusername, apikey=bzapikey) res['display'].extend(warnings) res['parentrrid'] = parentrid res['reviewrequests'][parentrid] = { 'status': reviews[parentrid]['status'], 'public': reviews[parentrid]['public'], } for node, rid in commitmap.items(): rd = reviews[rid] res['reviewrequests'][rid] = { 'node': node, 'status': rd['status'], 'public': rd['public'], } if rd['reviewers']: res['reviewrequests'][rid]['reviewers'] = list(rd['reviewers']) except AuthorizationError as e: return errorresponse(str(e)) except BadRequestError as e: return errorresponse(str(e)) return res
def add_latest_rev_and_toolshed(repo, **kwargs): """ Iterating over all, but the ignored mercurial files. If a file is called tool_dependencies.xml or repository_dependencies.xml we check if 'changeset_revision' and/or 'toolshed' is not set or empty and insert the latest revision of the corresponding repo (repo-name/owner/tooshed). The default tool_shed url is hardcoded and can be changed. This hook creates a backup of the original file, replaces revision number and toolshed and commit the adopted changes. To restore the backup files use the additional script (toolshed_pretxncommit_hook.py) as pretxncommit-hook. Add the following to your .hgrc: [hooks] pre-commit = python:.hg/toolshed_pre-commit_hook.py:add_latest_rev_and_toolshed """ toolshed_url = "http://testtoolshed.g2.bx.psu.edu/" # to force a commit the user can add a temporary file called: force_pre-commit_hook_temp_file # we will forget that file, because it should only force the execution of that function commands.forget(ui.ui(), repo, 'force_pre-commit_hook_temp_file') logging.info( 'Emtering pre-commit Hook: Updating "toolshed" and/or "changeset_revision" attribute.' ) filename_categories = repo.status(clean=True) filepaths = [item for sublist in filename_categories for item in sublist] backup_files = list() for filepath in filepaths: if os.path.split(filepath)[-1] in [ 'tool_dependencies.xml', 'repository_dependencies.xml' ]: tree = ET.parse(filepath, parser=CommentedTreeBuilder()) root = tree.getroot() change = False for repo_dep in root.iter('repository'): if repo_dep.attrib.get('changeset_revision', '') == '': logging.info( 'Change *changeset_revision* of [%s]\n in file: %s\n and repository: %s' % ('%s :: %s' % (repo_dep.attrib['owner'], repo_dep.attrib['name']), filepath, repo.url())) tip = get_latest_repo_rev( '%srepos/%s/%s' % (toolshed_url, repo_dep.attrib['owner'], repo_dep.attrib['name'])) repo_dep.attrib.update({'changeset_revision': "%s" % tip}) change = True if repo_dep.attrib.get('toolshed', '') == '': logging.info( 'Change *toolshed* of [%s]\n in file: %s\n and repository: %s' % ('%s :: %s' % (repo_dep.attrib['owner'], repo_dep.attrib['name']), filepath, repo.url())) repo_dep.attrib.update( {'toolshed': "http://testtoolshed.g2.bx.psu.edu/"}) change = True if change: backup_filepath = '%s.pre-commit-backup' % filepath backup_files.append(backup_filepath) shutil.move(filepath, backup_filepath) tree.write(filepath, xml_declaration=True, encoding='utf-8') logging.info('Add %s to repository: %s' % (filepath, repo.url())) commands.add(ui.ui(), repo, filepath) # check if there is anything to commit if not [diff for diff in patch.diff(repo)]: logging.info('Nothing to commit for repository: %s.' % repo.url()) # if nothing to commit, restore the original files # these is necessary because I could not find a 'nothing to commit'-hook for backup_file in backup_files: if os.path.split(backup_file)[-1] in [ 'tool_dependencies.xml.pre-commit-backup', 'repository_dependencies.xml.pre-commit-backup' ]: ori_filepath = backup_file.replace('.pre-commit-backup', '') if os.path.split(ori_filepath)[-1] in [ 'tool_dependencies.xml', 'repository_dependencies.xml' ]: os.remove(ori_filepath) shutil.move(backup_file, ori_filepath) # abort the commit, because nothing is to commit sys.exit(1)
def rdiff(ui, repo, url, lrev=None, rrev=None, *pats, **opts): def rui(): try: return hg.remoteui(repo, opts) except AttributeError: # pre 1.6 return cmdutil.remoteui(repo, opts) try: other = hg.repository(rui(), url) except AttributeError: # pre-1.3 other = hg.repository(ui, url) cmdutil.setremoteconfig(ui, opts) ui.status(_('comparing with %s\n') % url) if rrev: if 'lookup' in other.capabilities: rrev = other.lookup(rrev) else: error = _( "Other repository doesn't support revision lookup, so a rev cannot be specified." ) raise util.Abort(error) incoming = findincomingfn(repo)(other, heads=rrev and [rrev] or []) if not incoming: # remote is a subset of local if not rrev: if 'lookup' in other.capabilities: rrev = other.lookup('tip') else: raise util.Abort(_('cannot determine remote tip')) other = repo bundle = None try: if incoming: # create a bundle (uncompressed if other repo is not local) if not rrev: cg = other.changegroup(incoming, "incoming") else: if 'changegroupsubset' not in other.capabilities: raise util.Abort( _("Partial incoming cannot be done because other repository doesn't support changegroupsubset." )) cg = other.changegroupsubset(incoming, rrev and [rrev] or [], 'incoming') bundle = changegroup.writebundle(cg, '', 'HG10UN') other = hg.repository(ui, bundle) if lrev: lrev = repo.changectx(lrev).node() rrev = other.changectx(rrev or 'tip').node() if opts['reverse']: lrev, rrev = rrev, lrev if not lrev: # bundle dirstate removed prior to hg 1.1 lrev = repo.dirstate.parents()[0] try: m = cmdutil.match(repo, pats, opts) chunks = patch.diff(other, lrev, rrev, match=m, opts=patch.diffopts(ui, opts)) for chunk in chunks: ui.write(chunk) except AttributeError: # 1.0 compatibility fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts) patch.diff(other, lrev, rrev, fns, match=matchfn, opts=patch.diffopts(ui, opts)) finally: if hasattr(other, 'close'): other.close() if bundle: os.unlink(bundle)
def recordfunc(ui, repo, message, match, opts): """This is generic record driver. Its job is to interactively filter local changes, and accordingly prepare working directory into a state in which the job can be delegated to a non-interactive commit command such as 'commit' or 'qrefresh'. After the actual job is done by non-interactive command, the working directory is restored to its original state. In the end we'll record interesting changes, and everything else will be left in place, so the user can continue working. """ merge = len(repo[None].parents()) > 1 if merge: raise util.Abort( _('cannot partially commit a merge ' '(use "hg commit" instead)')) changes = repo.status(match=match)[:3] diffopts = mdiff.diffopts( git=True, nodates=True, ignorews=opts.get('ignore_all_space'), ignorewsamount=opts.get('ignore_space_change'), ignoreblanklines=opts.get('ignore_blank_lines')) chunks = patch.diff(repo, changes=changes, opts=diffopts) fp = cStringIO.StringIO() fp.write(''.join(chunks)) fp.seek(0) # 1. filter patch, so we have intending-to apply subset of it chunks = filterpatch(ui, parsepatch(fp)) del fp contenders = set() for h in chunks: try: contenders.update(set(h.files())) except AttributeError: pass changed = changes[0] + changes[1] + changes[2] newfiles = [f for f in changed if f in contenders] if not newfiles: ui.status(_('no changes to record\n')) return 0 modified = set(changes[0]) # 2. backup changed files, so we can restore them in the end if backupall: tobackup = changed else: tobackup = [f for f in newfiles if f in modified] backups = {} if tobackup: backupdir = repo.join('record-backups') try: os.mkdir(backupdir) except OSError, err: if err.errno != errno.EEXIST: raise
def apply(self, repo, source, revmap, merges, opts={}): '''apply the revisions in revmap one by one in revision order''' revs = sorted(revmap) p1, p2 = repo.dirstate.parents() pulls = [] diffopts = patch.diffopts(self.ui, opts) diffopts.git = True lock = wlock = None try: wlock = repo.wlock() lock = repo.lock() for rev in revs: node = revmap[rev] revstr = '%s:%s' % (rev, revlog.short(node)) if self.applied(repo, node, p1): self.ui.warn( _('skipping already applied revision %s\n') % revstr) continue parents = source.changelog.parents(node) if not opts.get('filter'): # If the changeset parent is the same as the # wdir's parent, just pull it. if parents[0] == p1: pulls.append(node) p1 = node continue if pulls: if source != repo: repo.pull(source, heads=pulls) merge.update(repo, pulls[-1], False, False, None) p1, p2 = repo.dirstate.parents() pulls = [] domerge = False if node in merges: # pulling all the merge revs at once would mean we # couldn't transplant after the latest even if # transplants before them fail. domerge = True if not hasnode(repo, node): repo.pull(source, heads=[node]) if parents[1] != revlog.nullid: self.ui.note( _('skipping merge changeset %s:%s\n') % (rev, revlog.short(node))) patchfile = None else: fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-') fp = os.fdopen(fd, 'w') gen = patch.diff(source, parents[0], node, opts=diffopts) for chunk in gen: fp.write(chunk) fp.close() del revmap[rev] if patchfile or domerge: try: n = self.applyone(repo, node, source.changelog.read(node), patchfile, merge=domerge, log=opts.get('log'), filter=opts.get('filter')) if n and domerge: self.ui.status( _('%s merged at %s\n') % (revstr, revlog.short(n))) elif n: self.ui.status( _('%s transplanted to %s\n') % (revlog.short(node), revlog.short(n))) finally: if patchfile: os.unlink(patchfile) if pulls: repo.pull(source, heads=pulls) merge.update(repo, pulls[-1], False, False, None) finally: self.saveseries(revmap, merges) self.transplants.write() lock.release() wlock.release()
def getdiff(ui, repo): m = cmdutil.match(repo) o = patch.diffopts(ui, {'git': True}) return patch.diff(repo, match=m, opts=o)
def absorb(ui, repo, stack=None, targetctx=None, pats=None, opts=None): """pick fixup chunks from targetctx, apply them to stack. if targetctx is None, the working copy context will be used. if stack is None, the current draft stack will be used. return fixupstate. """ if stack is None: limit = ui.configint('absorb', 'max-stack-size') headctx = repo['.'] if len(headctx.parents()) > 1: raise error.Abort(_('cannot absorb into a merge')) stack = getdraftstack(headctx, limit) if limit and len(stack) >= limit: ui.warn( _('absorb: only the recent %d changesets will ' 'be analysed\n') % limit) if not stack: raise error.Abort(_('no mutable changeset to change')) if targetctx is None: # default to working copy targetctx = repo[None] if pats is None: pats = () if opts is None: opts = {} state = fixupstate(stack, ui=ui, opts=opts) matcher = scmutil.match(targetctx, pats, opts) if opts.get('interactive'): diff = patch.diff(repo, stack[-1].node(), targetctx.node(), matcher) origchunks = patch.parsepatch(diff) chunks = cmdutil.recordfilter(ui, origchunks, matcher)[0] targetctx = overlaydiffcontext(stack[-1], chunks) fm = None if opts.get('print_changes') or not opts.get('apply_changes'): fm = ui.formatter('absorb', opts) state.diffwith(targetctx, matcher, fm) if fm is not None: fm.startitem() fm.write("count", "\n%d changesets affected\n", len(state.ctxaffected)) fm.data(linetype='summary') for ctx in reversed(stack): if ctx not in state.ctxaffected: continue fm.startitem() fm.context(ctx=ctx) fm.data(linetype='changeset') fm.write('node', '%-7.7s ', ctx.hex(), label='absorb.node') descfirstline = ctx.description().splitlines()[0] fm.write('descfirstline', '%s\n', descfirstline, label='absorb.description') fm.end() if not opts.get('dry_run'): if (not opts.get('apply_changes') and state.ctxaffected and ui.promptchoice("apply changes (yn)? $$ &Yes $$ &No", default=1)): raise error.Abort(_('absorb cancelled\n')) state.apply() if state.commit(): state.printchunkstats() elif not ui.quiet: ui.write(_('nothing applied\n')) return state
def shelvefunc(ui, repo, message, match, opts): parents = repo.dirstate.parents() changes = repo.status(match=match)[:5] modified, added, removed = changes[:3] files = modified + added + removed diffopts = mdiff.diffopts(git=True, nodates=True) patch_diff = ''.join( patch.diff(repo, parents[0], match=match, changes=changes, opts=diffopts)) fp = cStringIO.StringIO(patch_diff) ac = parsepatch(fp) fp.close() chunks = filterpatch(ui, ac, not opts['all']) rc = refilterpatch(ac, chunks) # set of files to be processed contenders = {} for h in chunks: try: contenders.update(dict.fromkeys(h.files())) except AttributeError: pass # exclude sources of copies that are otherwise untouched newfiles = set(f for f in files if f in contenders) if not newfiles: ui.status(_('no changes to shelve\n')) return 0 backupdir = repo.join('shelve-backups') try: backups = makebackup(ui, repo, backupdir, newfiles) # patch to shelve sp = cStringIO.StringIO() for c in chunks: c.write(sp) # patch to apply to shelved files fp = cStringIO.StringIO() for c in rc: # skip files not selected for shelving if c.filename() in newfiles: c.write(fp) dopatch = fp.tell() fp.seek(0) try: # 3a. apply filtered patch to clean repo (clean) opts['no_backup'] = True cmdutil.revert(ui, repo, repo['.'], parents, *[os.path.join(repo.root, f) for f in newfiles], **opts) for f in added: if f in newfiles: util.unlinkpath(repo.wjoin(f)) # 3b. apply filtered patch to clean repo (apply) if dopatch: ui.debug('applying patch\n') ui.debug(fp.getvalue()) patch.internalpatch(ui, repo, fp, 1) del fp # 3c. apply filtered patch to clean repo (shelve) ui.debug("saving patch to shelve\n") if opts['append']: sp.write(repo.opener(shelfpath).read()) sp.seek(0) f = repo.opener(shelfpath, "w") f.write(sp.getvalue()) del f, sp except: ui.warn("shelving failed: %s\n" % sys.exc_info()[1]) try: # re-schedule remove matchremoved = scmutil.matchfiles(repo, removed) cmdutil.forget(ui, repo, matchremoved, "", True) for f in removed: if f in newfiles and os.path.isfile(f): os.unlink(f) # copy back backups for realname, tmpname in backups.iteritems(): ui.debug('restoring %r to %r\n' % (tmpname, realname)) util.copyfile(tmpname, repo.wjoin(realname)) # re-schedule add matchadded = scmutil.matchfiles(repo, added) cmdutil.add(ui, repo, matchadded, False, False, "", True) ui.debug('removing shelve file\n') if os.path.isfile(repo.wjoin(shelfpath)): os.unlink(repo.join(shelfpath)) except OSError, err: ui.warn("restoring backup failed: %s\n" % err) return 0
def shelvefunc(ui, repo, message, match, opts): changes = repo.status(match=match)[:5] modified, added, removed = changes[:3] files = modified + added + removed diffopts = mdiff.diffopts(git=True, nodates=True) patch_diff = ''.join( patch.diff(repo, repo.dirstate.parents()[0], match=match, changes=changes, opts=diffopts)) fp = cStringIO.StringIO(patch_diff) ac = parsepatch(fp) fp.close() chunks = filterpatch(ui, ac, not opts['all']) rc = refilterpatch(ac, chunks) contenders = {} for h in chunks: try: contenders.update(dict.fromkeys(h.files())) except AttributeError: pass newfiles = [f for f in files if f in contenders] if not newfiles: ui.status(_('no changes to shelve\n')) return 0 modified = dict.fromkeys(changes[0]) backupdir = repo.join('shelve-backups') try: bkfiles = [f for f in newfiles if f in modified] backups = makebackup(ui, repo, backupdir, bkfiles) # patch to shelve sp = cStringIO.StringIO() for c in chunks: if c.filename() in backups: c.write(sp) doshelve = sp.tell() sp.seek(0) # patch to apply to shelved files fp = cStringIO.StringIO() for c in rc: if c.filename() in backups: c.write(fp) dopatch = fp.tell() fp.seek(0) try: # 3a. apply filtered patch to clean repo (clean) if backups: hg.revert(repo, repo.dirstate.parents()[0], backups.has_key) # 3b. apply filtered patch to clean repo (apply) if dopatch: ui.debug('applying patch\n') ui.debug(fp.getvalue()) patch.internalpatch(fp, ui, 1, repo.root) del fp # 3c. apply filtered patch to clean repo (shelve) if doshelve: ui.debug("saving patch to shelve\n") if opts['append']: f = repo.opener(shelfpath, "a") else: f = repo.opener(shelfpath, "w") f.write(sp.getvalue()) del f del sp except: try: for realname, tmpname in backups.iteritems(): ui.debug('restoring %r to %r\n' % (tmpname, realname)) util.copyfile(tmpname, repo.wjoin(realname)) ui.debug('removing shelve file\n') os.unlink(repo.join(shelfpath)) except OSError: pass return 0 finally: try: for realname, tmpname in backups.iteritems(): ui.debug('removing backup for %r : %r\n' % (realname, tmpname)) os.unlink(tmpname) os.rmdir(backupdir) except OSError: pass
def apply(self, repo, source, revmap, merges, opts=None): '''apply the revisions in revmap one by one in revision order''' if opts is None: opts = {} revs = sorted(revmap) p1, p2 = repo.dirstate.parents() pulls = [] diffopts = patch.difffeatureopts(self.ui, opts) diffopts.git = True lock = wlock = tr = None try: wlock = repo.wlock() lock = repo.lock() tr = repo.transaction('transplant') for rev in revs: node = revmap[rev] revstr = '%s:%s' % (rev, short(node)) if self.applied(repo, node, p1): self.ui.warn( _('skipping already applied revision %s\n') % revstr) continue parents = source.changelog.parents(node) if not (opts.get('filter') or opts.get('log')): # If the changeset parent is the same as the # wdir's parent, just pull it. if parents[0] == p1: pulls.append(node) p1 = node continue if pulls: if source != repo: exchange.pull(repo, source.peer(), heads=pulls) merge.update(repo, pulls[-1], False, False, None) p1, p2 = repo.dirstate.parents() pulls = [] domerge = False if node in merges: # pulling all the merge revs at once would mean we # couldn't transplant after the latest even if # transplants before them fail. domerge = True if not hasnode(repo, node): exchange.pull(repo, source.peer(), heads=[node]) skipmerge = False if parents[1] != revlog.nullid: if not opts.get('parent'): self.ui.note( _('skipping merge changeset %s:%s\n') % (rev, short(node))) skipmerge = True else: parent = source.lookup(opts['parent']) if parent not in parents: raise error.Abort( _('%s is not a parent of %s') % (short(parent), short(node))) else: parent = parents[0] if skipmerge: patchfile = None else: fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-') fp = os.fdopen(fd, 'w') gen = patch.diff(source, parent, node, opts=diffopts) for chunk in gen: fp.write(chunk) fp.close() del revmap[rev] if patchfile or domerge: try: try: n = self.applyone(repo, node, source.changelog.read(node), patchfile, merge=domerge, log=opts.get('log'), filter=opts.get('filter')) except TransplantError: # Do not rollback, it is up to the user to # fix the merge or cancel everything tr.close() raise if n and domerge: self.ui.status( _('%s merged at %s\n') % (revstr, short(n))) elif n: self.ui.status( _('%s transplanted to %s\n') % (short(node), short(n))) finally: if patchfile: os.unlink(patchfile) tr.close() if pulls: exchange.pull(repo, source.peer(), heads=pulls) merge.update(repo, pulls[-1], False, False, None) finally: self.saveseries(revmap, merges) self.transplants.write() if tr: tr.release() if lock: lock.release() wlock.release()