Change-Id: I94d943f707b4e3cfcb7db577fc95e489b2c47cc1
This commit is contained in:
Raman Tenneti 2021-07-28 14:01:11 -07:00
commit 6edbd1ab2d
12 changed files with 200 additions and 71 deletions

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

@ -31,7 +31,8 @@ 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 Add all the data of SyncAnalysisState object. # Prefix that is prepended to all the keys of SyncAnalysisState's data
# that is saved in the config.
SYNC_STATE_PREFIX = 'syncstate.' SYNC_STATE_PREFIX = 'syncstate.'
ID_RE = re.compile(r'^[0-9a-f]{40}$') ID_RE = re.compile(r'^[0-9a-f]{40}$')
@ -271,10 +272,7 @@ class GitConfig(object):
return {k: v for k, v in self.DumpConfigDict().items() if k.startswith(SYNC_STATE_PREFIX)} return {k: v for k, v in self.DumpConfigDict().items() if k.startswith(SYNC_STATE_PREFIX)}
def UpdateSyncAnalysisState(self, options, superproject_logging_data): def UpdateSyncAnalysisState(self, options, superproject_logging_data):
"""Update Config's SyncAnalysisState with the latest sync data. """Update Config's SYNC_STATE_PREFIX* data with the latest sync data.
Creates SyncAnalysisState object with |options| and |superproject_logging_data|
which in turn persists the data into the |self| object.
Args: Args:
options: Options passed to sync returned from optparse. See _Options(). options: Options passed to sync returned from optparse. See _Options().
@ -743,7 +741,7 @@ class Branch(object):
class SyncAnalysisState(): class SyncAnalysisState():
"""Configuration options related to logging of Sync state for analysis. """Configuration options related to logging of sync state for analysis.
This object is versioned. This object is versioned.
""" """
@ -786,7 +784,7 @@ class SyncAnalysisState():
def _Set(self, key, value): def _Set(self, key, value):
"""Set the |value| for a |key| in the |_config| member. """Set the |value| for a |key| in the |_config| member.
Every key is stored by prepending the value of SYNC_STATE_PREFIX constant to it. |key| is prepended with the value of SYNC_STATE_PREFIX constant.
Args: Args:
key: Name of the key. key: Name of the key.

View File

@ -106,6 +106,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

63
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,35 +134,41 @@ 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)):
if not argv[i].startswith('-'):
name = argv[i]
if i > 0:
glob = argv[:i] 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)
if name:
name, alias_args = self._ExpandAlias(name) name, alias_args = self._ExpandAlias(name)
argv = alias_args + argv 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)
def _ExpandAlias(self, name): def _ExpandAlias(self, name):
@ -186,11 +198,20 @@ class _Repo(object):
if gopts.trace: if gopts.trace:
SetTrace() SetTrace()
if gopts.show_version:
if name == 'help': # Handle options that terminate quickly first.
if gopts.help or gopts.help_all:
self._PrintHelp(short=False, all_commands=gopts.help_all)
return 0
elif gopts.show_version:
# Always allow global --version regardless of subcommand validity.
name = 'version' name = 'version'
else: elif gopts.show_toplevel:
print('fatal: invalid usage of --version', file=sys.stderr) 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 return 1
SetDefaultColoring(gopts.color) SetDefaultColoring(gopts.color)

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

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

@ -59,7 +59,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

@ -302,6 +302,12 @@ later is required to fix a server side protocol bug.
self.repodir, self.repodir,
self.git_event_log, self.git_event_log,
quiet=opt.quiet) quiet=opt.quiet)
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)
@ -1080,11 +1086,11 @@ 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 state from the config. # Log the previous sync analysis state from the config.
self.git_event_log.LogConfigEvents(mp.config.GetSyncAnalysisStateData(), self.git_event_log.LogConfigEvents(mp.config.GetSyncAnalysisStateData(),
'previous_sync_state') 'previous_sync_state')
# Update and log with the new sync state. # Update and log with the new sync analysis state.
mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data) mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
self.git_event_log.LogConfigEvents(mp.config.GetSyncAnalysisStateData(), self.git_event_log.LogConfigEvents(mp.config.GetSyncAnalysisStateData(),
'current_sync_state') 'current_sync_state')

View File

@ -12,7 +12,7 @@
intm = 10m intm = 10m
intg = 10g intg = 10g
[syncstate "main"] [syncstate "main"]
synctime = 2021-07-28T19:42:03.866355Z synctime = 2021-07-28T21:16:10.873226Z
version = 1 version = 1
[syncstate "sys"] [syncstate "sys"]
argv = ['/usr/bin/pytest-3'] argv = ['/usr/bin/pytest-3']