Compare commits

...

15 Commits

Author SHA1 Message Date
b55769a5c9 superproject: print messages if the manifest has superproject tag.
1) If the manifest has superproject tag (git_master, etc), then
   display error/warning messages (as it is doing today)
2) If the manifest doesn't have superproject tag (nest, chromeos
   manifests), then don't display any error/warning messages about
   superrproject (behave as though user has specified
   --no-use-superproject).
3) Print error/warning messages if --use-superproject passed as
   argument to repo sync.
4) No change in behavior for the repo init command.

git_superproject.py:
+ Fixed typo in _WriteManifestFile method name
+ Superproject accepts print_message  as an argument and it defaults
  to True. All messages that are printed to stderr are controlled by
  this flag. If it is True, then messages get printed.
+ Added PrintMessages function which return true if either
  --use-superproject is specified on the command line or if the
  manifest has a superproject tag.

sync.py:
+ Displays the warning message if PrintMessgages are enabled and
  passes that as argument to superproject object.
+ Added 'hassuperprojecttag' trace2 log entry for analysis. We can
  find users/branches that are using superproject, but the manifest is
  missing the superproject tag.

Tested:
$ ./run_tests

+ Verified printing of messages with and without superproject tag, with
  with --use-superproject option.

+ aosp-master
  $ repo_dev init --use-superproject -u https://android.googlesource.com/platform/manifest
  $ repo_dev sync

+ A manifest without superproject tag.
  $ repo_dev init -m $(pwd)/manifest_7482982.xml
  $ repo_dev sync -n -c -j32 -m $(pwd)/manifest_7482982.xml

Bug: [google internal] b/196411099
Change-Id: I92166dcad15a4129fab82edcf869e7c8db3efd4b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/314982
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-08-13 20:07:40 +00:00
5637afcc60 superproject: prepend messages with - "repo superproject"
Changed _LogError method to _LogWarning.

Replaced 'repo error:' with "repo superproject warning:"(except IOError
message, which is still an "repo superproject error:" message)

Tested:
$ ./run_tests

Tested the errors and warnings by forcing the error/warning.
$ repo_dev sync -j 20 --use-superproject platform/packages/apps/Music
  ...
  repo superproject warning: please file a bug using go/repo-bug to report missing commit_ids for: []
  ...
  repo superproject error: cannot write manifest to : /sdc/android/src/aosp/.repo/exp-superproject/superproject_override.xml
  ...

Bug: [google internal] b/193711236

Change-Id: Ia0b6c830e04cf18dfc1a2ce325181a5b1160e054
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/314642
Tested-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Ian Kasprzak <iankaz@google.com>
Reviewed-by: Xin Li <delphij@google.com>
2021-08-12 16:30:26 +00:00
df8b1cba47 man: make output system independent
The current help output might change based on the number of CPU cores
available (since it reflects the dynamic --jobs logic).  This is good
for users running repo locally, but not good for shipping static man
pages.  Hook the help output to have it generate the same output all
the time.

Change-Id: I3098ceddc0ad914b0b8e3b25d660b5a264cb41ee
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/312882
Reviewed-by: Roger Shimizu <rosh@debian.org>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-07-31 11:39:35 +00:00
9122bfc3a8 sync: Remove '_' from the repo.syncstate.* keys when saved to config.
GitConfig doesn't save keys if the keys contain "_" characters. Some
of the options like mp_update, use_superproject have underscores.

This fixes issue with previous_sync_state missing some of the options.

Tested:
$ ./run_tests

$ repo_dev init --use-superproject -u https://android.googlesource.com/platform/manifest

Tested it by running the sync command multiple times and verifing
previous_sync_state and current_sync_state have the same keys.

$ repo_dev sync -j 20
  repo sync has finished successfully

  Verified config file has [syncstate ...] data saved.

Bug: [google internal] b/188573450
Change-Id: I16b52a164f9dd1633d7dad1d8cf6b151c629fcb1
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/313242
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-07-29 22:41:57 +00:00
7954de13b7 sync: Added logging of repo sync state and config options for analysis.
git_config.py:
+ Added SyncAnalysisState class, which saves the following data
  into the config object.
  ++ sys.argv, options, superproject's logging data.
  ++ repo.*, branch.* and remote.* parameters from config object.
  ++ current time as synctime.
  ++ Version number of the object.
+ All the keys for the above data are prepended with 'repo.syncstate.'
+ Added GetSyncAnalysisStateData and UpdateSyncAnalysisState methods
  to GitConfig object to save/get the above data.

git_trace2_event_log.py:
+ Added LogConfigEvents method with code from DefParamRepoEvents
  to log events.

sync.py:
+ superproject_logging_data is a dictionary that collects all the
  superproject data that is to be logged as trace2 event.
+ Sync at the end logs the previously saved syncstate.* parameters
  as previous_sync_state. Then it calls config's UpdateSyncAnalysisState
  to save and log all the current options, superproject logged data.

docs/internal-fs-layout.md:
+ Added doc string explaining [repo.syncstate ...] sections of
  .repo/manifests.git/config file.

test_git_config.py:
+ Added unit test for the new methods of GitConfig object.

Tested:
$ ./run_tests

$ repo_dev init --use-superproject -u https://android.googlesource.com/platform/manifest

Tested it by running the following command multiple times.
$ repo_dev sync -j 20
  repo sync has finished successfully

  Verified config file has [syncstate ...] data saved.

Bug: [google internal] b/188573450
Change-Id: I1f914ce50f3382111b72940ca56de7c41b53d460
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/313123
Tested-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Xin Li <delphij@google.com>
2021-07-29 19:20:57 +00:00
ae86a46022 superproject: Skip updating of superproject when -l is used with sync.
Skip updating the superproject when -l is present and use the existing
superproject, if available (this would make sync -l work as it's
intended to do), and fall back to sync without superproject when not
(this would catch the case when superproject is enabled by automatic
rollout).

Tested:
$ repo sync -j 20 -n
NOTICE: --use-superproject is in beta; report any issues to the address described in `repo version`
/usr/local/google/home/rtenneti/work/android/src/aosp/.repo/exp-superproject/925043f706ba64db713e9bf3b55987e2-superproject.git: Initial setup for superproject completed.
Fetching: 100% (1032/1032), done in 41.184s
...

$ repo_dev sync -j 20 -l
prebuilts/asuite/: discarding 1 commits
prebuilts/runtime/: discarding 1 commits
...
repo sync has finished successfully.

+ With superproject-override.xml and test it.

  $ ls -l .repo/exp-superproject/
  total 176
  drwxr-xr-x 7 rtenneti primarygroup   4096 Jul 27 14:10 925043f706ba64db713e9bf3b55987e2-superproject.git
  -rw-r--r-- 1 rtenneti primarygroup 172742 Jul 27 14:10 superproject_override.xml
  rtenneti@rtenneti:~/work/android/src/aosp$ repo_dev sync -j 20 -l
  ...
  repo sync has finished successfully.

+ Rename the file superproject-override.xml and test it.
  $ ls -l .repo/exp-superproject/
  total 176
  drwxr-xr-x 7 rtenneti primarygroup   4096 Jul 27 14:10 925043f706ba64db713e9bf3b55987e2-superproject.git
  -rw-r--r-- 1 rtenneti primarygroup 172742 Jul 27 14:10 temp.xml

  $ repo_dev sync -j 20 -l
  Checking out:  1% (12/1031) platform/external/rust/crates/fallible-streaming-iteexternal/linux-kselftest/: discarding 1 commits
  prebuilts/remoteexecution-client/: discarding 1 commits
  Checking out: 51% (536/1031) platform/prebuilts/gcc/darwin-x86/aarch64/....
  ....
  Checking out: 100% (1031/1031), done in 5.478s
  repo sync has finished successfully.

Bug: [google internal] b/184368268
Change-Id: I3aba5872e4f7c299977b92c2a39847ef28698c5a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/312962
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-07-28 16:12:53 +00:00
73c43b839f repo: add --show-toplevel akin to git
Simple API to make it easy to find the top of the repo client checkout
for users.  This mirrors the `git rev-parse --show-toplevel` API.

Change-Id: I0c3f98def089d0fc9ebcfa50aa3dc02091c1c273
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/312909
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-07-28 05:38:53 +00:00
56345c345b repo: refactor help output handling
Currently we have the behavior:
* `repo`: Equivalent to `repo help` -- only shows common subcommands
  (with short description), and then exits 0.
* `repo --help`: Shows repo's core options, lists all commands (no
  specific info), and then exits 0.

The first case is not behaving well:
* If you run `repo` without a specific subcommand, that's an error,
  so we should be exiting 1 instead.
* Showing only subcommands and no actual option summary makes it seem
  like repo itself doesn't take any options.  This confuses users.

Let's rework things a bit.  Now we have the behavior:
* `repo`: Shows repo's core options, lists all commands (no specific
  info), and then exits 1.
* `repo --help`: Shows repo's core options, shows common subcommands
  (with short description), and then exits 0.
