mirror of
https://gerrit.googlesource.com/git-repo
synced 2025-06-26 20:17:52 +00:00
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
47020ba249 | |||
5ed8c63942 | |||
24c6314fca | |||
7efab539f0 | |||
a3ff64cae5 | |||
776138a938 | |||
5fb9c6a5b3 | |||
859d3d9580 | |||
fa8d939c8f | |||
a6c52f566a | |||
0d130d2da0 | |||
b750b48f50 | |||
6c8b894d8d | |||
b6cfa09500 | |||
78dcd3799b | |||
acc4c857a0 | |||
a39af3d432 | |||
4cdfdb7734 | |||
1eddca8476 | |||
aefa4d3a29 | |||
4ba29c42ca | |||
45ef9011c2 | |||
891e8f72ce | |||
af8fb132d5 |
@ -105,6 +105,8 @@ following DTD:
|
||||
<!ATTLIST extend-project groups CDATA #IMPLIED>
|
||||
<!ATTLIST extend-project revision 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>
|
||||
<!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
|
||||
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
|
||||
|
||||
Zero or more annotation elements may be specified as children of a
|
||||
|
@ -230,12 +230,11 @@ class GitCommand(object):
|
||||
stderr = (subprocess.STDOUT if merge_output else
|
||||
(subprocess.PIPE if capture_stderr else None))
|
||||
|
||||
dbg = ''
|
||||
if IsTrace():
|
||||
global LAST_CWD
|
||||
global LAST_GITDIR
|
||||
|
||||
dbg = ''
|
||||
|
||||
if cwd and LAST_CWD != cwd:
|
||||
if LAST_GITDIR or LAST_CWD:
|
||||
dbg += '\n'
|
||||
@ -263,31 +262,31 @@ class GitCommand(object):
|
||||
dbg += ' 2>|'
|
||||
elif stderr == subprocess.STDOUT:
|
||||
dbg += ' 2>&1'
|
||||
Trace('%s', dbg)
|
||||
|
||||
try:
|
||||
p = subprocess.Popen(command,
|
||||
cwd=cwd,
|
||||
env=env,
|
||||
encoding='utf-8',
|
||||
errors='backslashreplace',
|
||||
stdin=stdin,
|
||||
stdout=stdout,
|
||||
stderr=stderr)
|
||||
except Exception as e:
|
||||
raise GitError('%s: %s' % (command[1], e))
|
||||
with Trace('git command %s %s with debug: %s', LAST_GITDIR, command, dbg):
|
||||
try:
|
||||
p = subprocess.Popen(command,
|
||||
cwd=cwd,
|
||||
env=env,
|
||||
encoding='utf-8',
|
||||
errors='backslashreplace',
|
||||
stdin=stdin,
|
||||
stdout=stdout,
|
||||
stderr=stderr)
|
||||
except Exception as e:
|
||||
raise GitError('%s: %s' % (command[1], e))
|
||||
|
||||
if ssh_proxy:
|
||||
ssh_proxy.add_client(p)
|
||||
|
||||
self.process = p
|
||||
|
||||
try:
|
||||
self.stdout, self.stderr = p.communicate(input=input)
|
||||
finally:
|
||||
if ssh_proxy:
|
||||
ssh_proxy.remove_client(p)
|
||||
self.rc = p.wait()
|
||||
ssh_proxy.add_client(p)
|
||||
|
||||
self.process = p
|
||||
|
||||
try:
|
||||
self.stdout, self.stderr = p.communicate(input=input)
|
||||
finally:
|
||||
if ssh_proxy:
|
||||
ssh_proxy.remove_client(p)
|
||||
self.rc = p.wait()
|
||||
|
||||
@staticmethod
|
||||
def _GetBasicEnv():
|
||||
|
@ -219,8 +219,8 @@ class GitConfig(object):
|
||||
"""Set the value(s) for a key.
|
||||
Only this configuration file is modified.
|
||||
|
||||
The supplied value should be either a string,
|
||||
or a list of strings (to store multiple values).
|
||||
The supplied value should be either a string, or a list of strings (to
|
||||
store multiple values), or None (to delete the key).
|
||||
"""
|
||||
key = _key(name)
|
||||
|
||||
@ -349,9 +349,9 @@ class GitConfig(object):
|
||||
except OSError:
|
||||
return None
|
||||
try:
|
||||
Trace(': parsing %s', self.file)
|
||||
with open(self._json) as fd:
|
||||
return json.load(fd)
|
||||
with Trace(': parsing %s', self.file):
|
||||
with open(self._json) as fd:
|
||||
return json.load(fd)
|
||||
except (IOError, ValueError):
|
||||
platform_utils.remove(self._json, missing_ok=True)
|
||||
return None
|
||||
|
51
git_refs.py
51
git_refs.py
@ -67,38 +67,37 @@ class GitRefs(object):
|
||||
self._LoadAll()
|
||||
|
||||
def _NeedUpdate(self):
|
||||
Trace(': scan refs %s', self._gitdir)
|
||||
|
||||
for name, mtime in self._mtime.items():
|
||||
try:
|
||||
if mtime != os.path.getmtime(os.path.join(self._gitdir, name)):
|
||||
with Trace(': scan refs %s', self._gitdir):
|
||||
for name, mtime in self._mtime.items():
|
||||
try:
|
||||
if mtime != os.path.getmtime(os.path.join(self._gitdir, name)):
|
||||
return True
|
||||
except OSError:
|
||||
return True
|
||||
except OSError:
|
||||
return True
|
||||
return False
|
||||
return False
|
||||
|
||||
def _LoadAll(self):
|
||||
Trace(': load refs %s', self._gitdir)
|
||||
with Trace(': load refs %s', self._gitdir):
|
||||
|
||||
self._phyref = {}
|
||||
self._symref = {}
|
||||
self._mtime = {}
|
||||
self._phyref = {}
|
||||
self._symref = {}
|
||||
self._mtime = {}
|
||||
|
||||
self._ReadPackedRefs()
|
||||
self._ReadLoose('refs/')
|
||||
self._ReadLoose1(os.path.join(self._gitdir, HEAD), HEAD)
|
||||
self._ReadPackedRefs()
|
||||
self._ReadLoose('refs/')
|
||||
self._ReadLoose1(os.path.join(self._gitdir, HEAD), HEAD)
|
||||
|
||||
scan = self._symref
|
||||
attempts = 0
|
||||
while scan and attempts < 5:
|
||||
scan_next = {}
|
||||
for name, dest in scan.items():
|
||||
if dest in self._phyref:
|
||||
self._phyref[name] = self._phyref[dest]
|
||||
else:
|
||||
scan_next[name] = dest
|
||||
scan = scan_next
|
||||
attempts += 1
|
||||
scan = self._symref
|
||||
attempts = 0
|
||||
while scan and attempts < 5:
|
||||
scan_next = {}
|
||||
for name, dest in scan.items():
|
||||
if dest in self._phyref:
|
||||
self._phyref[name] = self._phyref[dest]
|
||||
else:
|
||||
scan_next[name] = dest
|
||||
scan = scan_next
|
||||
attempts += 1
|
||||
|
||||
def _ReadPackedRefs(self):
|
||||
path = os.path.join(self._gitdir, 'packed-refs')
|
||||
|
40
main.py
40
main.py
@ -37,7 +37,7 @@ except ImportError:
|
||||
|
||||
from color import SetDefaultColoring
|
||||
import event_log
|
||||
from repo_trace import SetTrace
|
||||
from repo_trace import SetTrace, Trace, SetTraceToStderr
|
||||
from git_command import user_agent
|
||||
from git_config import RepoConfig
|
||||
from git_trace2_event_log import EventLog
|
||||
@ -109,6 +109,9 @@ global_options.add_option('--color',
|
||||
global_options.add_option('--trace',
|
||||
dest='trace', action='store_true',
|
||||
help='trace git command execution (REPO_TRACE=1)')
|
||||
global_options.add_option('--trace-to-stderr',
|
||||
dest='trace_to_stderr', action='store_true',
|
||||
help='trace outputs go to stderr in addition to .repo/TRACE_FILE')
|
||||
global_options.add_option('--trace-python',
|
||||
dest='trace_python', action='store_true',
|
||||
help='trace python command execution')
|
||||
@ -198,9 +201,6 @@ class _Repo(object):
|
||||
"""Execute the requested subcommand."""
|
||||
result = 0
|
||||
|
||||
if gopts.trace:
|
||||
SetTrace()
|
||||
|
||||
# Handle options that terminate quickly first.
|
||||
if gopts.help or gopts.help_all:
|
||||
self._PrintHelp(short=False, all_commands=gopts.help_all)
|
||||
@ -216,6 +216,21 @@ class _Repo(object):
|
||||
self._PrintHelp(short=True)
|
||||
return 1
|
||||
|
||||
run = lambda: self._RunLong(name, gopts, argv) or 0
|
||||
with Trace('starting new command: %s', ', '.join([name] + argv),
|
||||
first_trace=True):
|
||||
if gopts.trace_python:
|
||||
import trace
|
||||
tracer = trace.Trace(count=False, trace=True, timing=True,
|
||||
ignoredirs=set(sys.path[1:]))
|
||||
result = tracer.runfunc(run)
|
||||
else:
|
||||
result = run()
|
||||
return result
|
||||
|
||||
def _RunLong(self, name, gopts, argv):
|
||||
"""Execute the (longer running) requested subcommand."""
|
||||
result = 0
|
||||
SetDefaultColoring(gopts.color)
|
||||
|
||||
git_trace2_event_log = EventLog()
|
||||
@ -652,17 +667,18 @@ def _Main(argv):
|
||||
Version.wrapper_path = opt.wrapper_path
|
||||
|
||||
repo = _Repo(opt.repodir)
|
||||
|
||||
try:
|
||||
init_http()
|
||||
name, gopts, argv = repo._ParseArgs(argv)
|
||||
run = lambda: repo._Run(name, gopts, argv) or 0
|
||||
if gopts.trace_python:
|
||||
import trace
|
||||
tracer = trace.Trace(count=False, trace=True, timing=True,
|
||||
ignoredirs=set(sys.path[1:]))
|
||||
result = tracer.runfunc(run)
|
||||
else:
|
||||
result = run()
|
||||
|
||||
if gopts.trace:
|
||||
SetTrace()
|
||||
|
||||
if gopts.trace_to_stderr:
|
||||
SetTraceToStderr()
|
||||
|
||||
result = repo._Run(name, gopts, argv) or 0
|
||||
except KeyboardInterrupt:
|
||||
print('aborted by user', file=sys.stderr)
|
||||
result = 1
|
||||
|
@ -1,5 +1,5 @@
|
||||
.\" 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
|
||||
repo \- repo gitc-init - manual page for repo gitc-init
|
||||
.SH SYNOPSIS
|
||||
@ -48,7 +48,7 @@ create a git checkout of the manifest repo
|
||||
.TP
|
||||
\fB\-\-manifest\-depth\fR=\fI\,DEPTH\/\fR
|
||||
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:
|
||||
.TP
|
||||
\fB\-\-current\-branch\fR
|
||||
|
@ -1,5 +1,5 @@
|
||||
.\" 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
|
||||
repo \- repo init - manual page for repo init
|
||||
.SH SYNOPSIS
|
||||
@ -48,7 +48,7 @@ create a git checkout of the manifest repo
|
||||
.TP
|
||||
\fB\-\-manifest\-depth\fR=\fI\,DEPTH\/\fR
|
||||
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:
|
||||
.TP
|
||||
\fB\-c\fR, \fB\-\-current\-branch\fR
|
||||
|
@ -1,5 +1,5 @@
|
||||
.\" 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
|
||||
repo \- repo manifest - manual page for repo manifest
|
||||
.SH SYNOPSIS
|
||||
@ -190,6 +190,8 @@ CDATA #IMPLIED>
|
||||
<!ATTLIST extend\-project groups CDATA #IMPLIED>
|
||||
<!ATTLIST extend\-project revision CDATA #IMPLIED>
|
||||
<!ATTLIST extend\-project remote CDATA #IMPLIED>
|
||||
<!ATTLIST extend\-project dest\-branch CDATA #IMPLIED>
|
||||
<!ATTLIST extend\-project upstream CDATA #IMPLIED>
|
||||
.IP
|
||||
<!ELEMENT remove\-project EMPTY>
|
||||
<!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.
|
||||
Same syntax as the corresponding element of `project`.
|
||||
.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
|
||||
.PP
|
||||
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
|
||||
corresponding element of `project`.
|
||||
.PP
|
||||
Local Manifests
|
||||
Local Manifests
|
||||
.PP
|
||||
Additional remotes and projects may be added through local manifest files stored
|
||||
in `$TOP_DIR/.repo/local_manifests/*.xml`.
|
||||
|
@ -1,5 +1,5 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||
.TH REPO "1" "August 2022" "repo smartsync" "Repo Manual"
|
||||
.TH REPO "1" "November 2022" "repo smartsync" "Repo Manual"
|
||||
.SH NAME
|
||||
repo \- repo smartsync - manual page for repo smartsync
|
||||
.SH SYNOPSIS
|
||||
@ -105,6 +105,13 @@ delete refs that no longer exist on the remote
|
||||
.TP
|
||||
\fB\-\-no\-prune\fR
|
||||
do not delete refs that no longer exist on the remote
|
||||
.TP
|
||||
\fB\-\-auto\-gc\fR
|
||||
run garbage collection on all synced projects
|
||||
.TP
|
||||
\fB\-\-no\-auto\-gc\fR
|
||||
do not run garbage collection on any projects
|
||||
(default)
|
||||
.SS Logging options:
|
||||
.TP
|
||||
\fB\-v\fR, \fB\-\-verbose\fR
|
||||
|
@ -1,5 +1,5 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||
.TH REPO "1" "August 2022" "repo sync" "Repo Manual"
|
||||
.TH REPO "1" "November 2022" "repo sync" "Repo Manual"
|
||||
.SH NAME
|
||||
repo \- repo sync - manual page for repo sync
|
||||
.SH SYNOPSIS
|
||||
@ -106,6 +106,13 @@ delete refs that no longer exist on the remote
|
||||
\fB\-\-no\-prune\fR
|
||||
do not delete refs that no longer exist on the remote
|
||||
.TP
|
||||
\fB\-\-auto\-gc\fR
|
||||
run garbage collection on all synced projects
|
||||
.TP
|
||||
\fB\-\-no\-auto\-gc\fR
|
||||
do not run garbage collection on any projects
|
||||
(default)
|
||||
.TP
|
||||
\fB\-s\fR, \fB\-\-smart\-sync\fR
|
||||
smart sync using manifest from the latest known good
|
||||
build
|
||||
@ -200,6 +207,9 @@ to a sha1 revision if the sha1 revision does not already exist locally.
|
||||
The \fB\-\-prune\fR option can be used to remove any refs that no longer exist on the
|
||||
remote.
|
||||
.PP
|
||||
The \fB\-\-auto\-gc\fR option can be used to trigger garbage collection on all projects.
|
||||
By default, repo does not run garbage collection.
|
||||
.PP
|
||||
SSH Connections
|
||||
.PP
|
||||
If at least one project remote URL uses an SSH connection (ssh://, git+ssh://,
|
||||
|
@ -1,5 +1,5 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||
.TH REPO "1" "July 2022" "repo" "Repo Manual"
|
||||
.TH REPO "1" "November 2022" "repo" "Repo Manual"
|
||||
.SH NAME
|
||||
repo \- repository management tool built on top of git
|
||||
.SH SYNOPSIS
|
||||
@ -25,6 +25,10 @@ control color usage: auto, always, never
|
||||
\fB\-\-trace\fR
|
||||
trace git command execution (REPO_TRACE=1)
|
||||
.TP
|
||||
\fB\-\-trace-to-stderr\fR
|
||||
trace outputs go to stderr in addition to
|
||||
\&.repo/TRACE_FILE
|
||||
.TP
|
||||
\fB\-\-trace\-python\fR
|
||||
trace python command execution
|
||||
.TP
|
||||
|
@ -1289,6 +1289,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
remote = self._default.remote
|
||||
else:
|
||||
remote = self._get_remote(node)
|
||||
dest_branch = node.getAttribute('dest-branch')
|
||||
upstream = node.getAttribute('upstream')
|
||||
|
||||
named_projects = self._projects[name]
|
||||
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:
|
||||
p.remote = remote.ToRemoteSpec(name)
|
||||
if dest_branch:
|
||||
p.dest_branch = dest_branch
|
||||
if upstream:
|
||||
p.upstream = upstream
|
||||
|
||||
if dest_path:
|
||||
del self._paths[p.relpath]
|
||||
@ -1940,11 +1946,14 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
fromKeys = sorted(fromProjects.keys())
|
||||
toKeys = sorted(toProjects.keys())
|
||||
|
||||
diff = {'added': [], 'removed': [], 'changed': [], 'unreachable': []}
|
||||
diff = {'added': [], 'removed': [], 'missing': [], 'changed': [], 'unreachable': []}
|
||||
|
||||
for proj in fromKeys:
|
||||
if proj not in toKeys:
|
||||
diff['removed'].append(fromProjects[proj])
|
||||
elif not fromProjects[proj].Exists:
|
||||
diff['missing'].append(toProjects[proj])
|
||||
toKeys.remove(proj)
|
||||
else:
|
||||
fromProj = fromProjects[proj]
|
||||
toProj = toProjects[proj]
|
||||
|
@ -15,7 +15,7 @@
|
||||
import os
|
||||
import sys
|
||||
from time import time
|
||||
from repo_trace import IsTrace
|
||||
from repo_trace import IsTraceToStderr
|
||||
|
||||
_NOT_TTY = not os.isatty(2)
|
||||
|
||||
@ -80,7 +80,7 @@ class Progress(object):
|
||||
def update(self, inc=1, msg=''):
|
||||
self._done += inc
|
||||
|
||||
if _NOT_TTY or IsTrace():
|
||||
if _NOT_TTY or IsTraceToStderr():
|
||||
return
|
||||
|
||||
if not self._show:
|
||||
@ -113,7 +113,7 @@ class Progress(object):
|
||||
sys.stderr.flush()
|
||||
|
||||
def end(self):
|
||||
if _NOT_TTY or IsTrace() or not self._show:
|
||||
if _NOT_TTY or IsTraceToStderr() or not self._show:
|
||||
return
|
||||
|
||||
duration = duration_str(time() - self._start)
|
||||
|
95
project.py
95
project.py
@ -26,6 +26,7 @@ import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
import time
|
||||
from typing import NamedTuple
|
||||
import urllib.parse
|
||||
|
||||
from color import Coloring
|
||||
@ -40,17 +41,25 @@ from error import ManifestInvalidRevisionError, ManifestInvalidPathError
|
||||
from error import NoManifestException, ManifestParseError
|
||||
import platform_utils
|
||||
import progress
|
||||
from repo_trace import IsTrace, Trace
|
||||
from repo_trace import Trace
|
||||
|
||||
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_RETRY_SLEEP_SEC = 3600.0
|
||||
# +-10% random jitter is added to each Fetches retry sleep duration.
|
||||
RETRY_JITTER_PERCENT = 0.1
|
||||
|
||||
# Whether to use alternates.
|
||||
# Whether to use alternates. Switching back and forth is *NOT* supported.
|
||||
# TODO(vapier): Remove knob once behavior is verified.
|
||||
_ALTERNATES = os.environ.get('REPO_USE_ALTERNATES') == '1'
|
||||
|
||||
@ -1133,7 +1142,7 @@ class Project(object):
|
||||
if archive and not isinstance(self, MetaProject):
|
||||
if self.remote.url.startswith(('http://', 'https://')):
|
||||
_error("%s: Cannot fetch archives from http/https remotes.", self.name)
|
||||
return False
|
||||
return SyncNetworkHalfResult(False, False)
|
||||
|
||||
name = self.relpath.replace('\\', '/')
|
||||
name = name.replace('/', '_')
|
||||
@ -1144,19 +1153,19 @@ class Project(object):
|
||||
self._FetchArchive(tarpath, cwd=topdir)
|
||||
except GitError as e:
|
||||
_error('%s', e)
|
||||
return False
|
||||
return SyncNetworkHalfResult(False, False)
|
||||
|
||||
# From now on, we only need absolute tarpath
|
||||
tarpath = os.path.join(topdir, tarpath)
|
||||
|
||||
if not self._ExtractArchive(tarpath, path=topdir):
|
||||
return False
|
||||
return SyncNetworkHalfResult(False, True)
|
||||
try:
|
||||
platform_utils.remove(tarpath)
|
||||
except OSError as e:
|
||||
_warn("Cannot remove archive %s: %s", tarpath, str(e))
|
||||
self._CopyAndLinkFiles()
|
||||
return True
|
||||
return SyncNetworkHalfResult(True, True)
|
||||
|
||||
# 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.
|
||||
@ -1220,9 +1229,11 @@ class Project(object):
|
||||
depth = self.manifest.manifestProject.depth
|
||||
|
||||
# See if we can skip the network fetch entirely.
|
||||
remote_fetched = False
|
||||
if not (optimized_fetch and
|
||||
(ID_RE.match(self.revisionExpr) and
|
||||
self._CheckForImmutableRevision())):
|
||||
remote_fetched = True
|
||||
if not self._RemoteFetch(
|
||||
initial=is_new,
|
||||
quiet=quiet, verbose=verbose, output_redir=output_redir,
|
||||
@ -1231,7 +1242,7 @@ class Project(object):
|
||||
submodules=submodules, force_sync=force_sync,
|
||||
ssh_proxy=ssh_proxy,
|
||||
clone_filter=clone_filter, retry_fetches=retry_fetches):
|
||||
return False
|
||||
return SyncNetworkHalfResult(False, remote_fetched)
|
||||
|
||||
mp = self.manifest.manifestProject
|
||||
dissociate = mp.dissociate
|
||||
@ -1244,7 +1255,7 @@ class Project(object):
|
||||
if p.stdout and output_redir:
|
||||
output_redir.write(p.stdout)
|
||||
if p.Wait() != 0:
|
||||
return False
|
||||
return SyncNetworkHalfResult(False, remote_fetched)
|
||||
platform_utils.remove(alternates_file)
|
||||
|
||||
if self.worktree:
|
||||
@ -1253,7 +1264,7 @@ class Project(object):
|
||||
self._InitMirrorHead()
|
||||
platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
|
||||
missing_ok=True)
|
||||
return True
|
||||
return SyncNetworkHalfResult(True, remote_fetched)
|
||||
|
||||
def PostRepoUpgrade(self):
|
||||
self._InitHooks()
|
||||
@ -1451,6 +1462,8 @@ class Project(object):
|
||||
cnt_mine += 1
|
||||
|
||||
if not upstream_gain and cnt_mine == len(local_changes):
|
||||
# The copy/linkfile config may have changed.
|
||||
self._CopyAndLinkFiles()
|
||||
return
|
||||
|
||||
if self.IsDirty(consider_untracked=False):
|
||||
@ -2403,16 +2416,16 @@ class Project(object):
|
||||
srcUrl = 'http' + srcUrl[len('persistent-http'):]
|
||||
cmd += [srcUrl]
|
||||
|
||||
if IsTrace():
|
||||
Trace('%s', ' '.join(cmd))
|
||||
if verbose:
|
||||
print('%s: Downloading bundle: %s' % (self.name, srcUrl))
|
||||
stdout = None if verbose else subprocess.PIPE
|
||||
stderr = None if verbose else subprocess.STDOUT
|
||||
try:
|
||||
proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
|
||||
except OSError:
|
||||
return False
|
||||
proc = None
|
||||
with Trace('Fetching bundle: %s', ' '.join(cmd)):
|
||||
if verbose:
|
||||
print('%s: Downloading bundle: %s' % (self.name, srcUrl))
|
||||
stdout = None if verbose else subprocess.PIPE
|
||||
stderr = None if verbose else subprocess.STDOUT
|
||||
try:
|
||||
proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
(output, _) = proc.communicate()
|
||||
curlret = proc.returncode
|
||||
@ -2794,35 +2807,6 @@ class Project(object):
|
||||
else:
|
||||
raise
|
||||
|
||||
def _InitialCheckoutStart(self):
|
||||
"""Called when checking out a project for the first time.
|
||||
|
||||
This will use temporary non-visible paths so we can be safely interrupted
|
||||
without leaving incomplete state behind.
|
||||
"""
|
||||
paths = [f'{x}.tmp' for x in (self.relpath, self.worktree, self.gitdir, self.objdir)]
|
||||
for p in paths:
|
||||
platform_utils.rmtree(p, ignore_errors=True)
|
||||
self.UpdatePaths(*paths)
|
||||
|
||||
def _InitialCheckoutFinalizeNetworkHalf(self):
|
||||
"""Finalize the object dirs after network syncing works."""
|
||||
# Once the network half finishes, we can move the objects into the right
|
||||
# place by removing the ".tmp" suffix on the dirs.
|
||||
platform_utils.rmtree(self.gitdir[:-4], ignore_errors=True)
|
||||
os.rename(self.gitdir, self.gitdir[:-4])
|
||||
self.UpdatePaths(self.relpath, self.worktree, self.gitdir[:-4], self.objdir[:-4])
|
||||
|
||||
def _InitialCheckoutFinalizeLocalHalf(self):
|
||||
"""Finalize the initial checkout and make it available."""
|
||||
assert self.gitdir == self.objdir
|
||||
# Once the local half finishes, we can move the manifest dir into the right
|
||||
# place by removing the ".tmp" suffix on the dirs.
|
||||
platform_utils.rmtree(self.worktree[:-4], ignore_errors=True)
|
||||
os.rename(self.worktree, self.worktree[:-4])
|
||||
self.UpdatePaths(
|
||||
self.relpath[:-4], self.worktree[:-4], self.gitdir, self.objdir)
|
||||
|
||||
def _InitGitWorktree(self):
|
||||
"""Init the project using git worktrees."""
|
||||
self.bare_git.worktree('prune')
|
||||
@ -3709,8 +3693,6 @@ class ManifestProject(MetaProject):
|
||||
(GitConfig.ForUser().UrlInsteadOf(manifest_url),),
|
||||
file=sys.stderr)
|
||||
|
||||
self._InitialCheckoutStart()
|
||||
|
||||
# The manifest project object doesn't keep track of the path on the
|
||||
# server where this git is located, so let's save that here.
|
||||
mirrored_manifest_git = None
|
||||
@ -3867,17 +3849,19 @@ class ManifestProject(MetaProject):
|
||||
is_new=is_new, quiet=not verbose, verbose=verbose,
|
||||
clone_bundle=clone_bundle, current_branch_only=current_branch_only,
|
||||
tags=tags, submodules=submodules, clone_filter=clone_filter,
|
||||
partial_clone_exclude=self.manifest.PartialCloneExclude):
|
||||
partial_clone_exclude=self.manifest.PartialCloneExclude).success:
|
||||
r = self.GetRemote()
|
||||
print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
|
||||
|
||||
# Better delete the manifest git dir if we created it; otherwise next
|
||||
# time (when user fixes problems) we won't go through the "is_new" logic.
|
||||
if is_new:
|
||||
platform_utils.rmtree(self.gitdir)
|
||||
return False
|
||||
|
||||
if manifest_branch:
|
||||
self.MetaBranchSwitch(submodules=submodules)
|
||||
|
||||
if is_new:
|
||||
self._InitialCheckoutFinalizeNetworkHalf()
|
||||
|
||||
syncbuf = SyncBuffer(self.config)
|
||||
self.Sync_LocalHalf(syncbuf, submodules=submodules)
|
||||
syncbuf.Finish()
|
||||
@ -3900,9 +3884,6 @@ class ManifestProject(MetaProject):
|
||||
with open(dest, 'wb') as f:
|
||||
f.write(manifest_data)
|
||||
|
||||
if is_new:
|
||||
self._InitialCheckoutFinalizeLocalHalf()
|
||||
|
||||
try:
|
||||
self.manifest.Link(manifest_name)
|
||||
except ManifestParseError as e:
|
||||
|
@ -59,18 +59,26 @@ def main(argv):
|
||||
version = RepoSourceVersion()
|
||||
cmdlist = [['help2man', '-N', '-n', f'repo {cmd} - manual page for repo {cmd}',
|
||||
'-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]
|
||||
cmdlist.append(['help2man', '-N', '-n', 'repository management tool built on top of git',
|
||||
'-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'])
|
||||
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
repo_dir = Path(tempdir) / '.repo'
|
||||
tempdir = Path(tempdir)
|
||||
repo_dir = tempdir / '.repo'
|
||||
repo_dir.mkdir()
|
||||
(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.
|
||||
with multiprocessing.Pool() as pool:
|
||||
pool.map(partial(worker, cwd=tempdir, check=True), cmdlist)
|
||||
|
5
repo
5
repo
@ -316,9 +316,10 @@ def InitParser(parser, gitc_init=False):
|
||||
help='download the manifest as a static file '
|
||||
'rather then create a git checkout of '
|
||||
'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 '
|
||||
'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
|
||||
# specified in the manifest itself.
|
||||
|
106
repo_trace.py
106
repo_trace.py
@ -15,26 +15,124 @@
|
||||
"""Logic for tracing repo interactions.
|
||||
|
||||
Activated via `repo --trace ...` or `REPO_TRACE=1 repo ...`.
|
||||
|
||||
Temporary: Tracing is always on. Set `REPO_TRACE=0` to turn off.
|
||||
To also include trace outputs in stderr do `repo --trace_to_stderr ...`
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from contextlib import ContextDecorator
|
||||
|
||||
import platform_utils
|
||||
|
||||
# Env var to implicitly turn on tracing.
|
||||
REPO_TRACE = 'REPO_TRACE'
|
||||
|
||||
_TRACE = os.environ.get(REPO_TRACE) == '1'
|
||||
# Temporarily set tracing to always on unless user expicitly sets to 0.
|
||||
_TRACE = os.environ.get(REPO_TRACE) != '0'
|
||||
_TRACE_TO_STDERR = False
|
||||
_TRACE_FILE = None
|
||||
_TRACE_FILE_NAME = 'TRACE_FILE'
|
||||
_MAX_SIZE = 70 # in mb
|
||||
_NEW_COMMAND_SEP = '+++++++++++++++NEW COMMAND+++++++++++++++++++'
|
||||
|
||||
|
||||
def IsTraceToStderr():
|
||||
return _TRACE_TO_STDERR
|
||||
|
||||
|
||||
def IsTrace():
|
||||
return _TRACE
|
||||
|
||||
|
||||
def SetTraceToStderr():
|
||||
global _TRACE_TO_STDERR
|
||||
_TRACE_TO_STDERR = True
|
||||
|
||||
|
||||
def SetTrace():
|
||||
global _TRACE
|
||||
_TRACE = True
|
||||
|
||||
|
||||
def Trace(fmt, *args):
|
||||
if IsTrace():
|
||||
print(fmt % args, file=sys.stderr)
|
||||
def _SetTraceFile():
|
||||
global _TRACE_FILE
|
||||
_TRACE_FILE = _GetTraceFile()
|
||||
|
||||
|
||||
class Trace(ContextDecorator):
|
||||
|
||||
def _time(self):
|
||||
"""Generate nanoseconds of time in a py3.6 safe way"""
|
||||
return int(time.time()*1e+9)
|
||||
|
||||
def __init__(self, fmt, *args, first_trace=False):
|
||||
if not IsTrace():
|
||||
return
|
||||
self._trace_msg = fmt % args
|
||||
|
||||
if not _TRACE_FILE:
|
||||
_SetTraceFile()
|
||||
|
||||
if first_trace:
|
||||
_ClearOldTraces()
|
||||
self._trace_msg = '%s %s' % (_NEW_COMMAND_SEP, self._trace_msg)
|
||||
|
||||
|
||||
def __enter__(self):
|
||||
if not IsTrace():
|
||||
return self
|
||||
|
||||
print_msg = f'PID: {os.getpid()} START: {self._time()} :' + self._trace_msg + '\n'
|
||||
|
||||
with open(_TRACE_FILE, 'a') as f:
|
||||
print(print_msg, file=f)
|
||||
|
||||
if _TRACE_TO_STDERR:
|
||||
print(print_msg, file=sys.stderr)
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc):
|
||||
if not IsTrace():
|
||||
return False
|
||||
|
||||
print_msg = f'PID: {os.getpid()} END: {self._time()} :' + self._trace_msg + '\n'
|
||||
|
||||
with open(_TRACE_FILE, 'a') as f:
|
||||
print(print_msg, file=f)
|
||||
|
||||
if _TRACE_TO_STDERR:
|
||||
print(print_msg, file=sys.stderr)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _GetTraceFile():
|
||||
"""Get the trace file or create one."""
|
||||
# TODO: refactor to pass repodir to Trace.
|
||||
repo_dir = os.path.dirname(os.path.dirname(__file__))
|
||||
trace_file = os.path.join(repo_dir, _TRACE_FILE_NAME)
|
||||
print('Trace outputs in %s' % trace_file, file=sys.stderr)
|
||||
return trace_file
|
||||
|
||||
def _ClearOldTraces():
|
||||
"""Clear the oldest commands if trace file is too big.
|
||||
|
||||
Note: If the trace file contains output from two `repo`
|
||||
commands that were running at the same time, this
|
||||
will not work precisely.
|
||||
"""
|
||||
if os.path.isfile(_TRACE_FILE):
|
||||
while os.path.getsize(_TRACE_FILE)/(1024*1024) > _MAX_SIZE:
|
||||
temp_file = _TRACE_FILE + '.tmp'
|
||||
with open(_TRACE_FILE, 'r', errors='ignore') as fin:
|
||||
with open(temp_file, 'w') as tf:
|
||||
trace_lines = fin.readlines()
|
||||
for i , l in enumerate(trace_lines):
|
||||
if 'END:' in l and _NEW_COMMAND_SEP in l:
|
||||
tf.writelines(trace_lines[i+1:])
|
||||
break
|
||||
platform_utils.rename(temp_file, _TRACE_FILE)
|
||||
|
@ -20,6 +20,7 @@ import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import repo_trace
|
||||
|
||||
|
||||
def find_pytest():
|
||||
|
37
ssh.py
37
ssh.py
@ -182,28 +182,29 @@ class ProxyManager:
|
||||
# be important because we can't tell that that 'git@myhost.com' is the same
|
||||
# as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
|
||||
check_command = command_base + ['-O', 'check']
|
||||
try:
|
||||
Trace(': %s', ' '.join(check_command))
|
||||
check_process = subprocess.Popen(check_command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
check_process.communicate() # read output, but ignore it...
|
||||
isnt_running = check_process.wait()
|
||||
with Trace('Call to ssh (check call): %s', ' '.join(check_command)):
|
||||
try:
|
||||
check_process = subprocess.Popen(check_command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
check_process.communicate() # read output, but ignore it...
|
||||
isnt_running = check_process.wait()
|
||||
|
||||
if not isnt_running:
|
||||
# Our double-check found that the master _was_ infact running. Add to
|
||||
# the list of keys.
|
||||
self._master_keys[key] = True
|
||||
return True
|
||||
except Exception:
|
||||
# Ignore excpetions. We we will fall back to the normal command and print
|
||||
# to the log there.
|
||||
pass
|
||||
if not isnt_running:
|
||||
# Our double-check found that the master _was_ infact running. Add to
|
||||
# the list of keys.
|
||||
self._master_keys[key] = True
|
||||
return True
|
||||
except Exception:
|
||||
# Ignore excpetions. We we will fall back to the normal command and
|
||||
# print to the log there.
|
||||
pass
|
||||
|
||||
command = command_base[:1] + ['-M', '-N'] + command_base[1:]
|
||||
p = None
|
||||
try:
|
||||
Trace(': %s', ' '.join(command))
|
||||
p = subprocess.Popen(command)
|
||||
with Trace('Call to ssh: %s', ' '.join(command)):
|
||||
p = subprocess.Popen(command)
|
||||
except Exception as e:
|
||||
self._master_broken.value = True
|
||||
print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
|
||||
|
@ -118,6 +118,16 @@ synced and their revisions won't be found.
|
||||
self.printRevision(project.revisionExpr)
|
||||
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']:
|
||||
self.out.nl()
|
||||
self.printText('changed projects : \n')
|
||||
|
@ -68,7 +68,8 @@ use for this GITC client.
|
||||
sys.exit(1)
|
||||
manifest_file = opt.manifest_file
|
||||
|
||||
manifest = GitcManifest(self.repodir, gitc_client)
|
||||
manifest = GitcManifest(self.repodir, os.path.join(self.client_dir,
|
||||
'.manifest'))
|
||||
manifest.Override(manifest_file)
|
||||
gitc_utils.generate_gitc_manifest(None, manifest)
|
||||
print('Please run `cd %s` to view your GITC client.' %
|
||||
|
@ -51,7 +51,7 @@ need to be performed by an end-user.
|
||||
_PostRepoUpgrade(self.manifest)
|
||||
|
||||
else:
|
||||
if not rp.Sync_NetworkHalf():
|
||||
if not rp.Sync_NetworkHalf().success:
|
||||
print("error: can't update repo", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
272
subcmds/sync.py
272
subcmds/sync.py
@ -21,10 +21,12 @@ import multiprocessing
|
||||
import netrc
|
||||
from optparse import SUPPRESS_HELP
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from typing import NamedTuple, List, Set
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
@ -58,11 +60,71 @@ from error import RepoChangedException, GitError, ManifestParseError
|
||||
import platform_utils
|
||||
from project import SyncBuffer
|
||||
from progress import Progress
|
||||
from repo_trace import Trace
|
||||
import ssh
|
||||
from wrapper import Wrapper
|
||||
from manifest_xml import GitcManifest
|
||||
|
||||
_ONE_DAY_S = 24 * 60 * 60
|
||||
# Env var to implicitly turn off object backups.
|
||||
REPO_BACKUP_OBJECTS = 'REPO_BACKUP_OBJECTS'
|
||||
_BACKUP_OBJECTS = os.environ.get(REPO_BACKUP_OBJECTS) != '0'
|
||||
|
||||
# Env var to implicitly turn auto-gc back on.
|
||||
_REPO_AUTO_GC = 'REPO_AUTO_GC'
|
||||
_AUTO_GC = os.environ.get(_REPO_AUTO_GC) == '1'
|
||||
|
||||
|
||||
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):
|
||||
@ -141,6 +203,9 @@ exist locally.
|
||||
The --prune option can be used to remove any refs that no longer
|
||||
exist on the remote.
|
||||
|
||||
The --auto-gc option can be used to trigger garbage collection on all
|
||||
projects. By default, repo does not run garbage collection.
|
||||
|
||||
# SSH Connections
|
||||
|
||||
If at least one project remote URL uses an SSH connection (ssh://,
|
||||
@ -250,6 +315,10 @@ later is required to fix a server side protocol bug.
|
||||
help='delete refs that no longer exist on the remote (default)')
|
||||
p.add_option('--no-prune', dest='prune', action='store_false',
|
||||
help='do not delete refs that no longer exist on the remote')
|
||||
p.add_option('--auto-gc', action='store_true', default=None,
|
||||
help='run garbage collection on all synced projects')
|
||||
p.add_option('--no-auto-gc', dest='auto_gc', action='store_false',
|
||||
help='do not run garbage collection on any projects (default)')
|
||||
if show_smart:
|
||||
p.add_option('-s', '--smart-sync',
|
||||
dest='smart_sync', action='store_true',
|
||||
@ -406,7 +475,7 @@ later is required to fix a server side protocol bug.
|
||||
success = False
|
||||
buf = io.StringIO()
|
||||
try:
|
||||
success = project.Sync_NetworkHalf(
|
||||
sync_result = project.Sync_NetworkHalf(
|
||||
quiet=opt.quiet,
|
||||
verbose=opt.verbose,
|
||||
output_redir=buf,
|
||||
@ -420,6 +489,7 @@ later is required to fix a server side protocol bug.
|
||||
ssh_proxy=self.ssh_proxy,
|
||||
clone_filter=project.manifest.CloneFilter,
|
||||
partial_clone_exclude=project.manifest.PartialCloneExclude)
|
||||
success = sync_result.success
|
||||
|
||||
output = buf.getvalue()
|
||||
if (opt.verbose or not success) and output:
|
||||
@ -437,7 +507,8 @@ later is required to fix a server side protocol bug.
|
||||
raise
|
||||
|
||||
finish = time.time()
|
||||
return (success, project, start, finish)
|
||||
return _FetchOneResult(success, project, start, finish,
|
||||
sync_result.remote_fetched)
|
||||
|
||||
@classmethod
|
||||
def _FetchInitChild(cls, ssh_proxy):
|
||||
@ -448,6 +519,7 @@ later is required to fix a server side protocol bug.
|
||||
|
||||
jobs = opt.jobs_network
|
||||
fetched = set()
|
||||
remote_fetched = set()
|
||||
pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet)
|
||||
|
||||
objdir_project_map = dict()
|
||||
@ -458,10 +530,16 @@ later is required to fix a server side protocol bug.
|
||||
def _ProcessResults(results_sets):
|
||||
ret = True
|
||||
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.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
|
||||
start, finish, success)
|
||||
if result.remote_fetched:
|
||||
remote_fetched.add(project)
|
||||
# Check for any errors before running any more tasks.
|
||||
# ...we'll let existing jobs finish, though.
|
||||
if not success:
|
||||
@ -519,7 +597,7 @@ later is required to fix a server side protocol bug.
|
||||
if not self.outer_client.manifest.IsArchive:
|
||||
self._GCProjects(projects, opt, err_event)
|
||||
|
||||
return (ret, fetched)
|
||||
return _FetchResult(ret, fetched)
|
||||
|
||||
def _FetchMain(self, opt, args, all_projects, err_event,
|
||||
ssh_proxy, manifest):
|
||||
@ -545,7 +623,9 @@ later is required to fix a server side protocol bug.
|
||||
to_fetch.extend(all_projects)
|
||||
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:
|
||||
err_event.set()
|
||||
|
||||
@ -555,7 +635,7 @@ later is required to fix a server side protocol bug.
|
||||
if err_event.is_set():
|
||||
print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return
|
||||
return _FetchMainResult([])
|
||||
|
||||
# Iteratively fetch missing and/or nested unregistered submodules
|
||||
previously_missing_set = set()
|
||||
@ -578,12 +658,14 @@ later is required to fix a server side protocol bug.
|
||||
if previously_missing_set == missing_set:
|
||||
break
|
||||
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:
|
||||
err_event.set()
|
||||
fetched.update(new_fetched)
|
||||
|
||||
return all_projects
|
||||
return _FetchMainResult(all_projects)
|
||||
|
||||
def _CheckoutOne(self, detach_head, force_sync, project):
|
||||
"""Checkout work tree for one project
|
||||
@ -615,7 +697,7 @@ later is required to fix a server side protocol bug.
|
||||
if not success:
|
||||
print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
|
||||
finish = time.time()
|
||||
return (success, project, start, finish)
|
||||
return _CheckoutOneResult(success, project, start, finish)
|
||||
|
||||
def _Checkout(self, all_projects, opt, err_results):
|
||||
"""Checkout projects listed in all_projects
|
||||
@ -630,7 +712,11 @@ later is required to fix a server side protocol bug.
|
||||
|
||||
def _ProcessResults(pool, pm, results):
|
||||
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,
|
||||
start, finish, success)
|
||||
# Check for any errors before running any more tasks.
|
||||
@ -652,33 +738,121 @@ later is required to fix a server side protocol bug.
|
||||
callback=_ProcessResults,
|
||||
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
|
||||
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):
|
||||
with Trace('%s saved %s', bare_git._project.name, 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)
|
||||
|
||||
@staticmethod
|
||||
def _GetPreciousObjectsState(project: Project, opt):
|
||||
"""Get the preciousObjects state for the project.
|
||||
|
||||
Args:
|
||||
project (Project): the project to examine, and possibly correct.
|
||||
opt (optparse.Values): options given to sync.
|
||||
|
||||
Returns:
|
||||
Expected state of extensions.preciousObjects:
|
||||
False: Should be disabled. (not present)
|
||||
True: Should be enabled.
|
||||
"""
|
||||
if project.use_git_worktrees:
|
||||
return False
|
||||
projects = project.manifest.GetProjectsWithName(project.name,
|
||||
all_manifests=True)
|
||||
if len(projects) == 1:
|
||||
return False
|
||||
relpath = project.RelPath(local=opt.this_manifest_only)
|
||||
if len(projects) > 1:
|
||||
# Objects are potentially shared with another project.
|
||||
# See the logic in Project.Sync_NetworkHalf regarding UseAlternates.
|
||||
# - When False, shared projects share (via symlink)
|
||||
# .repo/project-objects/{PROJECT_NAME}.git as the one-and-only objects
|
||||
# directory. All objects are precious, since there is no project with a
|
||||
# complete set of refs.
|
||||
# - When True, shared projects share (via info/alternates)
|
||||
# .repo/project-objects/{PROJECT_NAME}.git as an alternate object store,
|
||||
# which is written only on the first clone of the project, and is not
|
||||
# written subsequently. (When Sync_NetworkHalf sees that it exists, it
|
||||
# makes sure that the alternates file points there, and uses a
|
||||
# project-local .git/objects directory for all syncs going forward.
|
||||
# We do not support switching between the options. The environment
|
||||
# variable is present for testing and migration only.
|
||||
return not project.UseAlternates
|
||||
print(f'\r{relpath}: project not found in manifest.', file=sys.stderr)
|
||||
return False
|
||||
|
||||
def _RepairPreciousObjectsState(self, project: Project, opt):
|
||||
"""Correct the preciousObjects state for the project.
|
||||
|
||||
Args:
|
||||
project (Project): the project to examine, and possibly correct.
|
||||
opt (optparse.Values): options given to sync.
|
||||
"""
|
||||
expected = self._GetPreciousObjectsState(project, opt)
|
||||
actual = project.config.GetBoolean('extensions.preciousObjects') or False
|
||||
relpath = project.RelPath(local = opt.this_manifest_only)
|
||||
|
||||
if (expected != actual and
|
||||
not project.config.GetBoolean('repo.preservePreciousObjects')):
|
||||
# If this is unexpected, log it and repair.
|
||||
Trace(f'{relpath} expected preciousObjects={expected}, got {actual}')
|
||||
if expected:
|
||||
if not opt.quiet:
|
||||
print('\r%s: Shared project %s found, disabling pruning.' %
|
||||
(relpath, project.name))
|
||||
if git_require((2, 7, 0)):
|
||||
project.EnableRepositoryExtension('preciousObjects')
|
||||
else:
|
||||
# This isn't perfect, but it's the best we can do with old git.
|
||||
print('\r%s: WARNING: shared projects are unreliable when using '
|
||||
'old versions of git; please upgrade to git-2.7.0+.'
|
||||
% (relpath,),
|
||||
file=sys.stderr)
|
||||
project.config.SetString('gc.pruneExpire', 'never')
|
||||
else:
|
||||
if not opt.quiet:
|
||||
print(f'\r{relpath}: not shared, disabling pruning.')
|
||||
project.config.SetString('extensions.preciousObjects', None)
|
||||
project.config.SetString('gc.pruneExpire', None)
|
||||
|
||||
def _GCProjects(self, projects, opt, err_event):
|
||||
pm = Progress('Garbage collecting', len(projects), delay=False, quiet=opt.quiet)
|
||||
"""Perform garbage collection.
|
||||
|
||||
If We are skipping garbage collection (opt.auto_gc not set), we still want
|
||||
to potentially mark objects precious, so that `git gc` does not discard
|
||||
shared objects.
|
||||
"""
|
||||
pm = Progress(f'{"" if opt.auto_gc else "NOT "}Garbage collecting',
|
||||
len(projects), delay=False, quiet=opt.quiet)
|
||||
pm.update(inc=0, msg='prescan')
|
||||
|
||||
tidy_dirs = {}
|
||||
for project in projects:
|
||||
# Make sure pruning never kicks in with shared projects that do not use
|
||||
# alternates to avoid corruption.
|
||||
if (not project.use_git_worktrees and
|
||||
len(project.manifest.GetProjectsWithName(project.name, all_manifests=True)) > 1):
|
||||
if project.UseAlternates:
|
||||
# Undo logic set by previous versions of repo.
|
||||
project.config.SetString('extensions.preciousObjects', None)
|
||||
project.config.SetString('gc.pruneExpire', None)
|
||||
else:
|
||||
if not opt.quiet:
|
||||
print('\r%s: Shared project %s found, disabling pruning.' %
|
||||
(project.relpath, project.name))
|
||||
if git_require((2, 7, 0)):
|
||||
project.EnableRepositoryExtension('preciousObjects')
|
||||
else:
|
||||
# This isn't perfect, but it's the best we can do with old git.
|
||||
print('\r%s: WARNING: shared projects are unreliable when using old '
|
||||
'versions of git; please upgrade to git-2.7.0+.'
|
||||
% (project.relpath,),
|
||||
file=sys.stderr)
|
||||
project.config.SetString('gc.pruneExpire', 'never')
|
||||
self._RepairPreciousObjectsState(project, opt)
|
||||
|
||||
project.config.SetString('gc.autoDetach', 'false')
|
||||
# Only call git gc once per objdir, but call pack-refs for the remainder.
|
||||
if project.objdir not in tidy_dirs:
|
||||
@ -692,15 +866,28 @@ later is required to fix a server side protocol bug.
|
||||
project.bare_git,
|
||||
)
|
||||
|
||||
if not opt.auto_gc:
|
||||
pm.end()
|
||||
return
|
||||
|
||||
jobs = opt.jobs
|
||||
|
||||
gc_args = ['--auto']
|
||||
backup_cruft = False
|
||||
if git_require((2, 37, 0)):
|
||||
gc_args.append('--cruft')
|
||||
backup_cruft = True
|
||||
pack_refs_args = ()
|
||||
if jobs < 2:
|
||||
for (run_gc, bare_git) in tidy_dirs.values():
|
||||
pm.update(msg=bare_git._project.name)
|
||||
|
||||
if run_gc:
|
||||
bare_git.gc('--auto')
|
||||
bare_git.gc(*gc_args)
|
||||
else:
|
||||
bare_git.pack_refs()
|
||||
bare_git.pack_refs(*pack_refs_args)
|
||||
if backup_cruft:
|
||||
self._backup_cruft(bare_git)
|
||||
pm.end()
|
||||
return
|
||||
|
||||
@ -715,15 +902,17 @@ later is required to fix a server side protocol bug.
|
||||
try:
|
||||
try:
|
||||
if run_gc:
|
||||
bare_git.gc('--auto', config=config)
|
||||
bare_git.gc(*gc_args, config=config)
|
||||
else:
|
||||
bare_git.pack_refs(config=config)
|
||||
bare_git.pack_refs(*pack_refs_args, config=config)
|
||||
except GitError:
|
||||
err_event.set()
|
||||
except Exception:
|
||||
err_event.set()
|
||||
raise
|
||||
finally:
|
||||
if backup_cruft:
|
||||
self._backup_cruft(bare_git)
|
||||
pm.finish(bare_git._project.name)
|
||||
sem.release()
|
||||
|
||||
@ -1033,6 +1222,11 @@ 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:
|
||||
print(f"Will run `git gc --auto` because {_REPO_AUTO_GC} is set.",
|
||||
file=sys.stderr)
|
||||
opt.auto_gc = True
|
||||
|
||||
def Execute(self, opt, args):
|
||||
manifest = self.outer_manifest
|
||||
if not opt.outer_manifest:
|
||||
@ -1160,6 +1354,7 @@ later is required to fix a server side protocol bug.
|
||||
|
||||
err_network_sync = False
|
||||
err_update_projects = False
|
||||
err_update_linkfiles = False
|
||||
|
||||
self._fetch_times = _FetchTimes(manifest)
|
||||
if not opt.local_only:
|
||||
@ -1167,8 +1362,9 @@ later is required to fix a server side protocol bug.
|
||||
with ssh.ProxyManager(manager) as ssh_proxy:
|
||||
# Initialize the socket dir once in the parent.
|
||||
ssh_proxy.sock()
|
||||
all_projects = self._FetchMain(opt, args, all_projects, err_event,
|
||||
ssh_proxy, manifest)
|
||||
result = self._FetchMain(opt, args, all_projects, err_event,
|
||||
ssh_proxy, manifest)
|
||||
all_projects = result.all_projects
|
||||
|
||||
if opt.network_only:
|
||||
return
|
||||
|
@ -19,6 +19,7 @@ import tempfile
|
||||
import unittest
|
||||
|
||||
import git_config
|
||||
import repo_trace
|
||||
|
||||
|
||||
def fixture(*paths):
|
||||
@ -33,9 +34,16 @@ class GitConfigReadOnlyTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Create a GitConfig object using the test.gitconfig fixture.
|
||||
"""
|
||||
|
||||
self.tempdirobj = tempfile.TemporaryDirectory(prefix='repo_tests')
|
||||
repo_trace._TRACE_FILE = os.path.join(self.tempdirobj.name, 'TRACE_FILE_from_test')
|
||||
|
||||
config_fixture = fixture('test.gitconfig')
|
||||
self.config = git_config.GitConfig(config_fixture)
|
||||
|
||||
def tearDown(self):
|
||||
self.tempdirobj.cleanup()
|
||||
|
||||
def test_GetString_with_empty_config_values(self):
|
||||
"""
|
||||
Test config entries with no value.
|
||||
@ -109,9 +117,15 @@ class GitConfigReadWriteTests(unittest.TestCase):
|
||||
"""Read/write tests of the GitConfig class."""
|
||||
|
||||
def setUp(self):
|
||||
self.tempdirobj = tempfile.TemporaryDirectory(prefix='repo_tests')
|
||||
repo_trace._TRACE_FILE = os.path.join(self.tempdirobj.name, 'TRACE_FILE_from_test')
|
||||
|
||||
self.tmpfile = tempfile.NamedTemporaryFile()
|
||||
self.config = self.get_config()
|
||||
|
||||
def tearDown(self):
|
||||
self.tempdirobj.cleanup()
|
||||
|
||||
def get_config(self):
|
||||
"""Get a new GitConfig instance."""
|
||||
return git_config.GitConfig(self.tmpfile.name)
|
||||
|
@ -24,6 +24,7 @@ from unittest import mock
|
||||
import git_superproject
|
||||
import git_trace2_event_log
|
||||
import manifest_xml
|
||||
import repo_trace
|
||||
from test_manifest_xml import sort_attributes
|
||||
|
||||
|
||||
@ -39,6 +40,7 @@ class SuperprojectTestCase(unittest.TestCase):
|
||||
"""Set up superproject every time."""
|
||||
self.tempdirobj = tempfile.TemporaryDirectory(prefix='repo_tests')
|
||||
self.tempdir = self.tempdirobj.name
|
||||
repo_trace._TRACE_FILE = os.path.join(self.tempdir, 'TRACE_FILE_from_test')
|
||||
self.repodir = os.path.join(self.tempdir, '.repo')
|
||||
self.manifest_file = os.path.join(
|
||||
self.repodir, manifest_xml.MANIFEST_FILE_NAME)
|
||||
|
@ -23,6 +23,7 @@ import xml.dom.minidom
|
||||
|
||||
import error
|
||||
import manifest_xml
|
||||
import repo_trace
|
||||
|
||||
|
||||
# Invalid paths that we don't want in the filesystem.
|
||||
@ -93,6 +94,7 @@ class ManifestParseTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tempdirobj = tempfile.TemporaryDirectory(prefix='repo_tests')
|
||||
self.tempdir = self.tempdirobj.name
|
||||
repo_trace._TRACE_FILE = os.path.join(self.tempdir, 'TRACE_FILE_from_test')
|
||||
self.repodir = os.path.join(self.tempdir, '.repo')
|
||||
self.manifest_dir = os.path.join(self.repodir, 'manifests')
|
||||
self.manifest_file = os.path.join(
|
||||
@ -262,10 +264,10 @@ class XmlManifestTests(ManifestParseTestCase):
|
||||
'<project name="r" groups="keep"/>'
|
||||
'</manifest>')
|
||||
self.assertEqual(
|
||||
manifest.ToXml(omit_local=True).toxml(),
|
||||
sort_attributes(manifest.ToXml(omit_local=True).toxml()),
|
||||
'<?xml version="1.0" ?><manifest>'
|
||||
'<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
|
||||
'<project name="q"/><project name="r" groups="keep"/></manifest>')
|
||||
'<remote fetch=".." name="a"/><default remote="a" revision="r"/>'
|
||||
'<project name="q"/><project groups="keep" name="r"/></manifest>')
|
||||
|
||||
def test_toxml_with_local(self):
|
||||
"""Does include local_manifests projects when omit_local=False."""
|
||||
@ -277,11 +279,11 @@ class XmlManifestTests(ManifestParseTestCase):
|
||||
'<project name="r" groups="keep"/>'
|
||||
'</manifest>')
|
||||
self.assertEqual(
|
||||
manifest.ToXml(omit_local=False).toxml(),
|
||||
sort_attributes(manifest.ToXml(omit_local=False).toxml()),
|
||||
'<?xml version="1.0" ?><manifest>'
|
||||
'<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
|
||||
'<project name="p" groups="local::me"/>'
|
||||
'<project name="q"/><project name="r" groups="keep"/></manifest>')
|
||||
'<remote fetch=".." name="a"/><default remote="a" revision="r"/>'
|
||||
'<project groups="local::me" name="p"/>'
|
||||
'<project name="q"/><project groups="keep" name="r"/></manifest>')
|
||||
|
||||
def test_repo_hooks(self):
|
||||
"""Check repo-hooks settings."""
|
||||
@ -874,3 +876,27 @@ class ExtendProjectElementTests(ManifestParseTestCase):
|
||||
else:
|
||||
self.assertEqual(manifest.projects[0].relpath, 'bar')
|
||||
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')
|
||||
|
@ -26,6 +26,7 @@ import git_command
|
||||
import git_config
|
||||
import platform_utils
|
||||
import project
|
||||
import repo_trace
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
@ -64,6 +65,13 @@ class FakeProject(object):
|
||||
class ReviewableBranchTests(unittest.TestCase):
|
||||
"""Check ReviewableBranch behavior."""
|
||||
|
||||
def setUp(self):
|
||||
self.tempdirobj = tempfile.TemporaryDirectory(prefix='repo_tests')
|
||||
repo_trace._TRACE_FILE = os.path.join(self.tempdirobj.name, 'TRACE_FILE_from_test')
|
||||
|
||||
def tearDown(self):
|
||||
self.tempdirobj.cleanup()
|
||||
|
||||
def test_smoke(self):
|
||||
"""A quick run through everything."""
|
||||
with TempGitTree() as tempdir:
|
||||
|
@ -11,9 +11,9 @@
|
||||
# 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.
|
||||
|
||||
"""Unittests for the subcmds/sync.py module."""
|
||||
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
@ -21,17 +21,14 @@ import pytest
|
||||
from subcmds import sync
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'use_superproject, cli_args, result',
|
||||
[
|
||||
@pytest.mark.parametrize('use_superproject, cli_args, result', [
|
||||
(True, ['--current-branch'], True),
|
||||
(True, ['--no-current-branch'], True),
|
||||
(True, [], True),
|
||||
(False, ['--current-branch'], True),
|
||||
(False, ['--no-current-branch'], False),
|
||||
(False, [], None),
|
||||
]
|
||||
)
|
||||
])
|
||||
def test_get_current_branch_only(use_superproject, cli_args, result):
|
||||
"""Test Sync._GetCurrentBranchOnly logic.
|
||||
|
||||
@ -41,5 +38,49 @@ def test_get_current_branch_only(use_superproject, cli_args, result):
|
||||
cmd = sync.Sync()
|
||||
opts, _ = cmd.OptionParser.parse_args(cli_args)
|
||||
|
||||
with mock.patch('git_superproject.UseSuperproject', return_value=use_superproject):
|
||||
with mock.patch('git_superproject.UseSuperproject',
|
||||
return_value=use_superproject):
|
||||
assert cmd._GetCurrentBranchOnly(opts, cmd.manifest) == result
|
||||
|
||||
|
||||
class GetPreciousObjectsState(unittest.TestCase):
|
||||
"""Tests for _GetPreciousObjectsState."""
|
||||
|
||||
def setUp(self):
|
||||
"""Common setup."""
|
||||
self.cmd = sync.Sync()
|
||||
self.project = p = mock.MagicMock(use_git_worktrees=False,
|
||||
UseAlternates=False)
|
||||
p.manifest.GetProjectsWithName.return_value = [p]
|
||||
|
||||
self.opt = mock.Mock(spec_set=['this_manifest_only'])
|
||||
self.opt.this_manifest_only = False
|
||||
|
||||
def test_worktrees(self):
|
||||
"""False for worktrees."""
|
||||
self.project.use_git_worktrees = True
|
||||
self.assertFalse(self.cmd._GetPreciousObjectsState(self.project, self.opt))
|
||||
|
||||
def test_not_shared(self):
|
||||
"""Singleton project."""
|
||||
self.assertFalse(self.cmd._GetPreciousObjectsState(self.project, self.opt))
|
||||
|
||||
def test_shared(self):
|
||||
"""Shared project."""
|
||||
self.project.manifest.GetProjectsWithName.return_value = [
|
||||
self.project, self.project
|
||||
]
|
||||
self.assertTrue(self.cmd._GetPreciousObjectsState(self.project, self.opt))
|
||||
|
||||
def test_shared_with_alternates(self):
|
||||
"""Shared project, with alternates."""
|
||||
self.project.manifest.GetProjectsWithName.return_value = [
|
||||
self.project, self.project
|
||||
]
|
||||
self.project.UseAlternates = True
|
||||
self.assertFalse(self.cmd._GetPreciousObjectsState(self.project, self.opt))
|
||||
|
||||
def test_not_found(self):
|
||||
"""Project not found in manifest."""
|
||||
self.project.manifest.GetProjectsWithName.return_value = []
|
||||
self.assertFalse(self.cmd._GetPreciousObjectsState(self.project, self.opt))
|
||||
|
Reference in New Issue
Block a user