mirror of
https://gerrit.googlesource.com/git-repo
synced 2026-10-04 12:40:46 +00:00
Git --autostash predates repo's minimum supported client. Pass it directly to rebase instead of probing the index and separately running stash and stash pop, reducing the dirty path from four Git processes to one and correctly covering staged-only changes. Bug: 553599402 Change-Id: I0d6d59b17996576310498e9378e49380d355236e Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/623902 Tested-by: Gavin Mak <gavinmak@google.com> Reviewed-by: Brian Gan <brgan@google.com> Commit-Queue: Gavin Mak <gavinmak@google.com>
204 lines
6.2 KiB
Python
204 lines
6.2 KiB
Python
# Copyright (C) 2010 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import sys
|
|
|
|
from color import Coloring
|
|
from command import Command
|
|
from error import GitError
|
|
from git_command import GitCommand
|
|
from project import Project
|
|
from repo_logging import RepoLogger
|
|
|
|
|
|
logger = RepoLogger(__file__)
|
|
|
|
|
|
class RebaseColoring(Coloring):
|
|
def __init__(self, config):
|
|
Coloring.__init__(self, config, "rebase")
|
|
self.project = self.printer("project", attr="bold")
|
|
self.fail = self.printer("fail", fg="red")
|
|
|
|
|
|
def _ResolveOntoManifest(project: Project) -> str:
|
|
"""Resolve project's revisionExpr to a local tracking branch.
|
|
|
|
Falls back to the raw revisionExpr if ToLocal fails or raises GitError.
|
|
"""
|
|
try:
|
|
return project.GetRemote().ToLocal(project.revisionExpr)
|
|
except GitError:
|
|
return project.revisionExpr
|
|
|
|
|
|
class Rebase(Command):
|
|
COMMON = True
|
|
helpSummary = "Rebase local branches on upstream branch"
|
|
helpUsage = """
|
|
%prog {[<project>...] | -i <project>...}
|
|
"""
|
|
helpDescription = """
|
|
'%prog' uses git rebase to move local changes in the current topic branch to
|
|
the HEAD of the upstream history, useful when you have made commits in a topic
|
|
branch but need to incorporate new upstream changes "underneath" them.
|
|
"""
|
|
|
|
def _Options(self, p):
|
|
g = p.get_option_group("--quiet")
|
|
g.add_option(
|
|
"-i",
|
|
"--interactive",
|
|
action="store_true",
|
|
help="interactive rebase (single project only)",
|
|
)
|
|
|
|
p.add_option(
|
|
"--fail-fast",
|
|
action="store_true",
|
|
help="stop rebasing after first error is hit",
|
|
)
|
|
p.add_option(
|
|
"-f",
|
|
"--force-rebase",
|
|
action="store_true",
|
|
help="pass --force-rebase to git rebase",
|
|
)
|
|
p.add_option(
|
|
"--no-ff",
|
|
dest="ff",
|
|
default=True,
|
|
action="store_false",
|
|
help="pass --no-ff to git rebase",
|
|
)
|
|
p.add_option(
|
|
"--autosquash",
|
|
action="store_true",
|
|
help="pass --autosquash to git rebase",
|
|
)
|
|
p.add_option(
|
|
"--whitespace",
|
|
action="store",
|
|
metavar="WS",
|
|
help="pass --whitespace to git rebase",
|
|
)
|
|
p.add_option(
|
|
"--auto-stash",
|
|
action="store_true",
|
|
help="stash local modifications before starting",
|
|
)
|
|
p.add_option(
|
|
"-m",
|
|
"--onto-manifest",
|
|
action="store_true",
|
|
help="rebase onto the manifest version instead of upstream "
|
|
"HEAD (this helps to make sure the local tree stays "
|
|
"consistent if you previously synced to a manifest)",
|
|
)
|
|
|
|
def Execute(self, opt, args):
|
|
all_projects = self.GetProjects(
|
|
args, all_manifests=not opt.this_manifest_only
|
|
)
|
|
one_project = len(all_projects) == 1
|
|
|
|
if opt.interactive and not one_project:
|
|
logger.error(
|
|
"error: interactive rebase not supported with multiple projects"
|
|
)
|
|
|
|
if len(args) == 1:
|
|
logger.warning(
|
|
"note: project %s is mapped to more than one path", args[0]
|
|
)
|
|
|
|
return 1
|
|
|
|
# Setup the common git rebase args that we use for all projects.
|
|
common_args = ["rebase"]
|
|
if opt.whitespace:
|
|
common_args.append("--whitespace=%s" % opt.whitespace)
|
|
if opt.quiet:
|
|
common_args.append("--quiet")
|
|
if opt.force_rebase:
|
|
common_args.append("--force-rebase")
|
|
if not opt.ff:
|
|
common_args.append("--no-ff")
|
|
if opt.autosquash:
|
|
common_args.append("--autosquash")
|
|
if opt.interactive:
|
|
common_args.append("-i")
|
|
if opt.auto_stash:
|
|
common_args.append("--autostash")
|
|
|
|
config = self.manifest.manifestProject.config
|
|
out = RebaseColoring(config)
|
|
out.redirect(sys.stdout)
|
|
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
|
|
|
|
ret = 0
|
|
for project in all_projects:
|
|
if ret and opt.fail_fast:
|
|
break
|
|
|
|
cb = project.CurrentBranch
|
|
if not cb:
|
|
if one_project:
|
|
logger.error(
|
|
"error: project %s has a detached HEAD",
|
|
_RelPath(project),
|
|
)
|
|
return 1
|
|
# Ignore branches with detached HEADs.
|
|
continue
|
|
|
|
upbranch = project.GetBranch(cb)
|
|
if not upbranch.LocalMerge:
|
|
if one_project:
|
|
logger.error(
|
|
"error: project %s does not track any remote branches",
|
|
_RelPath(project),
|
|
)
|
|
return 1
|
|
# Ignore branches without remotes.
|
|
continue
|
|
|
|
args = common_args[:]
|
|
if opt.onto_manifest:
|
|
args.append("--onto")
|
|
args.append(_ResolveOntoManifest(project))
|
|
|
|
args.append(upbranch.LocalMerge)
|
|
|
|
out.project(
|
|
"project %s: rebasing %s -> %s",
|
|
_RelPath(project),
|
|
cb,
|
|
upbranch.LocalMerge,
|
|
)
|
|
out.nl()
|
|
out.flush()
|
|
|
|
if GitCommand(project, args).Wait() != 0:
|
|
ret += 1
|
|
continue
|
|
|
|
if ret:
|
|
msg_fmt = "%d projects had errors"
|
|
self.git_event_log.ErrorEvent(msg_fmt % (ret), msg_fmt)
|
|
out.fail(msg_fmt, ret)
|
|
out.nl()
|
|
|
|
return ret
|