* `repo --help-all`: Shows repo's core options, shows all subcommands
  (with short description), and then exits 0.

Basically we swap the behavior of `repo` and `repo --help`, and fix
the exit status when the subcommand is missing.

The addition of --help-all is mostly for the man pages.  We were
relying on `repo help --all` to generate the repo(1) man page, but
that too omitted the core repo options.  Now the man page includes
all the core repo options and provides a summary of all commands.

Change-Id: I1f99b99d5b8af2591f96a078d0647a3d76d6b0fc
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/312908
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-07-28 05:38:34 +00:00
a024bd33b8 repo: make --version always work
We don't really care what the subcommand is set to when --version
output is requested, so stop enforcing it.  This fixes some weird
behavior like `repo --version version` fails, but `repo --version
help` works.

The new logic skips subcommand validation, so `repo --version asdf`
will still display the version output.  This matches git behavior,
and makes a bit of sense when we consider that the user really wants
to see the tool version, and probably doesn't care about anything
else on the command line.

Change-Id: I87454d473c2c8869344b3888a7affaa2e03f5b0f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/312907
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-07-28 05:37:52 +00:00
968d646f04 repo: refactor internal --help/--version parsing
The _ParseArgs method parses the arguments and processes some of
the options, with the rest left to the _Run method.  Simplify the
_ParseArgs method to only parse arguments and have _Run handle all
actual processing.

This will make it easier to add more terminal options (ones that
exit immediately without a subcommand), and makes it easier to
understand the overall code flow.

Change-Id: I47f7274c3f2b59378fd479e403e70fb24b681536
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/312906
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-07-28 05:37:27 +00:00
cfa00d6e3d bash-completion: complete projects with repo forall
We need to add a little bit more logic here so we stop completing
projects once we see the -c argument.

Bug: https://crbug.com/gerrit/14797
Change-Id: Ic2ba4f3dd616ec49d8ad754ff62d0d6e0250dbe6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/312905
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-07-27 06:20:52 +00:00
5467185db0 list: add a --relative-to option
The current list output only shows project paths relative to the
root of the repo client checkout.  It can be helpful to also get
a listing of paths based on other paths (e.g. the current working
directory), so add an option to repo list to support that.  We'll
leverage this in bash completion to support completing projects by
their local paths and not just remote names.

Bug: https://crbug.com/gerrit/14797
Change-Id: Ia2b35d18c890217768448118b003874a1016efd4
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/312904
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-07-27 06:20:52 +00:00
b380322174 bash-completion: refactor unique subcommand processing
Let's keep the main processing loop free of subcommand implementations
by pulling the existing help & start commands into dedicated functions.
Having a single giant function is harder to track as we add more and
more logic in.

Bug: https://crbug.com/gerrit/14797
Change-Id: I2b62dc430c0e7574f09aa4838f4ef03fbe4bf7fb
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/312903
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-07-27 06:20:52 +00:00
13d6c94cfb bash-completion: fallback to default completion
If we can't provide any completions, then fallback to the standard
bash & readline ones.  This allows completion based on the user's
settings (e.g. local paths) to kick in.

Bug: https://crbug.com/gerrit/14797
Test: `repo rebase ./src/<tab>` works in a CrOS checkout
Change-Id: Iced343c4fc6fd3a932aab99875c1346687d187b6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/312902
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-07-27 06:20:52 +00:00
6ea0caea86 repo: properly handle remote annotations in manifest_xml
BUG=b:192664812
TEST=tests/

Change-Id: I1aa50260f4a00d3cebbd531141e1626825e70127
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/312643
Tested-by: Jack Neus <jackneus@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-07-23 18:03:11 +00:00
33 changed files with 520 additions and 137 deletions

View File

@ -24,6 +24,10 @@ from error import InvalidProjectGroupsError
import progress import progress
# Are we generating man-pages?
GENERATE_MANPAGES = os.environ.get('_REPO_GENERATE_MANPAGES_') == ' indeed! '
# Number of projects to submit to a single worker process at a time. # Number of projects to submit to a single worker process at a time.
# This number represents a tradeoff between the overhead of IPC and finer # This number represents a tradeoff between the overhead of IPC and finer
# grained opportunity for parallelism. This particular value was chosen by # grained opportunity for parallelism. This particular value was chosen by
@ -122,10 +126,14 @@ class Command(object):
help='only show errors') help='only show errors')
if self.PARALLEL_JOBS is not None: if self.PARALLEL_JOBS is not None:
default = 'based on number of CPU cores'
if not GENERATE_MANPAGES:
# Only include active cpu count if we aren't generating man pages.
default = f'%default; {default}'
p.add_option( p.add_option(
'-j', '--jobs', '-j', '--jobs',
type=int, default=self.PARALLEL_JOBS, type=int, default=self.PARALLEL_JOBS,
help='number of jobs to run in parallel (default: %s)' % self.PARALLEL_JOBS) help=f'number of jobs to run in parallel (default: {default})')
def _Options(self, p): def _Options(self, p):
"""Initialize the option parser with subcommand-specific options.""" """Initialize the option parser with subcommand-specific options."""

View File

