Compare commits

..

15 Commits

Author SHA1 Message Date
859d3d9580 GitcInit: fix gitc-init failure
Aligns argument usage of refactored GitcManifest (8c1e9cbef
"manifest_xml: refactor manifest parsing from client management") to fix
the `repo gitc-init` error: `fatal: manifest_file must be abspath`.

Change-Id: I1728032cce3f39ed1077bbb7ef714410c2c49e1a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/350374
Tested-by: Woody Lin <woodylin@google.com>
Reviewed-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Xin Li <delphij@google.com>
2022-11-04 17:30:40 +00:00
fa8d939c8f sync: clear preciousObjects when set in error.
If this is a project that is not using object sharing (there is only one
copy of the remote project) then clear preciousObjects.

To override this for a project, run:

  git config --replace-all repo.preservePreciousObjects true

Change-Id: If3ea061c631c5ecd44ead84f68576012e2c7405c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/350235
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-11-03 23:01:16 +00:00
a6c52f566a Set tracing to always on and save to .repo/TRACE_FILE.
- add `--trace_to_stderr` option so stderr will include trace outputs and any other errors that get sent to stderr
- while TRACE_FILE will only include trace outputs

piggy-backing on: https://gerrit-review.googlesource.com/c/git-repo/+/349154

Change-Id: I3895a84de4b2784f17fac4325521cd5e72e645e2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/350114
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Joanna Wang <jojwang@google.com>
2022-11-03 21:07:07 +00:00
0d130d2da0 tests: Make the tests pass for Python < 3.8
Before Python 3.8, xml.dom.minidom sorted the attributes of an element
when writing it to a file, while later versions output the attributes
in the order they were created. Avoid these differences by sorting the
attributes for each element before comparing the generated manifests
with the expected ones.

This corresponds to commit 5d58c18, but for new tests introduced since
it was integrated.

Change-Id: I5c360656a0968e6e8d57eb068c8e87da7dfa61c1
Signed-off-by: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/349917
Reviewed-by: LaMont Jones <lamontjones@google.com>
2022-10-28 17:26:48 +00:00
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
24 changed files with 593 additions and 203 deletions

View File

@ -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

View File

@ -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():

View File

@ -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

View File

@ -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')

33
main.py
View File

@ -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)
@ -652,17 +652,26 @@ 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()
with Trace('starting new command: %s', ', '.join([name] + argv), first_trace=True):
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()
except KeyboardInterrupt:
print('aborted by user', file=sys.stderr)
result = 1

View File

@ -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

View File

@ -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

View File

@ -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`.

View File

@ -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]

View File

@ -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
@ -3836,7 +3849,7 @@ 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)

View File

@ -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
View File

@ -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.

View File

@ -15,26 +15,128 @@
"""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 tempfile
import time
from contextlib import ContextDecorator
# 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 = 5 # in mb
_NEW_COMMAND_SEP = '+++++++++++++++NEW COMMAND+++++++++++++++++++'
def IsStraceToStderr():
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)
return trace_file
def _ClearOldTraces():
"""Clear traces from old 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 = tempfile.NamedTemporaryFile(mode='w', delete=False)
with open(_TRACE_FILE, 'r', errors='ignore') as fin:
trace_lines = fin.readlines()
for i , l in enumerate(trace_lines):
if 'END:' in l and _NEW_COMMAND_SEP in l:
temp.writelines(trace_lines[i+1:])
break
temp.close()
os.replace(temp.name, _TRACE_FILE)

View File

@ -20,6 +20,7 @@ import os
import shutil
import subprocess
import sys
import repo_trace
def find_pytest():

37
ssh.py
View File

@ -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'

View File

@ -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')

View File

@ -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.' %

View File

@ -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)

View File

@ -26,6 +26,7 @@ import socket
import sys
import tempfile
import time
from typing import NamedTuple, List, Set
import urllib.error
import urllib.parse
import urllib.request
@ -59,7 +60,7 @@ from error import RepoChangedException, GitError, ManifestParseError
import platform_utils
from project import SyncBuffer
from progress import Progress
from repo_trace import IsTrace, Trace
from repo_trace import Trace
import ssh
from wrapper import Wrapper
from manifest_xml import GitcManifest
@ -71,6 +72,58 @@ REPO_BACKUP_OBJECTS = 'REPO_BACKUP_OBJECTS'
_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):
COMMON = True
MULTI_MANIFEST_SUPPORT = True
@ -412,7 +465,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,
@ -426,6 +479,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:
@ -443,7 +497,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):
@ -454,6 +509,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()
@ -464,10 +520,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:
@ -525,7 +587,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):
@ -551,7 +613,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()
@ -561,7 +625,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()
@ -584,12 +648,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
@ -621,7 +687,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
@ -636,7 +702,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.
@ -658,33 +728,114 @@ 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)
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:
@ -700,36 +851,11 @@ later is required to fix a server side protocol bug.
jobs = opt.jobs
def _backup_cruft(bare_git):
# 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.
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')
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():
@ -739,7 +865,8 @@ later is required to fix a server side protocol bug.
bare_git.gc(*gc_args)
else:
bare_git.pack_refs(*pack_refs_args)
_backup_cruft(bare_git)
if backup_cruft:
self._backup_cruft(bare_git)
pm.end()
return
@ -763,7 +890,8 @@ later is required to fix a server side protocol bug.
err_event.set()
raise
finally:
_backup_cruft(bare_git)
if backup_cruft:
self._backup_cruft(bare_git)
pm.finish(bare_git._project.name)
sem.release()
@ -1200,6 +1328,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:
@ -1207,8 +1336,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

View File

@ -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)

View File

@ -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)

View File

@ -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')

View File

@ -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:

View File

@ -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))