Compare commits

...

18 Commits
v2.40 ... v2.42

Author SHA1 Message Date
Josip Sokcevic
5554572f02 sync: Introduce git checkout levels
If a repo manifest is updated so that project B is placed within a
project A, and if project A had content in new B's location in the old
checkout, then repo sync could break depending on checkout order, since
B can't be checked out before A.

This change introduces checkout levels which enforces right sequence of
checkouts while still allowing for parallel checkout. In an example
above, A will always be checked out first before B.

BUG=b:325119758
TEST=./run_tests, manual sync on ChromeOS repository

Change-Id: Ib3b5e4d2639ca56620a1e4c6bf76d7b1ab805250
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/410421
Tested-by: Josip Sokcevic <sokcevic@google.com>
Reviewed-by: Greg Edelston <gredelston@google.com>
Commit-Queue: Josip Sokcevic <sokcevic@google.com>
Reviewed-by: Gavin Mak <gavinmak@google.com>
2024-02-27 17:28:33 +00:00
Peter Collingbourne
97ca50f5f9 git_command: Return None from GetEventTargetPath() if set to empty string
If trace2.eventTarget was set to the empty string,
match git behavior and don't write a trace.

Bug: 319673783
Change-Id: I02b3884ad97551f8a9d7363c2cbe6b0adee6f73e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/410518
Reviewed-by: Josip Sokcevic <sokcevic@google.com>
Commit-Queue: Josip Sokcevic <sokcevic@google.com>
Tested-by: Peter Collingbourne <pcc@google.com>
2024-02-26 17:51:11 +00:00
Josip Sokcevic
8896b68926 trace: Save trace2 sid in REPO_TRACE file
git-trace2 events contain additional information what git is doing under
the hood, and repo doesn't have visibility into.

Instead of relying on timestamp information to match REPO_TRACE with
git-trace2 events, add SID information into REPO_TRACE.

Change-Id: I37672a3face81858072c7a3ce34ca3379199dab5
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/410280
Tested-by: Josip Sokcevic <sokcevic@google.com>
Reviewed-by: Gavin Mak <gavinmak@google.com>
Commit-Queue: Josip Sokcevic <sokcevic@google.com>
2024-02-22 20:55:09 +00:00
Greg Edelston
fec8cd6704 subcmds: sync: Remove deprecated _AUTO_GC
Opportunistic cleanup. It looks like this deprecated feature was slated
for deletion nearly a year ago.

Bug: None
Test: ./run_tests
Change-Id: I0bd0c0e6acbd1eaee1c0b4945c79038257d22f44
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/410198
Reviewed-by: Yiwei Zhang <yiwzhang@google.com>
Commit-Queue: Greg Edelston <gredelston@google.com>
Tested-by: Greg Edelston <gredelston@google.com>
2024-02-20 19:55:15 +00:00
Josip Sokcevic
b8139bdcf8 launcher: Set shebang to python3
Some (most?) Linux repos don't have /usr/bin/python, unless
python-is-python3 is installed. While package owners can adjust shebang,
we have seen an increase in number of bugs filed as extra steps are
required.

Per PEP-0394, python3 is acceptable and should be available if python3
is supported. We no longer support python2, and repo no longer works
with python2, so this change makes that explicit.

Bug: 40014585
Change-Id: I9aed90fd470ef601bd33bd596af3df69da69ef5d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/407497
Commit-Queue: Josip Sokcevic <sokcevic@google.com>
Reviewed-by: Jason Chang <jasonnc@google.com>
Reviewed-by: Gavin Mak <gavinmak@google.com>
Reviewed-by: Максим Паймушкин <maxim.paymushkin@gmail.com>
Tested-by: Josip Sokcevic <sokcevic@google.com>
2024-02-07 20:44:32 +00:00
Jason Chang
26fa3180fb sync: ensure RepoChangedException propagated
Prior to this change RepoChangedException would be caught and re-rasied
as a different exception. This would prevent RepoChangedException
handler from running in main.py