@ -14,6 +14,9 @@
# Programmable bash completion. https://github.com/scop/bash-completion # Programmable bash completion. https://github.com/scop/bash-completion
# TODO: Handle interspersed options. We handle `repo h<tab>`, but not
# `repo --time h<tab>`.
# Complete the list of repo subcommands. # Complete the list of repo subcommands.
__complete_repo_list_commands() { __complete_repo_list_commands() {
local repo=${COMP_WORDS[0]} local repo=${COMP_WORDS[0]}
@ -37,6 +40,7 @@ __complete_repo_list_branches() {
__complete_repo_list_projects() { __complete_repo_list_projects() {
local repo=${COMP_WORDS[0]} local repo=${COMP_WORDS[0]}
"${repo}" list -n 2>/dev/null "${repo}" list -n 2>/dev/null
"${repo}" list -p --relative-to=. 2>/dev/null
} }
# Complete the repo <command> argument. # Complete the repo <command> argument.
@ -66,6 +70,48 @@ __complete_repo_command_projects() {
COMPREPLY=($(compgen -W "$(__complete_repo_list_projects)" -- "${current}")) COMPREPLY=($(compgen -W "$(__complete_repo_list_projects)" -- "${current}"))
} }
# Complete `repo help`.
__complete_repo_command_help() {
local current=$1
# CWORD=1 is "start".
# CWORD=2 is the <subcommand> which we complete here.
if [[ ${COMP_CWORD} -eq 2 ]]; then
COMPREPLY=(
$(compgen -W "$(__complete_repo_list_commands)" -- "${current}")
)
fi
}
# Complete `repo forall`.
__complete_repo_command_forall() {
local current=$1
# CWORD=1 is "forall".
# CWORD=2+ are <projects> *until* we hit the -c option.
local i
for (( i = 0; i < COMP_CWORD; ++i )); do
if [[ "${COMP_WORDS[i]}" == "-c" ]]; then
return 0
fi
done
COMPREPLY=(
$(compgen -W "$(__complete_repo_list_projects)" -- "${current}")
)
}
# Complete `repo start`.
__complete_repo_command_start() {
local current=$1
# CWORD=1 is "start".
# CWORD=2 is the <branch> which we don't complete.
# CWORD=3+ are <projects> which we complete here.
if [[ ${COMP_CWORD} -gt 2 ]]; then
COMPREPLY=(
$(compgen -W "$(__complete_repo_list_projects)" -- "${current}")
)
fi
}
# Complete the repo subcommand arguments. # Complete the repo subcommand arguments.
__complete_repo_arg() { __complete_repo_arg() {
if [[ ${COMP_CWORD} -le 1 ]]; then if [[ ${COMP_CWORD} -le 1 ]]; then
@ -86,21 +132,8 @@ __complete_repo_arg() {
return 0 return 0
;; ;;
help) help|start|forall)
if [[ ${COMP_CWORD} -eq 2 ]]; then __complete_repo_command_${command} "${current}"
COMPREPLY=(
$(compgen -W "$(__complete_repo_list_commands)" -- "${current}")
)
fi
return 0
;;
start)
if [[ ${COMP_CWORD} -gt 2 ]]; then
COMPREPLY=(
$(compgen -W "$(__complete_repo_list_projects)" -- "${current}")
)
fi
return 0 return 0
;; ;;
@ -118,4 +151,6 @@ __complete_repo() {
return 0 return 0
} }
complete -F __complete_repo repo # Fallback to the default complete methods if we aren't able to provide anything
# useful. This will allow e.g. local paths to be used when it makes sense.
complete -F __complete_repo -o bashdefault -o default repo

View File

@ -146,7 +146,12 @@ Instead, you should use standard Git workflows like [git worktree] or
The `.repo/manifests.git/config` file is used to track settings for the entire The `.repo/manifests.git/config` file is used to track settings for the entire
repo client checkout. repo client checkout.
Most settings use the `[repo]` section to avoid conflicts with git. Most settings use the `[repo]` section to avoid conflicts with git.
Everything under `[repo.syncstate.*]` is used to keep track of sync details for logging
purposes.
User controlled settings are initialized when running `repo init`. User controlled settings are initialized when running `repo init`.
| Setting | `repo init` Option | Use/Meaning | | Setting | `repo init` Option | Use/Meaning |

View File

@ -36,7 +36,7 @@ following DTD:
<!ELEMENT notice (#PCDATA)> <!ELEMENT notice (#PCDATA)>
<!ELEMENT remote EMPTY> <!ELEMENT remote (annotation*)>
<!ATTLIST remote name ID #REQUIRED> <!ATTLIST remote name ID #REQUIRED>
<!ATTLIST remote alias CDATA #IMPLIED> <!ATTLIST remote alias CDATA #IMPLIED>
<!ATTLIST remote fetch CDATA #REQUIRED> <!ATTLIST remote fetch CDATA #REQUIRED>
@ -348,12 +348,12 @@ project. Same syntax as the corresponding element of `project`.
### Element annotation ### Element annotation
Zero or more annotation elements may be specified as children of a Zero or more annotation elements may be specified as children of a
project element. Each element describes a name-value pair that will be project or remote element. Each element describes a name-value pair.
exported into each project's environment during a 'forall' command, For projects, this name-value pair will be exported into each project's
prefixed with REPO__. In addition, there is an optional attribute environment during a 'forall' command, prefixed with `REPO__`. In addition,
"keep" which accepts the case insensitive values "true" (default) or there is an optional attribute "keep" which accepts the case insensitive values
"false". This attribute determines whether or not the annotation will "true" (default) or "false". This attribute determines whether or not the
be kept when exported with the manifest subcommand. annotation will be kept when exported with the manifest subcommand.
### Element copyfile ### Element copyfile

View File

@ -13,6 +13,7 @@
# limitations under the License. # limitations under the License.
import contextlib import contextlib
import datetime
import errno import errno
from http.client import HTTPException from http.client import HTTPException
import json import json
@ -30,6 +31,10 @@ from repo_trace import Trace
from git_command import GitCommand from git_command import GitCommand
from git_refs import R_CHANGES, R_HEADS, R_TAGS from git_refs import R_CHANGES, R_HEADS, R_TAGS
# Prefix that is prepended to all the keys of SyncAnalysisState's data
# that is saved in the config.
SYNC_STATE_PREFIX = 'repo.syncstate.'
ID_RE = re.compile(r'^[0-9a-f]{40}$') ID_RE = re.compile(r'^[0-9a-f]{40}$')
REVIEW_CACHE = dict() REVIEW_CACHE = dict()
@ -262,6 +267,22 @@ class GitConfig(object):
self._branches[b.name] = b self._branches[b.name] = b
return b return b
def GetSyncAnalysisStateData(self):
"""Returns data to be logged for the analysis of sync performance."""
return {k: v for k, v in self.DumpConfigDict().items() if k.startswith(SYNC_STATE_PREFIX)}
def UpdateSyncAnalysisState(self, options, superproject_logging_data):
"""Update Config's SYNC_STATE_PREFIX* data with the latest sync data.
Args:
options: Options passed to sync returned from optparse. See _Options().
superproject_logging_data: A dictionary of superproject data that is to be logged.
Returns:
SyncAnalysisState object.
"""
return SyncAnalysisState(self, options, superproject_logging_data)
def GetSubSections(self, section): def GetSubSections(self, section):
"""List all subsection names matching $section.*.* """List all subsection names matching $section.*.*
""" """
@ -717,3 +738,70 @@ class Branch(object):
def _Get(self, key, all_keys=False): def _Get(self, key, all_keys=False):
key = 'branch.%s.%s' % (self.name, key) key = 'branch.%s.%s' % (self.name, key)
return self._config.GetString(key, all_keys=all_keys) return self._config.GetString(key, all_keys=all_keys)
class SyncAnalysisState:
"""Configuration options related to logging of sync state for analysis.
This object is versioned.
"""
def __init__(self, config, options, superproject_logging_data):
"""Initializes SyncAnalysisState.
Saves the following data into the |config| object.
- sys.argv, options, superproject's logging data.
- repo.*, branch.* and remote.* parameters from config object.
- Current time as synctime.
- Version number of the object.
All the keys saved by this object are prepended with SYNC_STATE_PREFIX.
Args:
config: GitConfig object to store all options.
options: Options passed to sync returned from optparse. See _Options().
superproject_logging_data: A dictionary of superproject data that is to be logged.
"""
self._config = config
now = datetime.datetime.utcnow()
self._Set('main.synctime', now.isoformat() + 'Z')
self._Set('main.version', '1')
self._Set('sys.argv', sys.argv)
for key, value in superproject_logging_data.items():
self._Set(f'superproject.{key}', value)
for key, value in options.__dict__.items():
self._Set(f'options.{key}', value)
config_items = config.DumpConfigDict().items()
EXTRACT_NAMESPACES = {'repo', 'branch', 'remote'}
self._SetDictionary({k: v for k, v in config_items
if not k.startswith(SYNC_STATE_PREFIX) and
k.split('.', 1)[0] in EXTRACT_NAMESPACES})
def _SetDictionary(self, data):
"""Save all key/value pairs of |data| dictionary.
Args:
data: A dictionary whose key/value are to be saved.
"""
for key, value in data.items():
self._Set(key, value)
def _Set(self, key, value):
"""Set the |value| for a |key| in the |_config| member.
|key| is prepended with the value of SYNC_STATE_PREFIX constant.
Args:
key: Name of the key.
value: |value| could be of any type. If it is 'bool', it will be saved
as a Boolean and for all other types, it will be saved as a String.
"""
if value is None:
return
sync_key = f'{SYNC_STATE_PREFIX}{key}'
sync_key = sync_key.replace('_', '')
if isinstance(value, str):
self._config.SetString(sync_key, value)
elif isinstance(value, bool):
self._config.SetBoolean(sync_key, value)
else:
self._config.SetString(sync_key, str(value))

View File

@ -59,7 +59,7 @@ class CommitIdsResult(NamedTuple):
class UpdateProjectsResult(NamedTuple): class UpdateProjectsResult(NamedTuple):
"""Return the overriding manifest file and whether caller should exit.""" """Return the overriding manifest file and whether caller should exit."""
# Path name of the overriding manfiest file if successful, otherwise None. # Path name of the overriding manifest file if successful, otherwise None.
manifest_path: str manifest_path: str
# Whether the caller should exit. # Whether the caller should exit.
fatal: bool fatal: bool
@ -73,7 +73,7 @@ class Superproject(object):
is a dictionary with project/commit id entries. is a dictionary with project/commit id entries.
""" """
def __init__(self, manifest, repodir, git_event_log, def __init__(self, manifest, repodir, git_event_log,
superproject_dir='exp-superproject', quiet=False): superproject_dir='exp-superproject', quiet=False, print_messages=False):
"""Initializes superproject. """Initializes superproject.
Args: Args:
@ -83,11 +83,13 @@ class Superproject(object):
git_event_log: A git trace2 event log to log events. git_event_log: A git trace2 event log to log events.
superproject_dir: Relative path under |repodir| to checkout superproject. superproject_dir: Relative path under |repodir| to checkout superproject.
quiet: If True then only print the progress messages. quiet: If True then only print the progress messages.
print_messages: if True then print error/warning messages.
""" """
self._project_commit_ids = None self._project_commit_ids = None
self._manifest = manifest self._manifest = manifest
self._git_event_log = git_event_log self._git_event_log = git_event_log
self._quiet = quiet self._quiet = quiet
self._print_messages = print_messages
self._branch = self._GetBranch() self._branch = self._GetBranch()
self._repodir = os.path.abspath(repodir) self._repodir = os.path.abspath(repodir)
self._superproject_dir = superproject_dir self._superproject_dir = superproject_dir
@ -106,6 +108,11 @@ class Superproject(object):
"""Returns a dictionary of projects and their commit ids.""" """Returns a dictionary of projects and their commit ids."""
return self._project_commit_ids return self._project_commit_ids
@property
def manifest_path(self):
"""Returns the manifest path if the path exists or None."""
return self._manifest_path if os.path.exists(self._manifest_path) else None
def _GetBranch(self): def _GetBranch(self):
"""Returns the branch name for getting the approved manifest.""" """Returns the branch name for getting the approved manifest."""
p = self._manifest.manifestProject p = self._manifest.manifestProject
@ -117,11 +124,20 @@ class Superproject(object):
branch = branch[len(R_HEADS):] branch = branch[len(R_HEADS):]
return branch return branch
def _LogError(self, message): def _LogMessage(self, message):
"""Logs message to stderr and _git_event_log.""" """Logs message to stderr and _git_event_log."""
print(message, file=sys.stderr) if self._print_messages:
print(message, file=sys.stderr)
self._git_event_log.ErrorEvent(message, '') self._git_event_log.ErrorEvent(message, '')
def _LogError(self, message):
"""Logs error message to stderr and _git_event_log."""
self._LogMessage(f'repo superproject error: {message}')
def _LogWarning(self, message):
"""Logs warning message to stderr and _git_event_log."""
self._LogMessage(f'repo superproject warning: {message}')
def _Init(self): def _Init(self):
"""Sets up a local Git repository to get a copy of a superproject. """Sets up a local Git repository to get a copy of a superproject.
@ -141,8 +157,8 @@ class Superproject(object):
capture_stderr=True) capture_stderr=True)
retval = p.Wait() retval = p.Wait()
if retval: if retval:
self._LogError(f'repo: error: git init call failed, command: git {cmd}, ' self._LogWarning(f'git init call failed, command: git {cmd}, '
f'return code: {retval}, stderr: {p.stderr}') f'return code: {retval}, stderr: {p.stderr}')
return False return False
return True return True
@ -156,10 +172,10 @@ class Superproject(object):
True if fetch is successful, or False. True if fetch is successful, or False.
""" """
if not os.path.exists(self._work_git): if not os.path.exists(self._work_git):
self._LogError(f'git fetch missing directory: {self._work_git}') self._LogWarning(f'git fetch missing directory: {self._work_git}')
return False return False
if not git_require((2, 28, 0)): if not git_require((2, 28, 0)):
print('superproject requires a git version 2.28 or later', file=sys.stderr) self._LogWarning('superproject requires a git version 2.28 or later')
return False return False
cmd = ['fetch', url, '--depth', '1', '--force', '--no-tags', '--filter', 'blob:none'] cmd = ['fetch', url, '--depth', '1', '--force', '--no-tags', '--filter', 'blob:none']
if self._branch: if self._branch:
@ -171,8 +187,8 @@ class Superproject(object):
capture_stderr=True) capture_stderr=True)
retval = p.Wait() retval = p.Wait()
if retval: if retval:
self._LogError(f'repo: error: git fetch call failed, command: git {cmd}, ' self._LogWarning(f'git fetch call failed, command: git {cmd}, '
f'return code: {retval}, stderr: {p.stderr}') f'return code: {retval}, stderr: {p.stderr}')
return False return False
return True return True
@ -185,7 +201,7 @@ class Superproject(object):
data: data returned from 'git ls-tree ...' instead of None. data: data returned from 'git ls-tree ...' instead of None.
""" """
if not os.path.exists(self._work_git): if not os.path.exists(self._work_git):
self._LogError(f'git ls-tree missing directory: {self._work_git}') self._LogWarning(f'git ls-tree missing directory: {self._work_git}')
return None return None
data = None data = None
branch = 'HEAD' if not self._branch else self._branch branch = 'HEAD' if not self._branch else self._branch
@ -200,8 +216,8 @@ class Superproject(object):
if retval == 0: if retval == 0:
data = p.stdout data = p.stdout
else: else:
self._LogError(f'repo: error: git ls-tree call failed, command: git {cmd}, ' self._LogWarning(f'git ls-tree call failed, command: git {cmd}, '
f'return code: {retval}, stderr: {p.stderr}') f'return code: {retval}, stderr: {p.stderr}')
return data return data
def Sync(self): def Sync(self):
@ -210,19 +226,18 @@ class Superproject(object):
Returns: Returns:
SyncResult SyncResult
""" """
print('NOTICE: --use-superproject is in beta; report any issues to the '
'address described in `repo version`', file=sys.stderr)
if not self._manifest.superproject: if not self._manifest.superproject:
self._LogError(f'repo error: superproject tag is not defined in manifest: ' self._LogWarning(f'superproject tag is not defined in manifest: '
f'{self._manifest.manifestFile}') f'{self._manifest.manifestFile}')
return SyncResult(False, False) return SyncResult(False, False)
print('NOTICE: --use-superproject is in beta; report any issues to the '
'address described in `repo version`', file=sys.stderr)
should_exit = True should_exit = True
url = self._manifest.superproject['remote'].url url = self._manifest.superproject['remote'].url
if not url: if not url:
self._LogError(f'repo error: superproject URL is not defined in manifest: ' self._LogWarning(f'superproject URL is not defined in manifest: '
f'{self._manifest.manifestFile}') f'{self._manifest.manifestFile}')
return SyncResult(False, should_exit) return SyncResult(False, should_exit)
if not self._Init(): if not self._Init():
@ -245,8 +260,8 @@ class Superproject(object):
data = self._LsTree() data = self._LsTree()
if not data: if not data:
print('warning: git ls-tree failed to return data for superproject', self._LogWarning(f'warning: git ls-tree failed to return data for manifest: '
file=sys.stderr) f'{self._manifest.manifestFile}')
return CommitIdsResult(None, True) return CommitIdsResult(None, True)
# Parse lines like the following to select lines starting with '160000' and # Parse lines like the following to select lines starting with '160000' and
@ -265,14 +280,14 @@ class Superproject(object):
self._project_commit_ids = commit_ids self._project_commit_ids = commit_ids
return CommitIdsResult(commit_ids, False) return CommitIdsResult(commit_ids, False)
def _WriteManfiestFile(self): def _WriteManifestFile(self):
"""Writes manifest to a file. """Writes manifest to a file.
Returns: Returns:
manifest_path: Path name of the file into which manifest is written instead of None. manifest_path: Path name of the file into which manifest is written instead of None.
""" """
if not os.path.exists(self._superproject_path): if not os.path.exists(self._superproject_path):
self._LogError(f'error: missing superproject directory: {self._superproject_path}') self._LogWarning(f'missing superproject directory: {self._superproject_path}')
return None return None
manifest_str = self._manifest.ToXml(groups=self._manifest.GetGroupsStr()).toxml() manifest_str = self._manifest.ToXml(groups=self._manifest.GetGroupsStr()).toxml()
manifest_path = self._manifest_path manifest_path = self._manifest_path
@ -280,7 +295,7 @@ class Superproject(object):
with open(manifest_path, 'w', encoding='utf-8') as fp: with open(manifest_path, 'w', encoding='utf-8') as fp:
fp.write(manifest_str) fp.write(manifest_str)
except IOError as e: except IOError as e:
self._LogError(f'error: cannot write manifest to : {manifest_path} {e}') self._LogError(f'cannot write manifest to : {manifest_path} {e}')
return None return None
return manifest_path return manifest_path
@ -316,7 +331,6 @@ class Superproject(object):
commit_ids_result = self._GetAllProjectsCommitIds() commit_ids_result = self._GetAllProjectsCommitIds()
commit_ids = commit_ids_result.commit_ids commit_ids = commit_ids_result.commit_ids
if not commit_ids: if not commit_ids:
print('warning: Cannot get project commit ids from manifest', file=sys.stderr)
return UpdateProjectsResult(None, commit_ids_result.fatal) return UpdateProjectsResult(None, commit_ids_result.fatal)
projects_missing_commit_ids = [] projects_missing_commit_ids = []
@ -331,15 +345,15 @@ class Superproject(object):
# If superproject doesn't have a commit id for a project, then report an # If superproject doesn't have a commit id for a project, then report an
# error event and continue as if do not use superproject is specified. # error event and continue as if do not use superproject is specified.
if projects_missing_commit_ids: if projects_missing_commit_ids:
self._LogError(f'error: please file a bug using {self._manifest.contactinfo.bugurl} ' self._LogWarning(f'please file a bug using {self._manifest.contactinfo.bugurl} '
f'to report missing commit_ids for: {projects_missing_commit_ids}') f'to report missing commit_ids for: {projects_missing_commit_ids}')
return UpdateProjectsResult(None, False) return UpdateProjectsResult(None, False)
for project in projects: for project in projects:
if not self._SkipUpdatingProjectRevisionId(project): if not self._SkipUpdatingProjectRevisionId(project):
project.SetRevisionId(commit_ids.get(project.relpath)) project.SetRevisionId(commit_ids.get(project.relpath))
manifest_path = self._WriteManfiestFile() manifest_path = self._WriteManifestFile()
return UpdateProjectsResult(manifest_path, False) return UpdateProjectsResult(manifest_path, False)
@ -347,7 +361,6 @@ class Superproject(object):
def _UseSuperprojectFromConfiguration(): def _UseSuperprojectFromConfiguration():
"""Returns the user choice of whether to use superproject.""" """Returns the user choice of whether to use superproject."""
user_cfg = RepoConfig.ForUser() user_cfg = RepoConfig.ForUser()
system_cfg = RepoConfig.ForSystem()
time_now = int(time.time()) time_now = int(time.time())
user_value = user_cfg.GetBoolean('repo.superprojectChoice') user_value = user_cfg.GetBoolean('repo.superprojectChoice')
@ -362,6 +375,7 @@ def _UseSuperprojectFromConfiguration():
return user_value return user_value
# We don't have an unexpired choice, ask for one. # We don't have an unexpired choice, ask for one.
system_cfg = RepoConfig.ForSystem()
system_value = system_cfg.GetBoolean('repo.superprojectChoice') system_value = system_cfg.GetBoolean('repo.superprojectChoice')
if system_value: if system_value:
# The system configuration is proposing that we should enable the # The system configuration is proposing that we should enable the
@ -408,6 +422,11 @@ def _UseSuperprojectFromConfiguration():
return False return False
def PrintMessages(opt, manifest):
"""Returns a boolean if error/warning messages are to be printed."""
return opt.use_superproject is not None or manifest.superproject
def UseSuperproject(opt, manifest): def UseSuperproject(opt, manifest):
"""Returns a boolean if use-superproject option is enabled.""" """Returns a boolean if use-superproject option is enabled."""

