Compare commits

..

11 Commits

Author SHA1 Message Date
b750b48f50 init: add --manifest-depth for shallow manifest clone
People rarely care about the history of the manifest repo.  Add a
parameter to specify depth for the manifest.

For now, make the default behavior the same as the current behavior.  At
a future date, the default will be changed to 1.  People who need the
full history should begin passing --manifest-depth=0 to preserve the
behavior when the default changes.

We can't reuse the existing --depth option because that applies to
all projects we clone, not just the manifest repo.

Bug: https://crbug.com/gerrit/16193, https://crbug.com/gerrit/16358
Change-Id: I9130fed3eaed656435c778a85cfe9d04e3a4a6a0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/349814
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Xin Li <delphij@google.com>
2022-10-27 21:59:09 +00:00
6c8b894d8d Revert "init: change --depth default to 1 for manifest repo"
This reverts commit 076d54652e.

Reason for revert: crbug.com/gerrit/16358

Change-Id: I2970eb50677cca69786f71edffe4aa5271cf139f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/349834
Reviewed-by: Sam Saccone <samccone@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Xin Li <delphij@google.com>
2022-10-27 21:52:02 +00:00
b6cfa09500 sync: uninitialized variable on mirror sync failure
When repo sync fails, if the workspace is a mirror, an uninitialized
variable is referenced.

Bug: crbug.com/gerrit/16356
Change-Id: I1dba9f92319b9cbfd18460327560a395c88a089f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/349654
Reviewed-by: Sam Saccone <samccone@google.com>
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-10-26 23:13:02 +00:00
78dcd3799b sync: do not require python 3.9
Use pre-3.9 syntax for NamedTuple, so that users do not need to have
python 3.9 or later installed.

Bug: b/255632143, crbug.com/gerrit/16355
Test: manually verified with python 3.8
Change-Id: I488d2d5267ed98d5c55c233cc789e629f1911c9d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/349395
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2022-10-25 22:46:47 +00:00
acc4c857a0 sync: only use --cruft when git supports it.
git gc --cruft was added in 2.37.0.