Bug: b/323232806
Change-Id: I9055ff95d439d6ff225206c5bf1755cc718bcfcc
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/407144
Tested-by: Josip Sokcevic <sokcevic@google.com>
Reviewed-by: Josip Sokcevic <sokcevic@google.com>
Commit-Queue: Josip Sokcevic <sokcevic@google.com>
2024-02-06 18:46:19 +00:00
Yiwei Zhang
d379e77f44 stop passing project to UpdateManifestError
UpdateManifestError inherits from RepoExitError which inherits
from BaseRepoError. None of them takes project as kwargs
causing the error like "UpdateManifestError() takes no keyword
arguments" in b/317183321

[1]: https://gerrit.googlesource.com/git-repo/+/449b23b698d7d4b13909667a49a0698eb495eeaa/error.py#144

Bug: b/317183321
Change-Id: I64c3dc502027f9dda56a0824f2712364b4502934
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/398997
Commit-Queue: Yiwei Zhang <yiwzhang@google.com>
Tested-by: Yiwei Zhang <yiwzhang@google.com>
Reviewed-by: Josip Sokcevic <sokcevic@google.com>
Reviewed-by: Jason Chang <jasonnc@google.com>
2024-02-02 18:35:13 +00:00
Josip Sokcevic
4217a82bec project: Rename if deletion fails
If a project contains files not owned by the current user, remove will
fail. In order to ensure repo sync continues to work, rename the
affected project instead, and let user know about it.

Bug: 321273512
Change-Id: I0779d61fc67042308a0226adea7d98167252a5d3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/404372
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Josip Sokcevic <sokcevic@google.com>
Commit-Queue: Josip Sokcevic <sokcevic@google.com>
2024-01-25 21:32:58 +00:00
Tomasz Wasilczyk
208f344950 Clean up remaining repo sync log spam.
There are still some verbose messages (e.g. "remote: ...") when doing
repo sync after a couple days. Let's hide them behind verbose flag.

Bug: N/A
Test: repo sync
Change-Id: I1408472c95ed80d9555adfe8f92211245c03cf41
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/400855
Reviewed-by: Josip Sokcevic <sokcevic@google.com>
Tested-by: Tomasz Wasilczyk <twasilczyk@google.com>
Commit-Queue: Tomasz Wasilczyk <twasilczyk@google.com>
2024-01-05 21:40:43 +00:00
Mike Frysinger
138c8a9ff5 docs: fix some grammar typos
Change-Id: Ie1a32cda67f94b0a2b3329b1be9e03dcbedf39cc
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/400917
Reviewed-by: Josip Sokcevic <sokcevic@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
Commit-Queue: Mike Frysinger <vapier@google.com>
2024-01-04 17:19:33 +00:00
Josip Sokcevic
9b57aa00f6 project: Check references during sync
Symbolic references need to be checked each time sync is called, not
only for newly created repositories. For example, it is possible to
change a project name to the already existing name, and that will result
in a broken git setup without this patch: refs/ will still point to the
old repository, whereas all objects will point to the new repository.

Bug: 40013418
Change-Id: I596d29d182986804989f0562fb45090224549b0f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/395798
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Josip Sokcevic <sokcevic@google.com>
Commit-Queue: Josip Sokcevic <sokcevic@google.com>
2024-01-03 22:26:07 +00:00
Vitalii Dmitriev
b1d1ece2fb tests: setup user identity for tests
After a6413f5d a GitCommandError is raised.

Since there were no user identity were set up,
it fails:
 - ReviewableBranchTests from test_project.py
 - ResolveRepoRev and CheckRepoRev from test_wrapper.py

Test: ./run_tests
Change-Id: Id7f5772afe22c77fc4c8f8f0b8be1b627ed42187
Signed-off-by: Vitalii Dmitriev <vitalii.dmitriev@unikie.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/398658
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Vitalii Dmitriev <dmit.vitalii@gmail.com>
Commit-Queue: Vitalii Dmitriev <dmit.vitalii@gmail.com>
2023-12-20 19:04:57 +00:00
Vitalii Dmitriev
449b23b698 manifest_xml: fix url normalization for inits and remotes
Before the change, repo normalizes the urls
with a following format only:

    git@github.com:foo/bar