View File

@ -144,6 +144,19 @@ class EventLog(object):
command_event['subcommands'] = subcommands command_event['subcommands'] = subcommands
self._log.append(command_event) self._log.append(command_event)
def LogConfigEvents(self, config, event_dict_name):
"""Append a |event_dict_name| event for each config key in |config|.
Args:
config: Configuration dictionary.
event_dict_name: Name of the event dictionary for items to be logged under.
"""
for param, value in config.items():
event = self._CreateEventDict(event_dict_name)
event['param'] = param
event['value'] = value
self._log.append(event)
def DefParamRepoEvents(self, config): def DefParamRepoEvents(self, config):
"""Append a 'def_param' event for each repo.* config key to the current log. """Append a 'def_param' event for each repo.* config key to the current log.
@ -152,12 +165,7 @@ class EventLog(object):
""" """
# Only output the repo.* config parameters. # Only output the repo.* config parameters.
repo_config = {k: v for k, v in config.items() if k.startswith('repo.')} repo_config = {k: v for k, v in config.items() if k.startswith('repo.')}
self.LogConfigEvents(repo_config, 'def_param')
for param, value in repo_config.items():
def_param_event = self._CreateEventDict('def_param')
def_param_event['param'] = param
def_param_event['value'] = value
self._log.append(def_param_event)
def ErrorEvent(self, msg, fmt): def ErrorEvent(self, msg, fmt):
"""Append a 'error' event to the current log.""" """Append a 'error' event to the current log."""

