def _backup( repo, backupstate, remotepath, getconnection, revs=None, backupsnapshots=False ): """backs up the given revisions to commit cloud Returns (backedup, failed), where "backedup" is a revset of the commits that were backed up, and "failed" is a revset of the commits that could not be backed up. """ unfi = repo if revs is None: # No revs specified. Back up all visible commits that are not already # backed up. Also back up all the snapshots if needed. snapshotcond = " + snapshot()" if backupsnapshots else "" revset = ( "heads(not public() - hidden()%s - (not public() & ::%%ln))" % snapshotcond ) heads = unfi.revs(revset, backupstate.heads) else: # Some revs were specified. Back up all of those commits that are not # already backed up. heads = unfi.revs( "heads((not public() & ::%ld) - (not public() & ::%ln))", revs, backupstate.heads, ) if not heads: return smartset.baseset(), smartset.baseset() # Check if any of the heads are already available on the server. headnodes = list(unfi.nodes("%ld", heads)) remoteheadnodes = { head for head, backedup in zip( headnodes, dependencies.infinitepush.isbackedupnodes( getconnection, [nodemod.hex(n) for n in headnodes] ), ) if backedup } if remoteheadnodes: backupstate.update(remoteheadnodes) heads = unfi.revs("%ld - %ln", heads, remoteheadnodes) if not heads: return smartset.baseset(), smartset.baseset() # Filter out any commits that have been marked as bad. badnodes = repo.ui.configlist("infinitepushbackup", "dontbackupnodes", []) if badnodes: badnodes = [node for node in badnodes if node in unfi] # The nodes we can't back up are the bad nodes and their descendants, # minus any commits that we know are already backed up anyway. badnodes = list( unfi.nodes( "(not public() & ::%ld) & (%ls::) - (not public() & ::%ln)", heads, badnodes, backupstate.heads, ) ) if badnodes: repo.ui.warn( _("not backing up commits marked as bad: %s\n") % ", ".join([nodemod.hex(node) for node in badnodes]) ) heads = unfi.revs("heads((not public() & ::%ld) - %ln)", heads, badnodes) # Limit the number of heads we backup in a single operation. backuplimit = repo.ui.configint("infinitepushbackup", "maxheadstobackup") if backuplimit is not None and backuplimit >= 0: if len(heads) > backuplimit: repo.ui.status( _n( "backing up only the most recent %d head\n", "backing up only the most recent %d heads\n", backuplimit, ) % backuplimit ) heads = sorted(heads, reverse=True)[:backuplimit] # Back up the new heads. backingup = unfi.nodes( "(not public() & ::%ld) - (not public() & ::%ln)", heads, backupstate.heads ) backuplock.progressbackingup(repo, list(backingup)) with perftrace.trace("Push Backup Bundles"): newheads, failedheads = dependencies.infinitepush.pushbackupbundlestacks( repo.ui, unfi, getconnection, [nodemod.hex(n) for n in unfi.nodes("%ld", heads)], ) # The commits that got backed up are all the ancestors of the new backup # heads, minus any commits that were already backed up at the start. backedup = unfi.revs( "(not public() & ::%ls) - (not public() & ::%ln)", newheads, backupstate.heads ) # The commits that failed to get backed up are the ancestors of the failed # heads, except for commits that are also ancestors of a successfully backed # up head, or commits that were already known to be backed up. failed = unfi.revs( "(not public() & ::%ls) - (not public() & ::%ls) - (not public() & ::%ln)", failedheads, newheads, backupstate.heads, ) backupstate.update(unfi.nodes("%ld", backedup)) return backedup, failed
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", "maxstacksize", 50) stack = getdraftstack(repo["."], 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 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)[0] targetctx = overlaydiffcontext(stack[-1], chunks) fm = None if not (ui.quiet and opts.get("apply_changes")) and not opts.get("edit_lines"): fm = ui.formatter("absorb", opts) state.diffwith(targetctx, matcher, fm) if fm is not None and state.ctxaffected: fm.startitem() count = len(state.ctxaffected) fm.write( "count", _n("\n%d changeset affected\n", "\n%d changesets affected\n", count), count, ) 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("edit_lines") and not any( f.fixups for f in state.fixupmap.values() ): ui.write(_("nothing to absorb\n")) elif not opts.get("dry_run"): if not opts.get("apply_changes"): if ui.promptchoice("apply changes (yn)? $$ &Yes $$ &No", default=0): raise error.Abort(_("absorb cancelled\n")) state.apply() state.commit() state.printchunkstats() return state
def storebundle(op, params, bundlefile, iscrossbackendsync=False): log = _getorcreateinfinitepushlogger(op) parthandlerstart = time.time() log(constants.scratchbranchparttype, eventtype="start") index = op.repo.bundlestore.index store = op.repo.bundlestore.store op.records.add(constants.scratchbranchparttype + "_skippushkey", True) bundle = None try: # guards bundle bundlepath = "bundle:%s+%s" % (op.repo.root, bundlefile) bundle = hg.repository(op.repo.ui, bundlepath) bookmark = params.get("bookmark") create = params.get("create") force = params.get("force") if bookmark: oldnode = index.getnode(bookmark) if not oldnode and not create: raise error.Abort( "unknown bookmark %s" % bookmark, hint="use --create if you want to create one", ) else: oldnode = None bundleheads = bundle.revs("heads(bundle())") if bookmark and len(bundleheads) > 1: raise error.Abort( _("cannot push more than one head to a scratch branch")) revs = _getrevs(bundle, oldnode, force, bookmark) # Notify the user of what is being pushed op.repo.ui.warn( _n("pushing %s commit:\n", "pushing %s commits:\n", len(revs)) % len(revs)) maxoutput = 10 for i in range(0, min(len(revs), maxoutput)): firstline = bundle[revs[i]].description().split("\n")[0][:50] op.repo.ui.warn(_x(" %s %s\n") % (revs[i], firstline)) if len(revs) > maxoutput + 1: op.repo.ui.warn(_x(" ...\n")) firstline = bundle[revs[-1]].description().split("\n")[0][:50] op.repo.ui.warn(_x(" %s %s\n") % (revs[-1], firstline)) nodesctx = [bundle[rev] for rev in revs] inindex = lambda rev: bool(index.getbundle(bundle[rev].hex())) if bundleheads: newheadscount = sum(not inindex(rev) for rev in bundleheads) else: newheadscount = 0 # If there's a bookmark specified, the bookmarked node should also be # provided. Older clients may omit this, in which case there should be # only one head, so we choose the last node, which will be that head. # If a bug or malicious client allows there to be a bookmark # with multiple heads, we will place the bookmark on the last head. bookmarknode = params.get("bookmarknode", nodesctx[-1].hex() if nodesctx else None) key = None if newheadscount: bundlesize = os.stat(bundlefile).st_size with logservicecall(log, "bundlestore", bundlesize=bundlesize): bundlesizelimitmb = op.repo.ui.configint( "infinitepush", "maxbundlesize", 100) if bundlesize > bundlesizelimitmb * 1024 * 1024: error_msg = ( "bundle is too big: %d bytes. " + "max allowed size is %s MB" % bundlesizelimitmb) raise error.Abort(error_msg % (bundlesize, )) with open(bundlefile, "rb") as f: bundledata = f.read() key = store.write(bundledata) with logservicecall(log, "index", newheadscount=newheadscount), index: if key: index.addbundle(key, nodesctx, iscrossbackendsync=iscrossbackendsync) if bookmark and bookmarknode: index.addbookmark(bookmark, bookmarknode, False) log( constants.scratchbranchparttype, eventtype="success", elapsedms=(time.time() - parthandlerstart) * 1000, ) fillmetadatabranchpattern = op.repo.ui.config( "infinitepush", "fillmetadatabranchpattern", "") if bookmark and fillmetadatabranchpattern: __, __, matcher = util.stringmatcher(fillmetadatabranchpattern) if matcher(bookmark): _asyncsavemetadata(op.repo.root, [ctx.hex() for ctx in nodesctx]) except Exception as e: log( constants.scratchbranchparttype, eventtype="failure", elapsedms=(time.time() - parthandlerstart) * 1000, errormsg=str(e), ) raise finally: if bundle: bundle.close()
def hide(ui, repo, *revs, **opts): """hide commits and their descendants Mark the specified commits as hidden. Hidden commits are not included in the output of most Mercurial commands, including :hg:`log` and :hg:`smartlog.` Any descendants of the specified commits will also be hidden. Hidden commits are not deleted. They will remain in the repo indefinitely and are still accessible by their hashes. However, :hg:`hide` will delete any bookmarks pointing to hidden commits. Use the :hg:`unhide` command to make hidden commits visible again. See :hg:`help unhide` for more information. To view hidden commits, run :hg:`journal`. When you hide the current commit, the most recent visible ancestor is checked out. To hide obsolete stacks (stacks that have a newer version), run :hg:`hide --cleanup`. This command is equivalent to: :hg:`hide 'obsolete() - ancestors(draft() & not obsolete())'` --cleanup skips obsolete commits with non-obsolete descendants. """ if opts.get("cleanup") and len(opts.get("rev") + list(revs)) != 0: raise error.Abort(_("--rev and --cleanup are incompatible")) elif opts.get("cleanup"): # hides all the draft, obsolete commits that # don't have non-obsolete descendants revs = ["obsolete() - (draft() & ::(draft() & not obsolete()))"] else: revs = list(revs) + opts.pop("rev", []) with repo.wlock(), repo.lock(), repo.transaction("hide") as tr: revs = repo.revs("(%ld)::", scmutil.revrange(repo, revs)) bookmarks = set(opts.get("bookmark", ())) if bookmarks: revs += bookmarksmod.reachablerevs(repo, bookmarks) if not revs: # No revs are reachable exclusively from these bookmarks, just # delete the bookmarks. if not ui.quiet: for bookmark in sorted(bookmarks): ui.status( _("removing bookmark '%s' (was at: %s)\n") % (bookmark, short(repo._bookmarks[bookmark]))) bookmarksmod.delete(repo, tr, bookmarks) ui.status( _n( "%i bookmark removed\n", "%i bookmarks removed\n", len(bookmarks), ) % len(bookmarks)) return 0 if not revs: raise error.Abort(_("nothing to hide")) hidectxs = [repo[r] for r in revs] # revs to be hidden for ctx in hidectxs: if not ctx.mutable(): raise error.Abort( _("cannot hide immutable changeset: %s") % ctx, hint="see 'hg help phases' for details", ) if not ui.quiet: ui.status( _('hiding commit %s "%s"\n') % (ctx, ctx.description().split("\n")[0][:50])) wdp = repo["."] newnode = wdp while newnode in hidectxs: newnode = newnode.parents()[0] if newnode.node() != wdp.node(): cmdutil.bailifchanged(repo, merge=False) hg.update(repo, newnode, False) ui.status( _("working directory now at %s\n") % ui.label(str(newnode), "node")) # create markers if obsolete.isenabled(repo, obsolete.createmarkersopt): obsolete.createmarkers(repo, [(r, []) for r in hidectxs], operation="hide") visibility.remove(repo, [c.node() for c in hidectxs]) ui.status( _n("%i changeset hidden\n", "%i changesets hidden\n", len(hidectxs)) % len(hidectxs)) # remove bookmarks pointing to hidden changesets hnodes = [r.node() for r in hidectxs] deletebookmarks = set(bookmarks) for bookmark, node in sorted(bookmarksmod.listbinbookmarks(repo)): if node in hnodes: deletebookmarks.add(bookmark) if deletebookmarks: for bookmark in sorted(deletebookmarks): if not ui.quiet: ui.status( _('removing bookmark "%s (was at: %s)"\n') % (bookmark, short(repo._bookmarks[bookmark]))) bookmarksmod.delete(repo, tr, deletebookmarks) ui.status( _n( "%i bookmark removed\n", "%i bookmarks removed\n", len(deletebookmarks), ) % len(deletebookmarks)) # unsubscribe from the remote bookmarks pointing to hidden changesets # they always will be remote scratch bookmarks because hidectxs are all draft if ui.configbool("remotenames", "selectivepull"): node2marks = repo._remotenames.node2marks() marks = sum( [node2marks[node] for node in hnodes if node in node2marks], []) bmremove = {key: nodemod.nullhex for key in marks} if bmremove: for bookmark in sorted(marks): if not ui.quiet: ui.status( _('unsubscribing remote bookmark "%s"\n') % bookmark) repo._remotenames.applychanges({"bookmarks": bmremove}, override=False) ui.status( _n( "%i remote bookmark unsubscribed\n", "%i remote bookmarks unsubscribed\n", len(bmremove), ) % len(bmremove)) hintutil.trigger("undo")