It doesn't cover the following case:

   <remote name="org" fetch="git@github.com:org/" />
   <project name="somerepo" remote="org" />

Results to:
   error: Cannot fetch somerepo
     from ssh://git@github.com/org/git@github.com:org/somerepo

Current change fixes it by normalizing this format:

    git@github.com:foo

Test: ./run_tests tests/test_manifest_xml.py
Change-Id: I1ad0f5df0d52c0b7229ba4c9a4db4eecb5c1a003
Signed-off-by: Vitalii Dmitriev <vitalii.dmitriev@unikie.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/398337
Commit-Queue: Vitalii Dmitriev <dmit.vitalii@gmail.com>
Tested-by: Vitalii Dmitriev <dmit.vitalii@gmail.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2023-12-20 07:38:49 +00:00
Josip Sokcevic
e5fb6e585f git_trace2: Add socket timeout
repo blocks indefinitely until trace collector receives trace events,
which is not desired. This change adds a fixed timeout to connect and
send operations. It is possible that some events will be lost. repo logs
any failed trace operation.

Bug: b/316227772
Change-Id: I017636421b8e22ae3fcbab9e4eb2bee1d4fbbff4
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/398717
Tested-by: Josip Sokcevic <sokcevic@google.com>
Commit-Queue: Josip Sokcevic <sokcevic@google.com>
Reviewed-by: Jason Chang <jasonnc@google.com>
2023-12-19 19:38:52 +00:00
Mike Frysinger
48e4137eba manifest_xml: do not allow / before : in scp-like syntax
Since git doesn't treat these as ssh:// URIs, we shouldn't either.

Bug: https://g-issues.gerritcodereview.com/issues/40010331
Change-Id: I001f49be30395187cac447d09cb5a6c29e95768b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/398517
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jason Chang <jasonnc@google.com>
Commit-Queue: Mike Frysinger <vapier@google.com>
2023-12-19 18:00:44 +00:00
Peter Kjellerstedt
172c58398b repo: Drop reexec of python3 from check_python_version()
This simplifies check_python_version() since there is no point in trying
to fall back to python3, as we are already running using some Python 3
interpreter.

Change-Id: I9dfdd002b4ef5567e064d3d6ca98ee1f3410fd48
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/397759
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
Commit-Queue: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
2023-12-15 06:49:27 +00:00
Peter Kjellerstedt
aa506db8a7 repo: Remove Python 2 compatibility code
Change-Id: I1f5c691bf94f255442eea95e59ddd93db6213ad8
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/397758
Reviewed-by: Mike Frysinger <vapier@google.com>
Commit-Queue: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
Tested-by: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
2023-12-15 06:48:48 +00:00
Peter Kjellerstedt
14c61d2c9d repo: Remove a Python 2 related comment
The EnvironmentError exception was changed to OSError in commit
ae824fb2fc.

Change-Id: I1b4ff742af409ec848131e82900e885c9f089f0c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/397757
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
Commit-Queue: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
2023-12-14 18:31:51 +00:00
11 changed files with 221 additions and 104 deletions

View File

@@ -33,9 +33,8 @@ you have newer versions installed, your choices are:
* Modify the [repo launcher]'s shebang to suite your environment.
* Download an older version of the [repo launcher] and don't upgrade it.
Be aware that there is no guarantee old repo launchers are WILL work with
current versions of repo. Bug reports using old launchers will not be
accepted.
Be aware that we do not guarantee old repo launchers will work with current
versions of repo. Bug reports using old launchers will not be accepted.
## When to drop support

View File

@@ -135,6 +135,8 @@ def GetEventTargetPath():
if retval == 0:
# Strip trailing carriage-return in path.
path = p.stdout.rstrip("\n")
if path == "":
return None
elif retval != 1:
# `git config --get` is documented to produce an exit status of `1`
# if the requested variable is not present in the configuration.

View File