73
main.py
View File

@ -95,6 +95,8 @@ global_options = optparse.OptionParser(
add_help_option=False) add_help_option=False)
global_options.add_option('-h', '--help', action='store_true', global_options.add_option('-h', '--help', action='store_true',
help='show this help message and exit') help='show this help message and exit')
global_options.add_option('--help-all', action='store_true',
help='show this help message with all subcommands and exit')
global_options.add_option('-p', '--paginate', global_options.add_option('-p', '--paginate',
dest='pager', action='store_true', dest='pager', action='store_true',
help='display command output in the pager') help='display command output in the pager')
@ -116,6 +118,10 @@ global_options.add_option('--time',
global_options.add_option('--version', global_options.add_option('--version',
dest='show_version', action='store_true', dest='show_version', action='store_true',
help='display this version of repo') help='display this version of repo')
global_options.add_option('--show-toplevel',
action='store_true',
help='display the path of the top-level directory of '
'the repo client checkout')
global_options.add_option('--event-log', global_options.add_option('--event-log',
dest='event_log', action='store', dest='event_log', action='store',
help='filename of event log to append timeline to') help='filename of event log to append timeline to')
@ -128,34 +134,40 @@ class _Repo(object):
self.repodir = repodir self.repodir = repodir
self.commands = all_commands self.commands = all_commands
def _PrintHelp(self, short: bool = False, all_commands: bool = False):
"""Show --help screen."""
global_options.print_help()
print()
if short:
commands = ' '.join(sorted(self.commands))
wrapped_commands = textwrap.wrap(commands, width=77)
print('Available commands:\n %s' % ('\n '.join(wrapped_commands),))
print('\nRun `repo help <command>` for command-specific details.')
print('Bug reports:', Wrapper().BUG_URL)
else:
cmd = self.commands['help']()
if all_commands:
cmd.PrintAllCommandsBody()
else:
cmd.PrintCommonCommandsBody()
def _ParseArgs(self, argv): def _ParseArgs(self, argv):
"""Parse the main `repo` command line options.""" """Parse the main `repo` command line options."""
name = None for i, arg in enumerate(argv):
glob = [] if not arg.startswith('-'):
name = arg
for i in range(len(argv)): glob = argv[:i]
if not argv[i].startswith('-'):
name = argv[i]
if i > 0:
glob = argv[:i]
argv = argv[i + 1:] argv = argv[i + 1:]
break break
if not name: else:
name = None
glob = argv glob = argv
name = 'help'
argv = [] argv = []
gopts, _gargs = global_options.parse_args(glob) gopts, _gargs = global_options.parse_args(glob)
name, alias_args = self._ExpandAlias(name) if name:
argv = alias_args + argv name, alias_args = self._ExpandAlias(name)
argv = alias_args + argv
if gopts.help:
global_options.print_help()
commands = ' '.join(sorted(self.commands))
wrapped_commands = textwrap.wrap(commands, width=77)
print('\nAvailable commands:\n %s' % ('\n '.join(wrapped_commands),))
print('\nRun `repo help <command>` for command-specific details.')
global_options.exit()
return (name, gopts, argv) return (name, gopts, argv)
@ -186,12 +198,21 @@ class _Repo(object):
if gopts.trace: if gopts.trace:
SetTrace() SetTrace()
if gopts.show_version:
if name == 'help': # Handle options that terminate quickly first.
name = 'version' if gopts.help or gopts.help_all:
else: self._PrintHelp(short=False, all_commands=gopts.help_all)
print('fatal: invalid usage of --version', file=sys.stderr) return 0
return 1 elif gopts.show_version:
# Always allow global --version regardless of subcommand validity.
name = 'version'
elif gopts.show_toplevel:
print(os.path.dirname(self.repodir))
return 0
elif not name:
# No subcommand specified, so show the help/subcommand.
self._PrintHelp(short=True)
return 1
SetDefaultColoring(gopts.color) SetDefaultColoring(gopts.color)

View File

@ -20,7 +20,8 @@ It is equivalent to "git branch \fB\-D\fR <branchname>".
show this help message and exit show this help message and exit
.TP .TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: 4) number of jobs to run in parallel (default: based on
number of CPU cores)
.TP .TP
\fB\-\-all\fR \fB\-\-all\fR
delete all branches in all projects delete all branches in all projects

View File

@ -46,7 +46,8 @@ is shown, then the branch appears in all projects.
show this help message and exit show this help message and exit
.TP .TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: 4) number of jobs to run in parallel (default: based on
number of CPU cores)
.SS Logging options: .SS Logging options:
.TP .TP
\fB\-v\fR, \fB\-\-verbose\fR \fB\-v\fR, \fB\-\-verbose\fR

View File

@ -15,7 +15,8 @@ Checkout a branch for development
show this help message and exit show this help message and exit
.TP .TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: 4) number of jobs to run in parallel (default: based on
number of CPU cores)
.SS Logging options: .SS Logging options:
.TP .TP
\fB\-v\fR, \fB\-\-verbose\fR \fB\-v\fR, \fB\-\-verbose\fR

View File

@ -19,7 +19,8 @@ to the Unix 'patch' command.
show this help message and exit show this help message and exit
.TP .TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: 4) number of jobs to run in parallel (default: based on
number of CPU cores)
.TP .TP
\fB\-u\fR, \fB\-\-absolute\fR \fB\-u\fR, \fB\-\-absolute\fR
paths are relative to the repository root paths are relative to the repository root

View File

@ -17,7 +17,8 @@ repo forall \fB\-r\fR str1 [str2] ... \fB\-c\fR <command> [<arg>...]
show this help message and exit show this help message and exit
.TP .TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: 4) number of jobs to run in parallel (default: based on
number of CPU cores)
.TP .TP
\fB\-r\fR, \fB\-\-regex\fR \fB\-r\fR, \fB\-\-regex\fR
execute the command only on projects matching regex or execute the command only on projects matching regex or

View File

@ -15,7 +15,8 @@ Print lines matching a pattern
show this help message and exit show this help message and exit
.TP .TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: 4) number of jobs to run in parallel (default: based on
number of CPU cores)
.SS Logging options: .SS Logging options:
.TP .TP
\fB\-\-verbose\fR \fB\-\-verbose\fR

View File