Bug: https://crbug.com/gerrit/16270
Change-Id: I71e46741e33472a92f16d6f11c51a23e1e55d869
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/346577
Reviewed-by: Emily Shaffer <emilyshaffer@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-09-22 19:18:48 +00:00
a39af3d432 project: Add a missing call to _CopyAndLinkFiles
If a file that is copied using a <copyfile> tag is modified and not
committed or if it is committed to a detached head, then running `repo
sync` would update the target file as expected. However, if the
modified file is committed to a local branch, then running `repo sync'
would not update the target file as expected.

Change-Id: Ic98e37d1c2e51fd1bf15abf149c7d06190cfd6d2
Signed-off-by: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/344475
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-09-20 09:24:01 +00:00
4cdfdb7734 manifest: allow extend-project to override dest-branch and upstream
Bug: https://crbug.com/gerrit/16238
Change-Id: Id6eff34791525b3df690e160c911c0286331984b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/345144
Tested-by: Erik Elmeke <erik@haleytek.corp-partner.google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-09-20 04:25:02 +00:00
1eddca8476 sync: use namedtuples for internal return values
Replace tuple returns with namedtuples, to simplify adding new fields.

Extend the Sync_NetworkHalf return value to:
 - success: True if successful (the former return value)
 - remote_fetched: True if we called `git fetch`

Change-Id: If63c24c2f849523f77fa19c05bbf23a5e9a20ba9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/344534
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-09-19 22:03:18 +00:00
aefa4d3a29 sync: incorporate review feedback.
This incorporates feedback from
https://gerrit-review.googlesource.com/c/git-repo/+/345114

Change-Id: I04433d6435b967858f1ffb355217d90bc48c1e5d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/345894
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-09-19 22:03:18 +00:00
4ba29c42ca diffmanifests: Handle Missing Projects in Repo Workspace
By default there are 4 categories in the diffmanifests
api puts the diffs in to - added, removed, changed and unreachable

Example of command - repo diffmanifests 1.xml 2.xml

added - list down the projects present in second manifest but not in
first
removed - list down the projects present in first but not in
second
changed - list down the changes and the differences for each project
unreachable - when it encounters revision value in a project is incorrect

But, when there are projects present in both manifests and could not
find in local workspace where we have cloned the repo(because of
different/subset manifest xml) - this will create unhandled exception

Now we have added a 5th category called 'missing' - where in such
cases it will handle the scenario and print the log for user

Example:
added projects :
        project_2 at revision e6c8a59832c05dc4b6a68cee6bc0feb832181725

removed projects :
        project_1 at revision e6c8a59832c05dc4b6a68cee6bc0feb832181725

changed projects :
        project_3 changed from 3bb890e1286f04e84d505e5db48e0ada89892331 to e434b3736f11537c67590fefadfe4495895e9785

missing projects :
        project_4

Change-Id: I244e8389bff7e95664c29d3dcb61e22308e3a573
Signed-off-by: Shashank Devaraj <shashankkarthik@gmail.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/344774
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-09-15 17:42:08 +00:00
45ef9011c2 update-manpages: force use of active interp
Since the repo wrapper uses #!/usr/bin/python, use the python3 that
this wrapper is actively using.

Change-Id: I03d1e54418d18a504eec628e549b4cc233621c45
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/345294
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-09-12 19:27:09 +00:00
12 changed files with 225 additions and 65 deletions

View File

@ -105,6 +105,8 @@ following DTD:
<!ATTLIST extend-project groups CDATA #IMPLIED> <!ATTLIST extend-project groups CDATA #IMPLIED>
<!ATTLIST extend-project revision CDATA #IMPLIED> <!ATTLIST extend-project revision CDATA #IMPLIED>
<!ATTLIST extend-project remote CDATA #IMPLIED> <!ATTLIST extend-project remote CDATA #IMPLIED>
<!ATTLIST extend-project dest-branch CDATA #IMPLIED>
<!ATTLIST extend-project upstream CDATA #IMPLIED>
<!ELEMENT remove-project EMPTY> <!ELEMENT remove-project EMPTY>
<!ATTLIST remove-project name CDATA #REQUIRED> <!ATTLIST remove-project name CDATA #REQUIRED>
@ -423,6 +425,12 @@ project. Same syntax as the corresponding element of `project`.
Attribute `remote`: If specified, overrides the remote of the original Attribute `remote`: If specified, overrides the remote of the original
project. Same syntax as the corresponding element of `project`. project. Same syntax as the corresponding element of `project`.
Attribute `dest-branch`: If specified, overrides the dest-branch of the original
project. Same syntax as the corresponding element of `project`.
Attribute `upstream`: If specified, overrides the upstream of the original
project. Same syntax as the corresponding element of `project`.
### Element annotation ### Element annotation
Zero or more annotation elements may be specified as children of a Zero or more annotation elements may be specified as children of a

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "August 2022" "repo gitc-init" "Repo Manual" .TH REPO "1" "October 2022" "repo gitc-init" "Repo Manual"
.SH NAME .SH NAME
repo \- repo gitc-init - manual page for repo gitc-init repo \- repo gitc-init - manual page for repo gitc-init
.SH SYNOPSIS .SH SYNOPSIS
@ -48,7 +48,7 @@ create a git checkout of the manifest repo
.TP .TP
\fB\-\-manifest\-depth\fR=\fI\,DEPTH\/\fR \fB\-\-manifest\-depth\fR=\fI\,DEPTH\/\fR
create a shallow clone of the manifest repo with given create a shallow clone of the manifest repo with given
depth; see git clone (default: 1) depth (0 for full clone); see git clone (default: 0)
.SS Manifest (only) checkout options: .SS Manifest (only) checkout options:
.TP .TP
\fB\-\-current\-branch\fR \fB\-\-current\-branch\fR

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "August 2022" "repo init" "Repo Manual" .TH REPO "1" "October 2022" "repo init" "Repo Manual"
.SH NAME .SH NAME
repo \- repo init - manual page for repo init repo \- repo init - manual page for repo init
.SH SYNOPSIS .SH SYNOPSIS
@ -48,7 +48,7 @@ create a git checkout of the manifest repo
.TP .TP
\fB\-\-manifest\-depth\fR=\fI\,DEPTH\/\fR \fB\-\-manifest\-depth\fR=\fI\,DEPTH\/\fR
create a shallow clone of the manifest repo with given create a shallow clone of the manifest repo with given
depth; see git clone (default: 1) depth (0 for full clone); see git clone (default: 0)
.SS Manifest (only) checkout options: .SS Manifest (only) checkout options:
.TP .TP
\fB\-c\fR, \fB\-\-current\-branch\fR \fB\-c\fR, \fB\-\-current\-branch\fR

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo manifest" "Repo Manual" .TH REPO "1" "October 2022" "repo manifest" "Repo Manual"
.SH NAME .SH NAME
repo \- repo manifest - manual page for repo manifest repo \- repo manifest - manual page for repo manifest
.SH SYNOPSIS .SH SYNOPSIS
@ -190,6 +190,8 @@ CDATA #IMPLIED>
<!ATTLIST extend\-project groups CDATA #IMPLIED> <!ATTLIST extend\-project groups CDATA #IMPLIED>
<!ATTLIST extend\-project revision CDATA #IMPLIED> <!ATTLIST extend\-project revision CDATA #IMPLIED>
<!ATTLIST extend\-project remote CDATA #IMPLIED> <!ATTLIST extend\-project remote CDATA #IMPLIED>
<!ATTLIST extend\-project dest\-branch CDATA #IMPLIED>
<!ATTLIST extend\-project upstream CDATA #IMPLIED>
.IP .IP
<!ELEMENT remove\-project EMPTY> <!ELEMENT remove\-project EMPTY>
<!ATTLIST remove\-project name CDATA #REQUIRED> <!ATTLIST remove\-project name CDATA #REQUIRED>
@ -485,6 +487,12 @@ project. Same syntax as the corresponding element of `project`.
Attribute `remote`: If specified, overrides the remote of the original project. Attribute `remote`: If specified, overrides the remote of the original project.
Same syntax as the corresponding element of `project`. Same syntax as the corresponding element of `project`.
.PP .PP
Attribute `dest\-branch`: If specified, overrides the dest\-branch of the original
project. Same syntax as the corresponding element of `project`.
.PP
Attribute `upstream`: If specified, overrides the upstream of the original
project. Same syntax as the corresponding element of `project`.
.PP
Element annotation Element annotation
.PP .PP
Zero or more annotation elements may be specified as children of a project or Zero or more annotation elements may be specified as children of a project or
@ -600,7 +608,7 @@ included manifest belong. This appends and recurses, meaning all projects in
included manifests carry all parent include groups. Same syntax as the included manifests carry all parent include groups. Same syntax as the
corresponding element of `project`. corresponding element of `project`.
.PP .PP
Local Manifests Local Manifests
.PP .PP
Additional remotes and projects may be added through local manifest files stored Additional remotes and projects may be added through local manifest files stored
in `$TOP_DIR/.repo/local_manifests/*.xml`. in `$TOP_DIR/.repo/local_manifests/*.xml`.

View File

@ -1289,6 +1289,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
remote = self._default.remote remote = self._default.remote
else: else:
remote = self._get_remote(node) remote = self._get_remote(node)
dest_branch = node.getAttribute('dest-branch')
upstream = node.getAttribute('upstream')
named_projects = self._projects[name] named_projects = self._projects[name]
if dest_path and not path and len(named_projects) > 1: if dest_path and not path and len(named_projects) > 1:
@ -1304,6 +1306,10 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
if remote_name: if remote_name:
p.remote = remote.ToRemoteSpec(name) p.remote = remote.ToRemoteSpec(name)
if dest_branch:
p.dest_branch = dest_branch
if upstream:
p.upstream = upstream
if dest_path: if dest_path:
del self._paths[p.relpath] del self._paths[p.relpath]
@ -1940,11 +1946,14 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
fromKeys = sorted(fromProjects.keys()) fromKeys = sorted(fromProjects.keys())
toKeys = sorted(toProjects.keys()) toKeys = sorted(toProjects.keys())
diff = {'added': [], 'removed': [], 'changed': [], 'unreachable': []} diff = {'added': [], 'removed': [], 'missing': [], 'changed': [], 'unreachable': []}
for proj in fromKeys: for proj in fromKeys:
if proj not in toKeys: if proj not in toKeys:
diff['removed'].append(fromProjects[proj]) diff['removed'].append(fromProjects[proj])
elif not fromProjects[proj].Exists:
diff['missing'].append(toProjects[proj])
toKeys.remove(proj)
else: else:
fromProj = fromProjects[proj] fromProj = fromProjects[proj]
toProj = toProjects[proj] toProj = toProjects[proj]

View File

@ -26,6 +26,7 @@ import sys
import tarfile import tarfile
import tempfile import tempfile
import time import time
from typing import NamedTuple
import urllib.parse import urllib.parse
from color import Coloring from color import Coloring
@ -45,6 +46,14 @@ from repo_trace import IsTrace, Trace
from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M, R_WORKTREE_M from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M, R_WORKTREE_M
class SyncNetworkHalfResult(NamedTuple):
"""Sync_NetworkHalf return value."""
# True if successful.
success: bool
# Did we query the remote? False when optimized_fetch is True and we have the
# commit already present.
remote_fetched: bool
# Maximum sleep time allowed during retries. # Maximum sleep time allowed during retries.
MAXIMUM_RETRY_SLEEP_SEC = 3600.0 MAXIMUM_RETRY_SLEEP_SEC = 3600.0
# +-10% random jitter is added to each Fetches retry sleep duration. # +-10% random jitter is added to each Fetches retry sleep duration.
@ -1133,7 +1142,7 @@ class Project(object):
if archive and not isinstance(self, MetaProject): if archive and not isinstance(self, MetaProject):
if self.remote.url.startswith(('http://', 'https://')): if self.remote.url.startswith(('http://', 'https://')):
_error("%s: Cannot fetch archives from http/https remotes.", self.name) _error("%s: Cannot fetch archives from http/https remotes.", self.name)
return False return SyncNetworkHalfResult(False, False)
name = self.relpath.replace('\\', '/') name = self.relpath.replace('\\', '/')
name = name.replace('/', '_') name = name.replace('/', '_')
@ -1144,19 +1153,19 @@ class Project(object):
self._FetchArchive(tarpath, cwd=topdir) self._FetchArchive(tarpath, cwd=topdir)
except GitError as e: except GitError as e:
_error('%s', e) _error('%s', e)
return False return SyncNetworkHalfResult(False, False)
# From now on, we only need absolute tarpath # From now on, we only need absolute tarpath
tarpath = os.path.join(topdir, tarpath) tarpath = os.path.join(topdir, tarpath)
if not self._ExtractArchive(tarpath, path=topdir): if not self._ExtractArchive(tarpath, path=topdir):
return False return SyncNetworkHalfResult(False, True)
try: try:
platform_utils.remove(tarpath) platform_utils.remove(tarpath)
except OSError as e: except OSError as e:
_warn("Cannot remove archive %s: %s", tarpath, str(e)) _warn("Cannot remove archive %s: %s", tarpath, str(e))
self._CopyAndLinkFiles() self._CopyAndLinkFiles()
return True return SyncNetworkHalfResult(True, True)
# If the shared object dir already exists, don't try to rebootstrap with a # If the shared object dir already exists, don't try to rebootstrap with a
# clone bundle download. We should have the majority of objects already. # clone bundle download. We should have the majority of objects already.
@ -1220,9 +1229,11 @@ class Project(object):
depth = self.manifest.manifestProject.depth depth = self.manifest.manifestProject.depth
# See if we can skip the network fetch entirely. # See if we can skip the network fetch entirely.
remote_fetched = False
if not (optimized_fetch and if not (optimized_fetch and
(ID_RE.match(self.revisionExpr) and (ID_RE.match(self.revisionExpr) and
self._CheckForImmutableRevision())): self._CheckForImmutableRevision())):
remote_fetched = True
if not self._RemoteFetch( if not self._RemoteFetch(
initial=is_new, initial=is_new,
quiet=quiet, verbose=verbose, output_redir=output_redir, quiet=quiet, verbose=verbose, output_redir=output_redir,
@ -1231,7 +1242,7 @@ class Project(object):
submodules=submodules, force_sync=force_sync, submodules=submodules, force_sync=force_sync,
ssh_proxy=ssh_proxy, ssh_proxy=ssh_proxy,
clone_filter=clone_filter, retry_fetches=retry_fetches): clone_filter=clone_filter, retry_fetches=retry_fetches):
return False return SyncNetworkHalfResult(False, remote_fetched)
mp = self.manifest.manifestProject mp = self.manifest.manifestProject
dissociate = mp.dissociate dissociate = mp.dissociate
@ -1244,7 +1255,7 @@ class Project(object):
if p.stdout and output_redir: if p.stdout and output_redir:
output_redir.write(p.stdout) output_redir.write(p.stdout)
if p.Wait() != 0: if p.Wait() != 0:
return False return SyncNetworkHalfResult(False, remote_fetched)
platform_utils.remove(alternates_file) platform_utils.remove(alternates_file)
if self.worktree: if self.worktree:
@ -1253,7 +1264,7 @@ class Project(object):
self._InitMirrorHead() self._InitMirrorHead()
platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'), platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
missing_ok=True) missing_ok=True)
return True return SyncNetworkHalfResult(True, remote_fetched)
def PostRepoUpgrade(self): def PostRepoUpgrade(self):
self._InitHooks() self._InitHooks()
@ -1451,6 +1462,8 @@ class Project(object):
cnt_mine += 1 cnt_mine += 1
if not upstream_gain and cnt_mine == len(local_changes): if not upstream_gain and cnt_mine == len(local_changes):
# The copy/linkfile config may have changed.
self._CopyAndLinkFiles()
return return
if self.IsDirty(consider_untracked=False): if self.IsDirty(consider_untracked=False):
@ -3836,7 +3849,7 @@ class ManifestProject(MetaProject):
is_new=is_new, quiet=not verbose, verbose=verbose, is_new=is_new, quiet=not verbose, verbose=verbose,
clone_bundle=clone_bundle, current_branch_only=current_branch_only, clone_bundle=clone_bundle, current_branch_only=current_branch_only,
tags=tags, submodules=submodules, clone_filter=clone_filter, tags=tags, submodules=submodules, clone_filter=clone_filter,
partial_clone_exclude=self.manifest.PartialCloneExclude): partial_clone_exclude=self.manifest.PartialCloneExclude).success:
r = self.GetRemote() r = self.GetRemote()
print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr) print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)

View File

@ -59,18 +59,26 @@ def main(argv):
version = RepoSourceVersion() version = RepoSourceVersion()
cmdlist = [['help2man', '-N', '-n', f'repo {cmd} - manual page for repo {cmd}', cmdlist = [['help2man', '-N', '-n', f'repo {cmd} - manual page for repo {cmd}',
'-S', f'repo {cmd}', '-m', 'Repo Manual', f'--version-string={version}', '-S', f'repo {cmd}', '-m', 'Repo Manual', f'--version-string={version}',
'-o', MANDIR.joinpath(f'repo-{cmd}.1.tmp'), TOPDIR.joinpath('repo'), '-o', MANDIR.joinpath(f'repo-{cmd}.1.tmp'), './repo',
'-h', f'help {cmd}'] for cmd in subcmds.all_commands] '-h', f'help {cmd}'] for cmd in subcmds.all_commands]
cmdlist.append(['help2man', '-N', '-n', 'repository management tool built on top of git', cmdlist.append(['help2man', '-N', '-n', 'repository management tool built on top of git',
'-S', 'repo', '-m', 'Repo Manual', f'--version-string={version}', '-S', 'repo', '-m', 'Repo Manual', f'--version-string={version}',
'-o', MANDIR.joinpath('repo.1.tmp'), TOPDIR.joinpath('repo'), '-o', MANDIR.joinpath('repo.1.tmp'), './repo',
'-h', '--help-all']) '-h', '--help-all'])
with tempfile.TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory() as tempdir:
repo_dir = Path(tempdir) / '.repo' tempdir = Path(tempdir)
repo_dir = tempdir / '.repo'
repo_dir.mkdir() repo_dir.mkdir()
(repo_dir / 'repo').symlink_to(TOPDIR) (repo_dir / 'repo').symlink_to(TOPDIR)
# Create a repo wrapper using the active Python executable. We can't pass
# this directly to help2man as it's too simple, so insert it via shebang.
data = (TOPDIR / 'repo').read_text(encoding='utf-8')
tempbin = tempdir / 'repo'
tempbin.write_text(f'#!{sys.executable}\n' + data, encoding='utf-8')
tempbin.chmod(0o755)
# Run all cmd in parallel, and wait for them to finish. # Run all cmd in parallel, and wait for them to finish.
with multiprocessing.Pool() as pool: with multiprocessing.Pool() as pool:
pool.map(partial(worker, cwd=tempdir, check=True), cmdlist) pool.map(partial(worker, cwd=tempdir, check=True), cmdlist)

5
repo
View File

@ -316,9 +316,10 @@ def InitParser(parser, gitc_init=False):
help='download the manifest as a static file ' help='download the manifest as a static file '
'rather then create a git checkout of ' 'rather then create a git checkout of '
'the manifest repo') 'the manifest repo')
group.add_option('--manifest-depth', type='int', default=1, metavar='DEPTH', group.add_option('--manifest-depth', type='int', default=0, metavar='DEPTH',
help='create a shallow clone of the manifest repo with ' help='create a shallow clone of the manifest repo with '
'given depth; see git clone (default: %default)') 'given depth (0 for full clone); see git clone '
'(default: %default)')
# Options that only affect manifest project, and not any of the projects # Options that only affect manifest project, and not any of the projects
# specified in the manifest itself. # specified in the manifest itself.

View File

@ -118,6 +118,16 @@ synced and their revisions won't be found.
self.printRevision(project.revisionExpr) self.printRevision(project.revisionExpr)
self.out.nl() self.out.nl()
if diff['missing']:
self.out.nl()
self.printText('missing projects : \n')
self.out.nl()
for project in diff['missing']:
self.printProject('\t%s' % (project.relpath))
self.printText(' at revision ')
self.printRevision(project.revisionExpr)
self.out.nl()
if diff['changed']: if diff['changed']:
self.out.nl() self.out.nl()
self.printText('changed projects : \n') self.printText('changed projects : \n')

View File

@ -51,7 +51,7 @@ need to be performed by an end-user.
_PostRepoUpgrade(self.manifest) _PostRepoUpgrade(self.manifest)
else: else:
if not rp.Sync_NetworkHalf(): if not rp.Sync_NetworkHalf().success:
print("error: can't update repo", file=sys.stderr) print("error: can't update repo", file=sys.stderr)
sys.exit(1) sys.exit(1)

View File

@ -26,6 +26,7 @@ import socket
import sys import sys
import tempfile import tempfile
import time import time
from typing import NamedTuple, List, Set
import urllib.error import urllib.error
import urllib.parse import urllib.parse
import urllib.request import urllib.request
@ -71,6 +72,58 @@ REPO_BACKUP_OBJECTS = 'REPO_BACKUP_OBJECTS'
_BACKUP_OBJECTS = os.environ.get(REPO_BACKUP_OBJECTS) != '0' _BACKUP_OBJECTS = os.environ.get(REPO_BACKUP_OBJECTS) != '0'
class _FetchOneResult(NamedTuple):
"""_FetchOne return value.
Attributes:
success (bool): True if successful.
project (Project): The fetched project.
start (float): The starting time.time().
finish (float): The ending time.time().
remote_fetched (bool): True if the remote was actually queried.
"""
success: bool
project: Project
start: float
finish: float
remote_fetched: bool
class _FetchResult(NamedTuple):
"""_Fetch return value.
Attributes:
success (bool): True if successful.
projects (Set[str]): The names of the git directories of fetched projects.
"""
success: bool
projects: Set[str]
class _FetchMainResult(NamedTuple):
"""_FetchMain return value.
Attributes:
all_projects (List[Project]): The fetched projects.
"""
all_projects: List[Project]
class _CheckoutOneResult(NamedTuple):
"""_CheckoutOne return value.
Attributes:
success (bool): True if successful.
project (Project): The project.
start (float): The starting time.time().
finish (float): The ending time.time().
"""
success: bool
project: Project
start: float
finish: float
class Sync(Command, MirrorSafeCommand): class Sync(Command, MirrorSafeCommand):
COMMON = True COMMON = True
MULTI_MANIFEST_SUPPORT = True MULTI_MANIFEST_SUPPORT = True
@ -412,7 +465,7 @@ later is required to fix a server side protocol bug.
success = False success = False
buf = io.StringIO() buf = io.StringIO()
try: try:
success = project.Sync_NetworkHalf( sync_result = project.Sync_NetworkHalf(
quiet=opt.quiet, quiet=opt.quiet,
verbose=opt.verbose, verbose=opt.verbose,
output_redir=buf, output_redir=buf,
@ -426,6 +479,7 @@ later is required to fix a server side protocol bug.
ssh_proxy=self.ssh_proxy, ssh_proxy=self.ssh_proxy,
clone_filter=project.manifest.CloneFilter, clone_filter=project.manifest.CloneFilter,
partial_clone_exclude=project.manifest.PartialCloneExclude) partial_clone_exclude=project.manifest.PartialCloneExclude)
success = sync_result.success
output = buf.getvalue() output = buf.getvalue()
if (opt.verbose or not success) and output: if (opt.verbose or not success) and output:
@ -443,7 +497,8 @@ later is required to fix a server side protocol bug.
raise raise
finish = time.time() finish = time.time()
return (success, project, start, finish) return _FetchOneResult(success, project, start, finish,
sync_result.remote_fetched)
@classmethod @classmethod
def _FetchInitChild(cls, ssh_proxy): def _FetchInitChild(cls, ssh_proxy):
@ -454,6 +509,7 @@ later is required to fix a server side protocol bug.
jobs = opt.jobs_network jobs = opt.jobs_network
fetched = set() fetched = set()
remote_fetched = set()
pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet) pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet)
objdir_project_map = dict() objdir_project_map = dict()
@ -464,10 +520,16 @@ later is required to fix a server side protocol bug.
def _ProcessResults(results_sets): def _ProcessResults(results_sets):
ret = True ret = True
for results in results_sets: for results in results_sets:
for (success, project, start, finish) in results: for result in results:
success = result.success
project = result.project
start = result.start
finish = result.finish
self._fetch_times.Set(project, finish - start) self._fetch_times.Set(project, finish - start)
self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK, self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
start, finish, success) start, finish, success)
if result.remote_fetched:
remote_fetched.add(project)
# Check for any errors before running any more tasks. # Check for any errors before running any more tasks.
# ...we'll let existing jobs finish, though. # ...we'll let existing jobs finish, though.
if not success: if not success:
@ -525,7 +587,7 @@ later is required to fix a server side protocol bug.
if not self.outer_client.manifest.IsArchive: if not self.outer_client.manifest.IsArchive:
self._GCProjects(projects, opt, err_event) self._GCProjects(projects, opt, err_event)
return (ret, fetched) return _FetchResult(ret, fetched)
def _FetchMain(self, opt, args, all_projects, err_event, def _FetchMain(self, opt, args, all_projects, err_event,
ssh_proxy, manifest): ssh_proxy, manifest):
@ -551,7 +613,9 @@ later is required to fix a server side protocol bug.
to_fetch.extend(all_projects) to_fetch.extend(all_projects)
to_fetch.sort(key=self._fetch_times.Get, reverse=True) to_fetch.sort(key=self._fetch_times.Get, reverse=True)
success, fetched = self._Fetch(to_fetch, opt, err_event, ssh_proxy) result = self._Fetch(to_fetch, opt, err_event, ssh_proxy)
success = result.success
fetched = result.projects
if not success: if not success:
err_event.set() err_event.set()
@ -561,7 +625,7 @@ later is required to fix a server side protocol bug.
if err_event.is_set(): if err_event.is_set():
print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr) print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
sys.exit(1) sys.exit(1)
return return _FetchMainResult([])
# Iteratively fetch missing and/or nested unregistered submodules # Iteratively fetch missing and/or nested unregistered submodules
previously_missing_set = set() previously_missing_set = set()
@ -584,12 +648,14 @@ later is required to fix a server side protocol bug.
if previously_missing_set == missing_set: if previously_missing_set == missing_set:
break break
previously_missing_set = missing_set previously_missing_set = missing_set
success, new_fetched = self._Fetch(missing, opt, err_event, ssh_proxy) result = self._Fetch(missing, opt, err_event, ssh_proxy)
success = result.success
new_fetched = result.projects
if not success: if not success:
err_event.set() err_event.set()
fetched.update(new_fetched) fetched.update(new_fetched)
return all_projects return _FetchMainResult(all_projects)
def _CheckoutOne(self, detach_head, force_sync, project): def _CheckoutOne(self, detach_head, force_sync, project):
"""Checkout work tree for one project """Checkout work tree for one project
@ -621,7 +687,7 @@ later is required to fix a server side protocol bug.
if not success: if not success:
print('error: Cannot checkout %s' % (project.name), file=sys.stderr) print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
finish = time.time() finish = time.time()
return (success, project, start, finish) return _CheckoutOneResult(success, project, start, finish)
def _Checkout(self, all_projects, opt, err_results): def _Checkout(self, all_projects, opt, err_results):
"""Checkout projects listed in all_projects """Checkout projects listed in all_projects
@ -636,7 +702,11 @@ later is required to fix a server side protocol bug.
def _ProcessResults(pool, pm, results): def _ProcessResults(pool, pm, results):
ret = True ret = True
for (success, project, start, finish) in results: for result in results:
success = result.success
project = result.project
start = result.start
finish = result.finish
self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL, self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
start, finish, success) start, finish, success)
# Check for any errors before running any more tasks. # Check for any errors before running any more tasks.
@ -658,6 +728,36 @@ later is required to fix a server side protocol bug.
callback=_ProcessResults, callback=_ProcessResults,
output=Progress('Checking out', len(all_projects), quiet=opt.quiet)) and not err_results output=Progress('Checking out', len(all_projects), quiet=opt.quiet)) and not err_results
def _backup_cruft(self, bare_git):
"""Save a copy of any cruft from `git gc`."""
# Find any cruft packs in the current gitdir, and save them.
# b/221065125 (repo sync complains that objects are missing). This does
# not prevent that state, but makes it so that the missing objects are
# available.
objdir = bare_git._project.objdir
pack_dir = os.path.join(objdir, 'pack')
bak_dir = os.path.join(objdir, '.repo', 'pack.bak')
if not _BACKUP_OBJECTS or not platform_utils.isdir(pack_dir):
return
saved = []
files = set(platform_utils.listdir(pack_dir))
to_backup = []
for f in files:
base, ext = os.path.splitext(f)
if base + '.mtimes' in files:
to_backup.append(f)
if to_backup:
os.makedirs(bak_dir, exist_ok=True)
for fname in to_backup:
bak_fname = os.path.join(bak_dir, fname)
if not os.path.exists(bak_fname):
saved.append(fname)
# Use a tmp file so that we are sure of a complete copy.
shutil.copy(os.path.join(pack_dir, fname), bak_fname + '.tmp')
shutil.move(bak_fname + '.tmp', bak_fname)
if saved:
Trace('%s saved %s', bare_git._project.name, ' '.join(saved))
def _GCProjects(self, projects, opt, err_event): def _GCProjects(self, projects, opt, err_event):
pm = Progress('Garbage collecting', len(projects), delay=False, quiet=opt.quiet) pm = Progress('Garbage collecting', len(projects), delay=False, quiet=opt.quiet)
pm.update(inc=0, msg='prescan') pm.update(inc=0, msg='prescan')
@ -700,36 +800,11 @@ later is required to fix a server side protocol bug.
jobs = opt.jobs jobs = opt.jobs
def _backup_cruft(bare_git): gc_args = ['--auto']
# Find any cruft packs in the current gitdir, and save them. backup_cruft = False
# b/221065125 (repo sync complains that objects are missing). This does if git_require((2, 37, 0)):
# not prevent that state, but makes it so that the missing objects are gc_args.append('--cruft')
# available. backup_cruft = True
if not _BACKUP_OBJECTS:
return
saved = []
objdir = bare_git.GetDotgitPath('objects')
pack_dir = os.path.join(objdir, 'pack')
bak_dir = os.path.join(objdir, '.repo','pack.bak')
files = set(platform_utils.listdir(pack_dir))
to_backup = []
for f in files:
base, ext = os.path.splitext(f)
if base + ".mtimes" in files:
to_backup.append(f)
if to_backup and not platform_utils.isdir(bak_dir):
os.makedirs(bak_dir)
for fname in to_backup:
bak_fname = os.path.join(bak_dir, fname)
if not os.path.exists(bak_fname):
saved.append(fname)
# Use a tmp file so that we are sure of a complete copy.
shutil.copy(os.path.join(pack_dir, fname), bak_fname + '.tmp')
shutil.move(bak_fname + '.tmp', bak_fname)
if saved and IsTrace():
Trace('%s saved %s', bare_git._project.name, ' '.join(saved))
gc_args = ('--auto', '--cruft')
pack_refs_args = () pack_refs_args = ()
if jobs < 2: if jobs < 2:
for (run_gc, bare_git) in tidy_dirs.values(): for (run_gc, bare_git) in tidy_dirs.values():
@ -739,7 +814,8 @@ later is required to fix a server side protocol bug.
bare_git.gc(*gc_args) bare_git.gc(*gc_args)
else: else:
bare_git.pack_refs(*pack_refs_args) bare_git.pack_refs(*pack_refs_args)
_backup_cruft(bare_git) if backup_cruft:
self._backup_cruft(bare_git)
pm.end() pm.end()
return return
@ -763,7 +839,8 @@ later is required to fix a server side protocol bug.
err_event.set() err_event.set()
raise raise
finally: finally:
_backup_cruft(bare_git) if backup_cruft:
self._backup_cruft(bare_git)
pm.finish(bare_git._project.name) pm.finish(bare_git._project.name)
sem.release() sem.release()
@ -1200,6 +1277,7 @@ later is required to fix a server side protocol bug.
err_network_sync = False err_network_sync = False
err_update_projects = False err_update_projects = False
err_update_linkfiles = False
self._fetch_times = _FetchTimes(manifest) self._fetch_times = _FetchTimes(manifest)
if not opt.local_only: if not opt.local_only:
@ -1207,8 +1285,9 @@ later is required to fix a server side protocol bug.
with ssh.ProxyManager(manager) as ssh_proxy: with ssh.ProxyManager(manager) as ssh_proxy:
# Initialize the socket dir once in the parent. # Initialize the socket dir once in the parent.
ssh_proxy.sock() ssh_proxy.sock()
all_projects = self._FetchMain(opt, args, all_projects, err_event, result = self._FetchMain(opt, args, all_projects, err_event,
ssh_proxy, manifest) ssh_proxy, manifest)
all_projects = result.all_projects
if opt.network_only: if opt.network_only:
return return

View File

@ -874,3 +874,27 @@ class ExtendProjectElementTests(ManifestParseTestCase):
else: else:
self.assertEqual(manifest.projects[0].relpath, 'bar') self.assertEqual(manifest.projects[0].relpath, 'bar')
self.assertEqual(manifest.projects[1].relpath, 'y') self.assertEqual(manifest.projects[1].relpath, 'y')
def test_extend_project_dest_branch(self):
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" dest-branch="foo" />
<project name="myproject" />
<extend-project name="myproject" dest-branch="bar" />
</manifest>
""")
self.assertEqual(len(manifest.projects), 1)
self.assertEqual(manifest.projects[0].dest_branch, 'bar')
def test_extend_project_upstream(self):
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<project name="myproject" />
<extend-project name="myproject" upstream="bar" />
</manifest>
""")
self.assertEqual(len(manifest.projects), 1)
self.assertEqual(manifest.projects[0].upstream, 'bar')