@@ -38,6 +38,8 @@ import tempfile
import threading
# Timeout when sending events via socket (applies to connect, send)
SOCK_TIMEOUT = 0.5 # in seconds
# BaseEventLog __init__ Counter that is consistent within the same process
p_init_count = 0
@@ -296,6 +298,7 @@ class BaseEventLog:
with socket.socket(
socket.AF_UNIX, socket.SOCK_STREAM
) as sock:
sock.settimeout(SOCK_TIMEOUT)
sock.connect(path)
self._WriteLog(sock.sendall)
return f"af_unix:stream:{path}"

11
main.py
View File

@@ -270,10 +270,14 @@ class _Repo:
self._PrintHelp(short=True)
return 1
run = lambda: self._RunLong(name, gopts, argv) or 0
git_trace2_event_log = EventLog()
run = (
lambda: self._RunLong(name, gopts, argv, git_trace2_event_log) or 0
)
with Trace(
"starting new command: %s",
"starting new command: %s [sid=%s]",
", ".join([name] + argv),
git_trace2_event_log.full_sid,
first_trace=True,
):
if gopts.trace_python:
@@ -290,12 +294,11 @@ class _Repo:
result = run()
return result
def _RunLong(self, name, gopts, argv):
def _RunLong(self, name, gopts, argv, git_trace2_event_log):
"""Execute the (longer running) requested subcommand."""
result = 0
SetDefaultColoring(gopts.color)
git_trace2_event_log = EventLog()
outer_client = RepoClient(self.repodir)
repo_client = outer_client
if gopts.submanifest_path:

View File

@@ -133,8 +133,8 @@ def normalize_url(url: str) -> str:
url = url.rstrip("/")
parsed_url = urllib.parse.urlparse(url)
# This matches patterns like "git@github.com:foo/bar".
scp_like_url_re = r"^[^:]+@[^:]+:[^/]+/"
# This matches patterns like "git@github.com:foo".
scp_like_url_re = r"^[^/:]+@[^/:]+:[^/]+"
# If our URL is missing a schema and matches git's
# SCP-like syntax we should convert it to a proper

View File