@ -27,15 +27,19 @@ project is in
\fB\-a\fR, \fB\-\-all\fR \fB\-a\fR, \fB\-\-all\fR
show projects regardless of checkout state show projects regardless of checkout state
.TP .TP
\fB\-f\fR, \fB\-\-fullpath\fR
display the full work tree path instead of the
relative path
.TP
\fB\-n\fR, \fB\-\-name\-only\fR \fB\-n\fR, \fB\-\-name\-only\fR
display only the name of the repository display only the name of the repository
.TP .TP
\fB\-p\fR, \fB\-\-path\-only\fR \fB\-p\fR, \fB\-\-path\-only\fR
display only the path of the repository display only the path of the repository
.TP
\fB\-f\fR, \fB\-\-fullpath\fR
display the full work tree path instead of the
relative path
.TP
\fB\-\-relative\-to\fR=\fI\,PATH\/\fR
display paths relative to this one (default: top of
repo client checkout)
.SS Logging options: .SS Logging options:
.TP .TP
\fB\-v\fR, \fB\-\-verbose\fR \fB\-v\fR, \fB\-\-verbose\fR

View File

@ -36,6 +36,9 @@ output manifest in JSON format (experimental)
\fB\-\-pretty\fR \fB\-\-pretty\fR
format output for humans to read format output for humans to read
.TP .TP
\fB\-\-no\-local\-manifests\fR
ignore local manifests
.TP
\fB\-o\fR \-|NAME.xml, \fB\-\-output\-file\fR=\fI\,\-\/\fR|NAME.xml \fB\-o\fR \-|NAME.xml, \fB\-\-output\-file\fR=\fI\,\-\/\fR|NAME.xml
file to save the manifest to file to save the manifest to
.SS Logging options: .SS Logging options:
@ -95,7 +98,7 @@ include*)>
.IP .IP
<!ELEMENT notice (#PCDATA)> <!ELEMENT notice (#PCDATA)>
.IP .IP
<!ELEMENT remote EMPTY> <!ELEMENT remote (annotation*)>
<!ATTLIST remote name ID #REQUIRED> <!ATTLIST remote name ID #REQUIRED>
<!ATTLIST remote alias CDATA #IMPLIED> <!ATTLIST remote alias CDATA #IMPLIED>
<!ATTLIST remote fetch CDATA #REQUIRED> <!ATTLIST remote fetch CDATA #REQUIRED>
@ -393,13 +396,13 @@ Same syntax as the corresponding element of `project`.
.PP .PP
Element annotation Element annotation
.PP .PP
Zero or more annotation elements may be specified as children of a project Zero or more annotation elements may be specified as children of a project or
element. Each element describes a name\-value pair that will be exported into remote element. Each element describes a name\-value pair. For projects, this
each project's environment during a 'forall' command, prefixed with REPO__. In name\-value pair will be exported into each project's environment during a
addition, there is an optional attribute "keep" which accepts the case \&'forall' command, prefixed with `REPO__`. In addition, there is an optional
insensitive values "true" (default) or "false". This attribute determines attribute "keep" which accepts the case insensitive values "true" (default) or
whether or not the annotation will be kept when exported with the manifest "false". This attribute determines whether or not the annotation will be kept
subcommand. when exported with the manifest subcommand.
.PP .PP
Element copyfile Element copyfile
.PP .PP

View File

@ -15,7 +15,8 @@ Prune (delete) already merged topics
show this help message and exit show this help message and exit
.TP .TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: 4) number of jobs to run in parallel (default: based on
number of CPU cores)
.SS Logging options: .SS Logging options:
.TP .TP
\fB\-v\fR, \fB\-\-verbose\fR \fB\-v\fR, \fB\-\-verbose\fR

View File

@ -15,7 +15,8 @@ Update working tree to the latest known good revision
show this help message and exit show this help message and exit
.TP .TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: 1) number of jobs to run in parallel (default: based on
number of CPU cores)
.TP .TP
\fB\-\-jobs\-network\fR=\fI\,JOBS\/\fR \fB\-\-jobs\-network\fR=\fI\,JOBS\/\fR
number of network jobs to run in parallel (defaults to number of network jobs to run in parallel (defaults to

View File

@ -15,7 +15,8 @@ Start a new branch for development
show this help message and exit show this help message and exit
.TP .TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: 4) number of jobs to run in parallel (default: based on
number of CPU cores)
.TP .TP
\fB\-\-all\fR \fB\-\-all\fR
begin branch in all projects begin branch in all projects

View File

@ -15,7 +15,8 @@ Show the working tree status
show this help message and exit show this help message and exit
.TP .TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: 4) number of jobs to run in parallel (default: based on
number of CPU cores)
.TP .TP
\fB\-o\fR, \fB\-\-orphans\fR \fB\-o\fR, \fB\-\-orphans\fR
include objects in working directory outside of repo include objects in working directory outside of repo

View File

@ -15,7 +15,8 @@ Update working tree to the latest revision
show this help message and exit show this help message and exit
.TP .TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: 1) number of jobs to run in parallel (default: based on
number of CPU cores)
.TP .TP
\fB\-\-jobs\-network\fR=\fI\,JOBS\/\fR \fB\-\-jobs\-network\fR=\fI\,JOBS\/\fR
number of network jobs to run in parallel (defaults to number of network jobs to run in parallel (defaults to

View File

@ -15,7 +15,8 @@ Upload changes for code review
show this help message and exit show this help message and exit
.TP .TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: 4) number of jobs to run in parallel (default: based on
number of CPU cores)
.TP .TP
\fB\-t\fR \fB\-t\fR
send local branch name to Gerrit Code Review send local branch name to Gerrit Code Review

View File

@ -2,9 +2,48 @@
.TH REPO "1" "July 2021" "repo" "Repo Manual" .TH REPO "1" "July 2021" "repo" "Repo Manual"
.SH NAME .SH NAME
repo \- repository management tool built on top of git repo \- repository management tool built on top of git
.SH DESCRIPTION .SH SYNOPSIS
usage: repo COMMAND [ARGS] .B repo
The complete list of recognized repo commands are: [\fI\,-p|--paginate|--no-pager\/\fR] \fI\,COMMAND \/\fR[\fI\,ARGS\/\fR]
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-\-help\-all\fR
show this help message with all subcommands and exit
.TP
\fB\-p\fR, \fB\-\-paginate\fR
display command output in the pager
.TP
\fB\-\-no\-pager\fR
disable the pager
.TP
\fB\-\-color\fR=\fI\,COLOR\/\fR
control color usage: auto, always, never
.TP
\fB\-\-trace\fR
trace git command execution (REPO_TRACE=1)
.TP
\fB\-\-trace\-python\fR
trace python command execution
.TP
\fB\-\-time\fR
time repo command execution
.TP
\fB\-\-version\fR
display this version of repo
.TP
\fB\-\-show\-toplevel\fR
display the path of the top\-level directory of the
repo client checkout
.TP
\fB\-\-event\-log\fR=\fI\,EVENT_LOG\/\fR
filename of event log to append timeline to
.TP
\fB\-\-git\-trace2\-event\-log\fR=\fI\,GIT_TRACE2_EVENT_LOG\/\fR
directory to write git trace2 event log to
.SS "The complete list of recognized repo commands are:"
.TP .TP
abandon abandon
Permanently abandon a development branch Permanently abandon a development branch
@ -91,3 +130,4 @@ version
Display the version of repo Display the version of repo
.PP .PP
See 'repo help <command>' for more information on a specific command. See 'repo help <command>' for more information on a specific command.
Bug reports: https://bugs.chromium.org/p/gerrit/issues/entry?template=Repo+tool+issue

View File

@ -25,7 +25,7 @@ import gitc_utils
from git_config import GitConfig, IsId from git_config import GitConfig, IsId
from git_refs import R_HEADS, HEAD from git_refs import R_HEADS, HEAD
import platform_utils import platform_utils
from project import RemoteSpec, Project, MetaProject from project import Annotation, RemoteSpec, Project, MetaProject
from error import (ManifestParseError, ManifestInvalidPathError, from error import (ManifestParseError, ManifestInvalidPathError,
ManifestInvalidRevisionError) ManifestInvalidRevisionError)
from wrapper import Wrapper from wrapper import Wrapper
@ -149,16 +149,18 @@ class _XmlRemote(object):
self.reviewUrl = review self.reviewUrl = review
self.revision = revision self.revision = revision
self.resolvedFetchUrl = self._resolveFetchUrl() self.resolvedFetchUrl = self._resolveFetchUrl()
self.annotations = []
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, _XmlRemote): if not isinstance(other, _XmlRemote):
return False return False
return self.__dict__ == other.__dict__ return (sorted(self.annotations) == sorted(other.annotations) and
self.name == other.name and self.fetchUrl == other.fetchUrl and
self.pushUrl == other.pushUrl and self.remoteAlias == other.remoteAlias
and self.reviewUrl == other.reviewUrl and self.revision == other.revision)
def __ne__(self, other): def __ne__(self, other):
if not isinstance(other, _XmlRemote): return not self.__eq__(other)
return True
return self.__dict__ != other.__dict__
def _resolveFetchUrl(self): def _resolveFetchUrl(self):
if self.fetchUrl is None: if self.fetchUrl is None:
@ -191,6 +193,9 @@ class _XmlRemote(object):
orig_name=self.name, orig_name=self.name,
fetchUrl=self.fetchUrl) fetchUrl=self.fetchUrl)
def AddAnnotation(self, name, value, keep):
self.annotations.append(Annotation(name, value, keep))
class XmlManifest(object): class XmlManifest(object):
"""manages the repo configuration file""" """manages the repo configuration file"""
@ -300,6 +305,13 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
if r.revision is not None: if r.revision is not None:
e.setAttribute('revision', r.revision) e.setAttribute('revision', r.revision)
for a in r.annotations:
if a.keep == 'true':
ae = doc.createElement('annotation')
ae.setAttribute('name', a.name)
ae.setAttribute('value', a.value)
e.appendChild(ae)
def _ParseList(self, field): def _ParseList(self, field):
"""Parse fields that contain flattened lists. """Parse fields that contain flattened lists.
@ -995,7 +1007,14 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
if revision == '': if revision == '':
revision = None revision = None
manifestUrl = self.manifestProject.config.GetString('remote.origin.url') manifestUrl = self.manifestProject.config.GetString('remote.origin.url')
return _XmlRemote(name, alias, fetch, pushUrl, manifestUrl, review, revision)
remote = _XmlRemote(name, alias, fetch, pushUrl, manifestUrl, review, revision)
for n in node.childNodes:
if n.nodeName == 'annotation':
self._ParseAnnotation(remote, n)
return remote
def _ParseDefault(self, node): def _ParseDefault(self, node):
""" """
@ -1362,7 +1381,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
self._ValidateFilePaths('linkfile', src, dest) self._ValidateFilePaths('linkfile', src, dest)
project.AddLinkFile(src, dest, self.topdir) project.AddLinkFile(src, dest, self.topdir)
def _ParseAnnotation(self, project, node): def _ParseAnnotation(self, element, node):
name = self._reqatt(node, 'name') name = self._reqatt(node, 'name')
value = self._reqatt(node, 'value') value = self._reqatt(node, 'value')
try: try:
@ -1372,7 +1391,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
if keep != "true" and keep != "false": if keep != "true" and keep != "false":
raise ManifestParseError('optional "keep" attribute must be ' raise ManifestParseError('optional "keep" attribute must be '
'"true" or "false"') '"true" or "false"')
project.AddAnnotation(name, value, keep) element.AddAnnotation(name, value, keep)
def _get_remote(self, node): def _get_remote(self, node):
name = node.getAttribute('remote') name = node.getAttribute('remote')

