Files
git-repo/subcmds/rebase.py
T
Gavin Mak 83428a9b26 rebase: delegate automatic stashing to Git
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>
2026-09-02 17:43:48 -07:00

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