Files
UnrealEngine/Engine/Extras/ushell/channels/unreal/perforce/cmds/mergedown.py
2025-05-18 13:04:45 +08:00

174 lines
5.8 KiB
Python

# Copyright Epic Games, Inc. All Rights Reserved.
import p4utils
import flow.cmd
from peafour import P4
#-------------------------------------------------------------------------------
class _TtyCounter(object):
def __init__(self, leader, suffix):
self._suffix = suffix
self._count = 0
self._leader = leader
self.flush(True)
def inc(self):
self._count += 1
self.flush()
def flush(self, inc_leader=False):
if inc_leader:
print(self._leader, end="")
out = str(self._count) + self._suffix
print(out, "\b" * len(out), sep="", end="")
return self
#-------------------------------------------------------------------------------
class MergeDown(flow.cmd.Cmd):
""" Merge down from the parent stream. """
changelist = flow.cmd.Arg("", "Changelist to merge down at")
path = flow.cmd.Opt("", "Limit mergedown to a sub-tree; --path=Engine/Source")
maxscanrows = flow.cmd.Opt(0, "Set the max scan rows with integrating")
dryrun = flow.cmd.Opt(False, "Only pretend to do work")
noautoresolve = flow.cmd.Opt(False, "Do not automatically resolve")
@flow.cmd.Cmd.summarise
def main(self):
p4utils.login()
# Determine the streams involved
self.print_info("Getting stream info")
info = P4.info()
stream = getattr(info, "clientStream", None)
if not stream:
raise EnvironmentError(f"Client '{info.clientName}' is not a stream")
# Get the parent, skipping past virtual streams.
stream_info = P4.stream(stream, o=True).run()
parent = stream_info.Parent
while True:
stream_info = P4.stream(parent, o=True).run()
if stream_info.Type != "virtual":
break
parent = stream_info.Parent
if self.args.changelist:
parent_cl = self.args.changelist
else:
parent_cl = P4.changes(parent + "/...", m=1, s="submitted").run()
parent_cl = parent_cl.change
print(f"Target: {stream} (as {info.clientName})")
print(f"Parent: {parent}")
print(f"Change: {parent_cl}")
# Create a changelist to merge into
if self.args.path:
spec_stem = f"/{self.args.path}/...@".replace("\\", "/")
print("Filter:", stream + spec_stem + parent_cl)
else:
spec_stem = "/...@"
# Create a changelist to merge into
def _create_cl(desc):
cl_spec = {
"Change" : "new",
"Description" : desc,
}
P4.change(i=True).run(input_data=cl_spec)
return P4.changes(c=info.clientName, m=1, s="pending").change
self.print_info("Creating target changelist")
cl_desc = f"Merging {parent} @ {parent_cl} to {stream} ({info.clientName})"
cl_desc += "\n\n#ushell-mergedown"
dest_cl = _create_cl(cl_desc)
print(dest_cl)
# Do the merge
self.print_info("Merging")
path_args = (
stream + spec_stem + parent_cl,
)
int_args = {
"c" : dest_cl,
"n" : self.args.dryrun,
"S" : stream,
"r" : True,
}
def on_error(data):
msg = data.data.strip()
print(end="\r")
self.print_error(msg)
file_count.flush(True)
file_count.inc()
file_count = _TtyCounter("Running 'p4 integrate'; ", " files")
p4 = P4(zmaxscanrows=str(self.args.maxscanrows or 1000000000))
integrate = p4.integrate(*path_args, **int_args)
for item in integrate.read(on_error=on_error):
path = getattr(item, "clientFile", None)
if path:
file_count.inc()
print("\nDone!")
if self.args.dryrun:
return
# Resolve safely
def do_resolve(cl, *modes):
mode_args = {x:True for x in modes}
yield from P4.resolve(c=cl, **mode_args).read(on_error=False)
self.print_info("Resolving")
file_count = _TtyCounter("Safely; ", " files")
for item in do_resolve(dest_cl, "as"):
path = getattr(item, "clientFile", None)
if path:
file_count.inc()
# Move files that didn't resolve to a review changelist
def create_review_cl():
lines = ("AUTO MERGED/UNRESOLVED", cl_desc, "#nocheckin")
review_cl_desc = "\n\n".join(lines)
review_cl = _create_cl(review_cl_desc)
print("Moving unresolved to " + review_cl)
return review_cl
review_cl = None
try:
for item in do_resolve(dest_cl, "n"):
review_cl = review_cl or create_review_cl()
path = getattr(item, "clientFile", None)
if path:
P4.reopen(path, c=review_cl).run()
except P4.Error:
pass
# Perhaps there's nothing to review? Hooray!
if not review_cl:
return
# Make sure moved-pairs are in the same changelist
def read_moved_pairs():
for item in P4.opened(c=review_cl):
move_pair = getattr(item, "movedFile", None)
if move_pair:
yield move_pair
file_count = _TtyCounter("Collecting moved pairs; ", " files")
for item in P4.reopen(read_moved_pairs(), c=review_cl):
file_count.inc()
# Resolve what we can automatically.
if not self.args.noautoresolve:
file_count = _TtyCounter("Automatically; ", " files")
for item in do_resolve(review_cl, "am"):
path = getattr(item, "clientFile", None)
if path:
file_count.inc()