View File

@ -251,13 +251,29 @@ class DiffColoring(Coloring):
self.fail = self.printer('fail', fg='red') self.fail = self.printer('fail', fg='red')
class _Annotation(object): class Annotation(object):
def __init__(self, name, value, keep): def __init__(self, name, value, keep):
self.name = name self.name = name
self.value = value self.value = value
self.keep = keep self.keep = keep
def __eq__(self, other):
if not isinstance(other, Annotation):
return False
return self.__dict__ == other.__dict__
def __lt__(self, other):
# This exists just so that lists of Annotation objects can be sorted, for
# use in comparisons.
if not isinstance(other, Annotation):
raise ValueError('comparison is not between two Annotation objects')
if self.name == other.name:
if self.value == other.value:
return self.keep < other.keep
return self.value < other.value
return self.name < other.name
def _SafeExpandPath(base, subpath, skipfinal=False): def _SafeExpandPath(base, subpath, skipfinal=False):
"""Make sure |subpath| is completely safe under |base|. """Make sure |subpath| is completely safe under |base|.
@ -1448,7 +1464,7 @@ class Project(object):
self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest)) self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
def AddAnnotation(self, name, value, keep): def AddAnnotation(self, name, value, keep):
self.annotations.append(_Annotation(name, value, keep)) self.annotations.append(Annotation(name, value, keep))
def DownloadPatchSet(self, change_id, patch_id): def DownloadPatchSet(self, change_id, patch_id):
"""Download a single patch set of a single change to FETCH_HEAD. """Download a single patch set of a single change to FETCH_HEAD.

View File

@ -47,6 +47,11 @@ def main(argv):
if not shutil.which('help2man'): if not shutil.which('help2man'):
sys.exit('Please install help2man to continue.') sys.exit('Please install help2man to continue.')
# Let repo know we're generating man pages so it can avoid some dynamic
# behavior (like probing active number of CPUs). We use a weird name &
# value to make it less likely for users to set this var themselves.
os.environ['_REPO_GENERATE_MANPAGES_'] = ' indeed! '
# "repo branch" is an alias for "repo branches". # "repo branch" is an alias for "repo branches".
del subcmds.all_commands['branch'] del subcmds.all_commands['branch']
(MANDIR / 'repo-branch.1').write_text('.so man1/repo-branches.1') (MANDIR / 'repo-branch.1').write_text('.so man1/repo-branches.1')
@ -59,7 +64,7 @@ def main(argv):
cmdlist.append(['help2man', '-N', '-n', 'repository management tool built on top of git', cmdlist.append(['help2man', '-N', '-n', 'repository management tool built on top of git',
'-S', 'repo', '-m', 'Repo Manual', f'--version-string={version}', '-S', 'repo', '-m', 'Repo Manual', f'--version-string={version}',
'-o', MANDIR.joinpath('repo.1'), TOPDIR.joinpath('repo'), '-o', MANDIR.joinpath('repo.1'), TOPDIR.joinpath('repo'),
'-h', 'help --all']) '-h', '--help-all'])
with tempfile.TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory() as tempdir:
repo_dir = Path(tempdir) / '.repo' repo_dir = Path(tempdir) / '.repo'

View File

@ -50,14 +50,21 @@ Displays detailed usage information about a command.
def _PrintAllCommands(self): def _PrintAllCommands(self):
print('usage: repo COMMAND [ARGS]') print('usage: repo COMMAND [ARGS]')
self.PrintAllCommandsBody()
def PrintAllCommandsBody(self):
print('The complete list of recognized repo commands are:') print('The complete list of recognized repo commands are:')
commandNames = list(sorted(all_commands)) commandNames = list(sorted(all_commands))
self._PrintCommands(commandNames) self._PrintCommands(commandNames)
print("See 'repo help <command>' for more information on a " print("See 'repo help <command>' for more information on a "
'specific command.') 'specific command.')
print('Bug reports:', Wrapper().BUG_URL)
def _PrintCommonCommands(self): def _PrintCommonCommands(self):
print('usage: repo COMMAND [ARGS]') print('usage: repo COMMAND [ARGS]')
self.PrintCommonCommandsBody()
def PrintCommonCommandsBody(self):
print('The most commonly used repo commands are:') print('The most commonly used repo commands are:')
def gitc_supported(cmd): def gitc_supported(cmd):

View File

@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import os
from command import Command, MirrorSafeCommand from command import Command, MirrorSafeCommand
@ -43,20 +45,26 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
p.add_option('-a', '--all', p.add_option('-a', '--all',
action='store_true', action='store_true',
help='show projects regardless of checkout state') help='show projects regardless of checkout state')
p.add_option('-f', '--fullpath',
dest='fullpath', action='store_true',
help='display the full work tree path instead of the relative path')
p.add_option('-n', '--name-only', p.add_option('-n', '--name-only',
dest='name_only', action='store_true', dest='name_only', action='store_true',
help='display only the name of the repository') help='display only the name of the repository')
p.add_option('-p', '--path-only', p.add_option('-p', '--path-only',
dest='path_only', action='store_true', dest='path_only', action='store_true',
help='display only the path of the repository') help='display only the path of the repository')
p.add_option('-f', '--fullpath',
dest='fullpath', action='store_true',
help='display the full work tree path instead of the relative path')
p.add_option('--relative-to', metavar='PATH',
help='display paths relative to this one (default: top of repo client checkout)')
def ValidateOptions(self, opt, args): def ValidateOptions(self, opt, args):
if opt.fullpath and opt.name_only: if opt.fullpath and opt.name_only:
self.OptionParser.error('cannot combine -f and -n') self.OptionParser.error('cannot combine -f and -n')
# Resolve any symlinks so the output is stable.
if opt.relative_to:
opt.relative_to = os.path.realpath(opt.relative_to)
def Execute(self, opt, args): def Execute(self, opt, args):
"""List all projects and the associated directories. """List all projects and the associated directories.
@ -76,6 +84,8 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
def _getpath(x): def _getpath(x):
if opt.fullpath: if opt.fullpath:
return x.worktree return x.worktree
if opt.relative_to:
return os.path.relpath(x.worktree, opt.relative_to)
return x.relpath return x.relpath
lines = [] lines = []

View File