@@ -1277,7 +1277,20 @@ class Project:
if is_new:
self._InitGitDir(force_sync=force_sync, quiet=quiet)
else:
self._UpdateHooks(quiet=quiet)
try:
# At this point, it's possible that gitdir points to an old
# objdir (e.g. name changed, but objdir exists). Check
# references to ensure that's not the case. See
# https://issues.gerritcodereview.com/40013418 for more
# details.
self._CheckDirReference(self.objdir, self.gitdir)
self._UpdateHooks(quiet=quiet)
except GitError as e:
if not force_sync:
raise e
# Let _InitGitDir fix the issue, force_sync is always True here.
self._InitGitDir(force_sync=True, quiet=quiet)
self._InitRemote()
if self.UseAlternates:
@@ -1623,9 +1636,9 @@ class Project:
elif pub == head:
# All published commits are merged, and thus we are a
# strict subset. We can fast-forward safely.
syncbuf.later1(self, _doff)
syncbuf.later1(self, _doff, not verbose)
if submodules:
syncbuf.later1(self, _dosubmodules)
syncbuf.later1(self, _dosubmodules, not verbose)
return
# Examine the local commits not in the remote. Find the
@@ -1684,10 +1697,10 @@ class Project:
def _dorebase():
self._Rebase(upstream="%s^1" % last_mine, onto=revid)
syncbuf.later2(self, _dorebase)
syncbuf.later2(self, _dorebase, not verbose)
if submodules:
syncbuf.later2(self, _dosubmodules)
syncbuf.later2(self, _docopyandlink)
syncbuf.later2(self, _dosubmodules, not verbose)
syncbuf.later2(self, _docopyandlink, not verbose)
elif local_changes:
try:
self._ResetHard(revid)
@@ -1698,9 +1711,9 @@ class Project:
fail(e)
return
else:
syncbuf.later1(self, _doff)
syncbuf.later1(self, _doff, not verbose)
if submodules:
syncbuf.later1(self, _dosubmodules)
syncbuf.later1(self, _dosubmodules, not verbose)
def AddCopyFile(self, src, dest, topdir):
"""Mark |src| for copying to |dest| (relative to |topdir|).
@@ -1835,7 +1848,7 @@ class Project:
platform_utils.remove(path)
except OSError as e:
if e.errno != errno.ENOENT:
logger.error("error: %s: Failed to remove: %s", path, e)
logger.warning("%s: Failed to remove: %s", path, e)
failed = True
errors.append(e)
dirs[:] = [
@@ -1854,7 +1867,7 @@ class Project:
platform_utils.remove(d)
except OSError as e:
if e.errno != errno.ENOENT:
logger.error("error: %s: Failed to remove: %s", d, e)
logger.warning("%s: Failed to remove: %s", d, e)
failed = True
errors.append(e)
elif not platform_utils.listdir(d):
@@ -1862,18 +1875,30 @@ class Project:
platform_utils.rmdir(d)
except OSError as e:
if e.errno != errno.ENOENT:
logger.error("error: %s: Failed to remove: %s", d, e)
logger.warning("%s: Failed to remove: %s", d, e)
failed = True
errors.append(e)
if failed:
logger.error(
"error: %s: Failed to delete obsolete checkout.",
self.RelPath(local=False),
rename_path = (
f"{self.worktree}_repo_to_be_deleted_{int(time.time())}"
)
logger.error(
" Remove manually, then run `repo sync -l`.",
)
raise DeleteWorktreeError(aggregate_errors=errors)
try:
platform_utils.rename(self.worktree, rename_path)
logger.warning(
"warning: renamed %s to %s. You can delete it, but you "
"might need elevated permissions (e.g. root)",
self.worktree,
rename_path,
)
# Rename successful! Clear the errors.
errors = []
except OSError:
logger.error(
"%s: Failed to delete obsolete checkout.\n",
" Remove manually, then run `repo sync -l`.",
self.RelPath(local=False),
)
raise DeleteWorktreeError(aggregate_errors=errors)
# Try deleting parent dirs if they are empty.
path = self.worktree
@@ -2870,10 +2895,12 @@ class Project:
if GitCommand(self, cmd).Wait() != 0:
raise GitError(f"{self.name} rebase {upstream} ", project=self.name)
def _FastForward(self, head, ffonly=False):
def _FastForward(self, head, ffonly=False, quiet=True):
cmd = ["merge", "--no-stat", head]
if ffonly:
cmd.append("--ff-only")
if quiet:
cmd.append("-q")
if GitCommand(self, cmd).Wait() != 0:
raise GitError(f"{self.name} merge {head} ", project=self.name)
@@ -3746,17 +3773,20 @@ class _Failure:
class _Later:
def __init__(self, project, action):
def __init__(self, project, action, quiet):
self.project = project
self.action = action
self.quiet = quiet
def Run(self, syncbuf):
out = syncbuf.out
out.project("project %s/", self.project.RelPath(local=False))
out.nl()
if not self.quiet:
out.project("project %s/", self.project.RelPath(local=False))
out.nl()
try:
self.action()
out.nl()
if not self.quiet:
out.nl()
return True
except GitError:
out.nl()
@@ -3792,11 +3822,11 @@ class SyncBuffer:
self._failures.append(_Failure(project, err))
self._MarkUnclean()
def later1(self, project, what):
self._later_queue1.append(_Later(project, what))
def later1(self, project, what, quiet):
self._later_queue1.append(_Later(project, what, quiet))
def later2(self, project, what):
self._later_queue2.append(_Later(project, what))
def later2(self, project, what, quiet):
self._later_queue2.append(_Later(project, what, quiet))
def Finish(self):
self._PrintMessages()

45
repo
View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
#
# Copyright (C) 2008 The Android Open Source Project
#
@@ -79,7 +79,7 @@ def check_python_version():
major = ver.major
minor = ver.minor
# Try to re-exec the version specific Python 3 if needed.
# Try to re-exec the version specific Python if needed.
if (major, minor) < MIN_PYTHON_VERSION_SOFT:
# Python makes releases ~once a year, so try our min version +10 to help
# bridge the gap. This is the fallback anyways so perf isn't critical.
@@ -96,36 +96,10 @@ def check_python_version():
break
reexec(f"python{min_major}.{min_minor - inc}")
# Try the generic Python 3 wrapper, but only if it's new enough. If it
# isn't, we want to just give up below and make the user resolve things.
try:
proc = subprocess.Popen(
[
"python3",
"-c",
"import sys; "
"print(sys.version_info.major, sys.version_info.minor)",
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
(output, _) = proc.communicate()
python3_ver = tuple(int(x) for x in output.decode("utf-8").split())
except (OSError, subprocess.CalledProcessError):
python3_ver = None
# If the python3 version looks like it's new enough, give it a try.
if (
python3_ver
and python3_ver >= MIN_PYTHON_VERSION_HARD
and python3_ver != (major, minor)
):
reexec("python3")
# We're still here, so diagnose things for the user.
if (major, minor) < MIN_PYTHON_VERSION_HARD:
print(
"repo: error: Python 3 version is too old; "
"repo: error: Python version is too old; "
"Please use Python {}.{} or newer.".format(
*MIN_PYTHON_VERSION_HARD
),
@@ -150,7 +124,7 @@ if not REPO_REV:
BUG_URL = "https://issues.gerritcodereview.com/issues/new?component=1370071"
# increment this whenever we make important changes to this script
VERSION = (2, 40)
VERSION = (2, 42)
# increment this if the MAINTAINER_KEYS block is modified
KEYRING_VERSION = (2, 3)
@@ -1245,7 +1219,6 @@ class Requirements:
with open(path, "rb") as f:
data = f.read()
except OSError:
# NB: EnvironmentError is used for Python 2 & 3 compatibility.
# If we couldn't open the file, assume it's an old source tree.
return None
@@ -1364,13 +1337,9 @@ def _Version():
print(f"git {ParseGitVersion().full}")
print(f"Python {sys.version}")
uname = platform.uname()
if sys.version_info.major < 3:
# Python 3 returns a named tuple, but Python 2 is simpler.
print(uname)
else:
print(f"OS {uname.system} {uname.release} ({uname.version})")
processor = uname.processor if uname.processor else "unknown"
print(f"CPU {uname.machine} ({processor})")
print(f"OS {uname.system} {uname.release} ({uname.version})")
processor = uname.processor if uname.processor else "unknown"
print(f"CPU {uname.machine} ({processor})")
print("Bug reports:", BUG_URL)
sys.exit(0)

View File

@@ -21,6 +21,7 @@ import multiprocessing
import netrc
import optparse
import os
from pathlib import Path
import sys
import tempfile
import time
@@ -82,16 +83,50 @@ from wrapper import Wrapper
_ONE_DAY_S = 24 * 60 * 60
# Env var to implicitly turn auto-gc back on. This was added to allow a user to
# revert a change in default behavior in v2.29.9. Remove after 2023-04-01.
_REPO_AUTO_GC = "REPO_AUTO_GC"
_AUTO_GC = os.environ.get(_REPO_AUTO_GC) == "1"
_REPO_ALLOW_SHALLOW = os.environ.get("REPO_ALLOW_SHALLOW")
logger = RepoLogger(__file__)
def _SafeCheckoutOrder(checkouts: List[Project]) -> List[List[Project]]:
"""Generate a sequence of checkouts that is safe to perform. The client
should checkout everything from n-th index before moving to n+1.
This is only useful if manifest contains nested projects.
E.g. if foo, foo/bar and foo/bar/baz are project paths, then foo needs to
finish before foo/bar can proceed, and foo/bar needs to finish before
foo/bar/baz."""
res = [[]]
current = res[0]
# depth_stack contains a current stack of parent paths.
depth_stack = []
# checkouts are iterated in asc order by relpath. That way, it can easily be
# determined if the previous checkout is parent of the current checkout.
for checkout in sorted(checkouts, key=lambda x: x.relpath):
checkout_path = Path(checkout.relpath)
while depth_stack:
try:
checkout_path.relative_to(depth_stack[-1])
except ValueError:
# Path.relative_to returns ValueError if paths are not relative.
# TODO(sokcevic): Switch to is_relative_to once min supported
# version is py3.9.
depth_stack.pop()
else:
if len(depth_stack) >= len(res):
# Another depth created.
res.append([])
break
current = res[len(depth_stack)]
current.append(checkout)
depth_stack.append(checkout_path)
return res
class _FetchOneResult(NamedTuple):
"""_FetchOne return value.
@@ -618,7 +653,7 @@ later is required to fix a server side protocol bug.
if not use_super:
continue
m.superproject.SetQuiet(opt.quiet)
m.superproject.SetQuiet(not opt.verbose)
print_messages = git_superproject.PrintMessages(
opt.use_superproject, m
)
@@ -1040,15 +1075,21 @@ later is required to fix a server side protocol bug.
pm.update(msg=project.name)
return ret
proc_res = self.ExecuteInParallel(
opt.jobs_checkout,
functools.partial(
self._CheckoutOne, opt.detach_head, opt.force_sync, opt.verbose
),
all_projects,
callback=_ProcessResults,
output=Progress("Checking out", len(all_projects), quiet=opt.quiet),
)
for projects in _SafeCheckoutOrder(all_projects):
proc_res = self.ExecuteInParallel(
opt.jobs_checkout,
functools.partial(
self._CheckoutOne,
opt.detach_head,
opt.force_sync,
opt.verbose,
),
projects,
callback=_ProcessResults,
output=Progress(
"Checking out", len(all_projects), quiet=opt.quiet
),
)
self._local_sync_state.Save()
return proc_res and not err_results
@@ -1501,7 +1542,7 @@ later is required to fix a server side protocol bug.
buf = TeeStringIO(sys.stdout)
try:
result = mp.Sync_NetworkHalf(
quiet=opt.quiet,
quiet=not opt.verbose,
output_redir=buf,
verbose=opt.verbose,
current_branch_only=self._GetCurrentBranchOnly(
@@ -1544,9 +1585,7 @@ later is required to fix a server side protocol bug.
mp, event_log.TASK_SYNC_LOCAL, start, time.time(), clean
)
if not clean:
raise UpdateManifestError(
aggregate_errors=errors, project=mp.name
)
raise UpdateManifestError(aggregate_errors=errors)
self._ReloadManifest(manifest_name, mp.manifest)
def ValidateOptions(self, opt, args):
@@ -1577,16 +1616,6 @@ later is required to fix a server side protocol bug.
if opt.prune is None:
opt.prune = True
if opt.auto_gc is None and _AUTO_GC:
logger.error(
"Will run `git gc --auto` because %s is set. %s is deprecated "
"and will be removed in a future release. Use `--auto-gc` "
"instead.",
_REPO_AUTO_GC,
_REPO_AUTO_GC,
)
opt.auto_gc = True
def _ValidateOptionsWithManifest(self, opt, mp):
"""Like ValidateOptions, but after we've updated the manifest.
@@ -1630,7 +1659,7 @@ later is required to fix a server side protocol bug.
errors = []
try:
self._ExecuteHelper(opt, args, errors)
except RepoExitError:
except (RepoExitError, RepoChangedException):
raise
except (KeyboardInterrupt, Exception) as e:
raise RepoUnhandledExceptionError(e, aggregate_errors=errors)

View File

@@ -72,3 +72,12 @@ def tmp_home_dir(monkeypatch, tmp_path_factory):
the function scope.
"""
return _set_home(monkeypatch, tmp_path_factory.mktemp("home"))
@pytest.fixture(autouse=True)
def setup_user_identity(monkeysession, scope="session"):
"""Set env variables for author and committer name and email."""
monkeysession.setenv("GIT_AUTHOR_NAME", "Foo Bar")
monkeysession.setenv("GIT_COMMITTER_NAME", "Foo Bar")
monkeysession.setenv("GIT_AUTHOR_EMAIL", "foo@bar.baz")
monkeysession.setenv("GIT_COMMITTER_EMAIL", "foo@bar.baz")

View File

@@ -1139,6 +1139,20 @@ class NormalizeUrlTests(ManifestParseTestCase):
"http://foo.com/bar/baz", manifest_xml.normalize_url(url)
)
url = "http://foo.com/bar/"
self.assertEqual("http://foo.com/bar", manifest_xml.normalize_url(url))
def test_has_leading_slash(self):
"""SCP-like syntax except a / comes before the : which git disallows."""
url = "/git@foo.com:bar/baf"
self.assertEqual(url, manifest_xml.normalize_url(url))
url = "gi/t@foo.com:bar/baf"
self.assertEqual(url, manifest_xml.normalize_url(url))
url = "git@fo/o.com:bar/baf"
self.assertEqual(url, manifest_xml.normalize_url(url))
def test_has_no_scheme(self):
"""Deal with cases where we have no scheme, but we also
aren't dealing with the git SCP-like syntax
@@ -1146,9 +1160,15 @@ class NormalizeUrlTests(ManifestParseTestCase):
url = "foo.com/baf/bat"
self.assertEqual(url, manifest_xml.normalize_url(url))
url = "foo.com/baf"
self.assertEqual(url, manifest_xml.normalize_url(url))
url = "git@foo.com/baf/bat"
self.assertEqual(url, manifest_xml.normalize_url(url))
url = "git@foo.com/baf"
self.assertEqual(url, manifest_xml.normalize_url(url))
url = "/file/path/here"
self.assertEqual(url, manifest_xml.normalize_url(url))
@@ -1157,3 +1177,30 @@ class NormalizeUrlTests(ManifestParseTestCase):
self.assertEqual(
"ssh://git@foo.com/bar/baf", manifest_xml.normalize_url(url)
)
url = "git@foo.com:bar/"
self.assertEqual(
"ssh://git@foo.com/bar", manifest_xml.normalize_url(url)
)
def test_remote_url_resolution(self):
remote = manifest_xml._XmlRemote(
name="foo",
fetch="git@github.com:org2/",
manifestUrl="git@github.com:org2/custom_manifest.git",
)
self.assertEqual("ssh://git@github.com/org2", remote.resolvedFetchUrl)
remote = manifest_xml._XmlRemote(
name="foo",
fetch="ssh://git@github.com/org2/",
manifestUrl="git@github.com:org2/custom_manifest.git",
)
self.assertEqual("ssh://git@github.com/org2", remote.resolvedFetchUrl)
remote = manifest_xml._XmlRemote(
name="foo",
fetch="git@github.com:org2/",
manifestUrl="ssh://git@github.com/org2/custom_manifest.git",
)
self.assertEqual("ssh://git@github.com/org2", remote.resolvedFetchUrl)

View File

@@ -304,6 +304,32 @@ class LocalSyncState(unittest.TestCase):
self.assertEqual(self.state.GetFetchTime(projA), 5)
class SafeCheckoutOrder(unittest.TestCase):
def test_no_nested(self):
p_f = mock.MagicMock(relpath="f")
p_foo = mock.MagicMock(relpath="foo")
out = sync._SafeCheckoutOrder([p_f, p_foo])
self.assertEqual(out, [[p_f, p_foo]])
def test_basic_nested(self):
p_foo = p_foo = mock.MagicMock(relpath="foo")
p_foo_bar = mock.MagicMock(relpath="foo/bar")
out = sync._SafeCheckoutOrder([p_foo, p_foo_bar])
self.assertEqual(out, [[p_foo], [p_foo_bar]])
def test_complex_nested(self):
p_foo = mock.MagicMock(relpath="foo")
p_foo_bar = mock.MagicMock(relpath="foo/bar")
p_foo_bar_baz_baq = mock.MagicMock(relpath="foo/bar/baz/baq")
p_bar = mock.MagicMock(relpath="bar")
out = sync._SafeCheckoutOrder(
[p_foo_bar_baz_baq, p_foo, p_foo_bar, p_bar]
)
self.assertEqual(
out, [[p_bar, p_foo], [p_foo_bar], [p_foo_bar_baz_baq]]
)
class GetPreciousObjectsState(unittest.TestCase):
"""Tests for _GetPreciousObjectsState."""