@ -282,7 +282,7 @@ later is required to fix a server side protocol bug.
"""Returns True if current-branch or use-superproject options are enabled.""" """Returns True if current-branch or use-superproject options are enabled."""
return opt.current_branch_only or git_superproject.UseSuperproject(opt, self.manifest) return opt.current_branch_only or git_superproject.UseSuperproject(opt, self.manifest)
def _UpdateProjectsRevisionId(self, opt, args, load_local_manifests): def _UpdateProjectsRevisionId(self, opt, args, load_local_manifests, superproject_logging_data):
"""Update revisionId of every project with the SHA from superproject. """Update revisionId of every project with the SHA from superproject.
This function updates each project's revisionId with SHA from superproject. This function updates each project's revisionId with SHA from superproject.
@ -293,26 +293,37 @@ later is required to fix a server side protocol bug.
args: Arguments to pass to GetProjects. See the GetProjects args: Arguments to pass to GetProjects. See the GetProjects
docstring for details. docstring for details.
load_local_manifests: Whether to load local manifests. load_local_manifests: Whether to load local manifests.
superproject_logging_data: A dictionary of superproject data that is to be logged.
Returns: Returns:
Returns path to the overriding manifest file instead of None. Returns path to the overriding manifest file instead of None.
""" """
print_messages = git_superproject.PrintMessages(opt, self.manifest)
superproject = git_superproject.Superproject(self.manifest, superproject = git_superproject.Superproject(self.manifest,
self.repodir, self.repodir,
self.git_event_log, self.git_event_log,
quiet=opt.quiet) quiet=opt.quiet,
print_messages=print_messages)
if opt.local_only:
manifest_path = superproject.manifest_path
if manifest_path:
self._ReloadManifest(manifest_path, load_local_manifests)
return manifest_path
all_projects = self.GetProjects(args, all_projects = self.GetProjects(args,
missing_ok=True, missing_ok=True,
submodules_ok=opt.fetch_submodules) submodules_ok=opt.fetch_submodules)
update_result = superproject.UpdateProjectsRevisionId(all_projects) update_result = superproject.UpdateProjectsRevisionId(all_projects)
manifest_path = update_result.manifest_path manifest_path = update_result.manifest_path
superproject_logging_data['updatedrevisionid'] = bool(manifest_path)
if manifest_path: if manifest_path:
self._ReloadManifest(manifest_path, load_local_manifests) self._ReloadManifest(manifest_path, load_local_manifests)
else: else:
print('warning: Update of revisionId from superproject has failed, ' if print_messages:
'repo sync will not use superproject to fetch the source. ', print('warning: Update of revisionId from superproject has failed, '
'Please resync with the --no-use-superproject option to avoid this repo warning.', 'repo sync will not use superproject to fetch the source. ',
file=sys.stderr) 'Please resync with the --no-use-superproject option to avoid this repo warning.',
file=sys.stderr)
if update_result.fatal and opt.use_superproject is not None: if update_result.fatal and opt.use_superproject is not None:
sys.exit(1) sys.exit(1)
return manifest_path return manifest_path
@ -958,8 +969,15 @@ later is required to fix a server side protocol bug.
self._UpdateManifestProject(opt, mp, manifest_name) self._UpdateManifestProject(opt, mp, manifest_name)
load_local_manifests = not self.manifest.HasLocalManifests load_local_manifests = not self.manifest.HasLocalManifests
if git_superproject.UseSuperproject(opt, self.manifest): use_superproject = git_superproject.UseSuperproject(opt, self.manifest)
manifest_name = self._UpdateProjectsRevisionId(opt, args, load_local_manifests) or opt.manifest_name superproject_logging_data = {
'superproject': use_superproject,
'haslocalmanifests': bool(self.manifest.HasLocalManifests),
'hassuperprojecttag': bool(self.manifest.superproject),
}
if use_superproject:
manifest_name = self._UpdateProjectsRevisionId(
opt, args, load_local_manifests, superproject_logging_data) or opt.manifest_name
if self.gitc_manifest: if self.gitc_manifest:
gitc_manifest_projects = self.GetProjects(args, gitc_manifest_projects = self.GetProjects(args,
@ -1073,6 +1091,15 @@ later is required to fix a server side protocol bug.
file=sys.stderr) file=sys.stderr)
sys.exit(1) sys.exit(1)
# Log the previous sync analysis state from the config.
self.git_event_log.LogConfigEvents(mp.config.GetSyncAnalysisStateData(),
'previous_sync_state')
# Update and log with the new sync analysis state.
mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
self.git_event_log.LogConfigEvents(mp.config.GetSyncAnalysisStateData(),
'current_sync_state')
if not opt.quiet: if not opt.quiet:
print('repo sync has finished successfully.') print('repo sync has finished successfully.')

View File

@ -11,3 +11,13 @@
intk = 10k intk = 10k
intm = 10m intm = 10m
intg = 10g intg = 10g
[repo "syncstate.main"]
synctime = 2021-08-13T18:37:43.928600Z
version = 1
[repo "syncstate.sys"]
argv = ['/usr/bin/pytest-3']
[repo "syncstate.superproject"]
test = false
[repo "syncstate.options"]
verbose = true
mpupdate = false

View File

@ -104,6 +104,25 @@ class GitConfigReadOnlyTests(unittest.TestCase):
for key, value in TESTS: for key, value in TESTS:
self.assertEqual(value, self.config.GetInt('section.%s' % (key,))) self.assertEqual(value, self.config.GetInt('section.%s' % (key,)))
def test_GetSyncAnalysisStateData(self):
"""Test config entries with a sync state analysis data."""
superproject_logging_data = {}
superproject_logging_data['test'] = False
options = type('options', (object,), {})()
options.verbose = 'true'
options.mp_update = 'false'
TESTS = (
('superproject.test', 'false'),
('options.verbose', 'true'),
('options.mpupdate', 'false'),
('main.version', '1'),
)
self.config.UpdateSyncAnalysisState(options, superproject_logging_data)
sync_data = self.config.GetSyncAnalysisStateData()
for key, value in TESTS:
self.assertEqual(sync_data[f'{git_config.SYNC_STATE_PREFIX}{key}'], value)
self.assertTrue(sync_data[f'{git_config.SYNC_STATE_PREFIX}main.synctime'])
class GitConfigReadWriteTests(unittest.TestCase): class GitConfigReadWriteTests(unittest.TestCase):
"""Read/write tests of the GitConfig class.""" """Read/write tests of the GitConfig class."""

View File

@ -203,7 +203,7 @@ class SuperprojectTestCase(unittest.TestCase):
project.SetRevisionId('ABCDEF') project.SetRevisionId('ABCDEF')
# Create temporary directory so that it can write the file. # Create temporary directory so that it can write the file.
os.mkdir(self._superproject._superproject_path) os.mkdir(self._superproject._superproject_path)
manifest_path = self._superproject._WriteManfiestFile() manifest_path = self._superproject._WriteManifestFile()
self.assertIsNotNone(manifest_path) self.assertIsNotNone(manifest_path)
with open(manifest_path, 'r') as fp: with open(manifest_path, 'r') as fp:
manifest_xml_data = fp.read() manifest_xml_data = fp.read()

View File

@ -286,6 +286,25 @@ class XmlManifestTests(ManifestParseTestCase):
'<superproject name="superproject"/>' '<superproject name="superproject"/>'
'</manifest>') '</manifest>')
def test_remote_annotations(self):
"""Check remote settings."""
manifest = self.getXmlManifest("""
<manifest>
<remote name="test-remote" fetch="http://localhost">
<annotation name="foo" value="bar"/>
</remote>
</manifest>
""")
self.assertEqual(manifest.remotes['test-remote'].annotations[0].name, 'foo')
self.assertEqual(manifest.remotes['test-remote'].annotations[0].value, 'bar')
self.assertEqual(
sort_attributes(manifest.ToXml().toxml()),
'<?xml version="1.0" ?><manifest>'
'<remote fetch="http://localhost" name="test-remote">'
'<annotation name="foo" value="bar"/>'
'</remote>'
'</manifest>')
class IncludeElementTests(ManifestParseTestCase): class IncludeElementTests(ManifestParseTestCase):
"""Tests for <include>.""" """Tests for <include>."""
@ -632,9 +651,17 @@ class RemoteElementTests(ManifestParseTestCase):
def test_remote(self): def test_remote(self):
"""Check remote settings.""" """Check remote settings."""
a = manifest_xml._XmlRemote(name='foo') a = manifest_xml._XmlRemote(name='foo')
b = manifest_xml._XmlRemote(name='bar') a.AddAnnotation('key1', 'value1', 'true')
b = manifest_xml._XmlRemote(name='foo')
b.AddAnnotation('key2', 'value1', 'true')
c = manifest_xml._XmlRemote(name='foo')
c.AddAnnotation('key1', 'value2', 'true')
d = manifest_xml._XmlRemote(name='foo')
d.AddAnnotation('key1', 'value1', 'false')
self.assertEqual(a, a) self.assertEqual(a, a)
self.assertNotEqual(a, b) self.assertNotEqual(a, b)
self.assertNotEqual(a, c)
self.assertNotEqual(a, d)
self.assertNotEqual(a, manifest_xml._Default()) self.assertNotEqual(a, manifest_xml._Default())
self.assertNotEqual(a, 123) self.assertNotEqual(a, 123)
self.assertNotEqual(a, None) self.assertNotEqual(a, None)