├── .flake8 ├── .gitattributes ├── .github └── workflows │ ├── close-pull-request.yml │ ├── flake8-postsubmit.yml │ └── test-ci.yml ├── .gitignore ├── .gitreview ├── .isort.cfg ├── .mailmap ├── .project ├── .pydevproject ├── LICENSE ├── MANIFEST.in ├── README.md ├── SUBMITTING_PATCHES.md ├── color.py ├── command.py ├── completion.bash ├── constraints.txt ├── docs ├── internal-fs-layout.md ├── manifest-format.md ├── python-support.md ├── release-process.md ├── repo-hooks.md ├── smart-sync.md └── windows.md ├── editor.py ├── error.py ├── event_log.py ├── fetch.py ├── git_command.py ├── git_config.py ├── git_refs.py ├── git_ssh ├── git_superproject.py ├── git_trace2_event_log.py ├── git_trace2_event_log_base.py ├── hooks.py ├── hooks ├── commit-msg └── pre-auto-gc ├── main.py ├── man ├── repo-abandon.1 ├── repo-branch.1 ├── repo-branches.1 ├── repo-checkout.1 ├── repo-cherry-pick.1 ├── repo-diff.1 ├── repo-diffmanifests.1 ├── repo-download.1 ├── repo-forall.1 ├── repo-gc.1 ├── repo-grep.1 ├── repo-help.1 ├── repo-info.1 ├── repo-init.1 ├── repo-list.1 ├── repo-manifest.1 ├── repo-overview.1 ├── repo-prune.1 ├── repo-rebase.1 ├── repo-selfupdate.1 ├── repo-smartsync.1 ├── repo-stage.1 ├── repo-start.1 ├── repo-status.1 ├── repo-sync.1 ├── repo-upload.1 ├── repo-version.1 └── repo.1 ├── manifest_xml.py ├── pager.py ├── platform_utils.py ├── platform_utils_win32.py ├── progress.py ├── project.py ├── pyproject.toml ├── release ├── README.md ├── sign-launcher.py ├── sign-tag.py ├── update-hooks ├── update-manpages ├── update_manpages.py └── util.py ├── repo ├── repo_logging.py ├── repo_trace.py ├── requirements.json ├── run_tests ├── run_tests.vpython3 ├── run_tests.vpython3.8 ├── setup.py ├── ssh.py ├── subcmds ├── __init__.py ├── abandon.py ├── branches.py ├── checkout.py ├── cherry_pick.py ├── diff.py ├── diffmanifests.py ├── download.py ├── forall.py ├── gc.py ├── grep.py ├── help.py ├── info.py ├── init.py ├── list.py ├── manifest.py ├── overview.py ├── prune.py ├── rebase.py ├── selfupdate.py ├── smartsync.py ├── stage.py ├── start.py ├── status.py ├── sync.py ├── upload.py └── version.py ├── tests ├── conftest.py ├── fixtures │ ├── .gitignore │ └── test.gitconfig ├── test_color.py ├── test_editor.py ├── test_error.py ├── test_git_command.py ├── test_git_config.py ├── test_git_superproject.py ├── test_git_trace2_event_log.py ├── test_hooks.py ├── test_manifest_xml.py ├── test_platform_utils.py ├── test_project.py ├── test_repo_logging.py ├── test_repo_trace.py ├── test_ssh.py ├── test_subcmds.py ├── test_subcmds_forall.py ├── test_subcmds_init.py ├── test_subcmds_manifest.py ├── test_subcmds_sync.py ├── test_subcmds_upload.py ├── test_update_manpages.py └── test_wrapper.py ├── tox.ini └── wrapper.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 80 3 | per-file-ignores = 4 | # E501: line too long 5 | tests/test_git_superproject.py: E501 6 | extend-ignore = 7 | # E203: Whitespace before ':' 8 | # See https://github.com/PyCQA/pycodestyle/issues/373 9 | E203, 10 | # E402: Module level import not at top of file 11 | E402, 12 | # E731: do not assign a lambda expression, use a def 13 | E731, 14 | exclude = 15 | .venv, 16 | venv, 17 | .tox, 18 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Prevent /bin/sh scripts from being clobbered by autocrlf=true 2 | git_ssh text eol=lf 3 | repo text eol=lf 4 | hooks/* text eol=lf 5 | -------------------------------------------------------------------------------- /.github/workflows/close-pull-request.yml: -------------------------------------------------------------------------------- 1 | # GitHub actions workflow. 2 | # https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions 3 | 4 | # https://github.com/superbrothers/close-pull-request 5 | name: Close Pull Request 6 | 7 | on: 8 | pull_request_target: 9 | types: [opened] 10 | 11 | jobs: 12 | run: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: superbrothers/close-pull-request@v3 16 | with: 17 | comment: > 18 | Thanks for your contribution! 19 | Unfortunately, we don't use GitHub pull requests to manage code 20 | contributions to this repository. 21 | Instead, please see [README.md](../blob/HEAD/SUBMITTING_PATCHES.md) 22 | which provides full instructions on how to get involved. 23 | -------------------------------------------------------------------------------- /.github/workflows/flake8-postsubmit.yml: -------------------------------------------------------------------------------- 1 | # GitHub actions workflow. 2 | # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions 3 | # https://github.com/marketplace/actions/python-flake8 4 | 5 | name: Flake8 6 | 7 | on: 8 | push: 9 | branches: [main] 10 | 11 | jobs: 12 | lint: 13 | name: Python Lint 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/setup-python@v4 18 | with: 19 | python-version: "3.9" 20 | - name: Run flake8 21 | uses: julianwachholz/flake8-action@v2 22 | with: 23 | checkName: "Python Lint" 24 | -------------------------------------------------------------------------------- /.github/workflows/test-ci.yml: -------------------------------------------------------------------------------- 1 | # GitHub actions workflow. 2 | # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions 3 | 4 | name: Test CI 5 | 6 | on: 7 | push: 8 | branches: [main, repo-1, stable, maint] 9 | tags: [v*] 10 | 11 | jobs: 12 | test: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | # ubuntu-20.04 is the last version that supports python 3.6 17 | os: [ubuntu-20.04, macos-latest, windows-latest] 18 | python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] 19 | runs-on: ${{ matrix.os }} 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v4 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | python -m pip install tox tox-gh-actions 31 | - name: Test with tox 32 | run: tox 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.asc 2 | *.egg-info/ 3 | *.log 4 | *.pyc 5 | __pycache__ 6 | /dist 7 | .repopickle_* 8 | /repoc 9 | /.tox 10 | /.venv 11 | 12 | # PyCharm related 13 | /.idea/ 14 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=gerrit-review.googlesource.com 3 | scheme=https 4 | project=git-repo.git 5 | defaultbranch=main 6 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Config file for the isort python module. 16 | # This is used to enforce import sorting standards. 17 | # 18 | # https://pycqa.github.io/isort/docs/configuration/options.html 19 | 20 | [settings] 21 | # Be compatible with `black` since it also matches what we want. 22 | profile = black 23 | 24 | line_length = 80 25 | length_sort = false 26 | force_single_line = true 27 | lines_after_imports = 2 28 | from_first = false 29 | case_sensitive = false 30 | force_sort_within_sections = true 31 | order_by_type = false 32 | 33 | # Ignore generated files. 34 | extend_skip_glob = *_pb2.py 35 | 36 | # Allow importing multiple classes on a single line from these modules. 37 | # https://google.github.io/styleguide/pyguide#s2.2-imports 38 | single_line_exclusions = 39 | abc, 40 | collections.abc, 41 | typing, 42 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Anthony Newnam Anthony 2 | He Ping heping 3 | Hu Xiuyun Hu xiuyun 4 | Hu Xiuyun Hu Xiuyun 5 | Jelly Chen chenguodong 6 | Jia Bi bijia 7 | Jiri Tyr Jiri tyr 8 | JoonCheol Park Jooncheol Park 9 | Sergii Pylypenko pelya 10 | Shawn Pearce Shawn O. Pearce 11 | Ulrik Sjölin Ulrik Sjolin 12 | Ulrik Sjölin Ulrik Sjolin 13 | Ulrik Sjölin Ulrik Sjölin 14 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | git-repo 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | /git-repo 7 | 8 | python 2.7 9 | Default 10 | 11 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft docs hooks tests 2 | include *.py 3 | include LICENSE 4 | include git_ssh 5 | include repo 6 | include run_tests 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # repo 2 | 3 | Repo is a tool built on top of Git. Repo helps manage many Git repositories, 4 | does the uploads to revision control systems, and automates parts of the 5 | development workflow. Repo is not meant to replace Git, only to make it 6 | easier to work with Git. The repo command is an executable Python script 7 | that you can put anywhere in your path. 8 | 9 | * Homepage: 10 | * Mailing list: [repo-discuss on Google Groups][repo-discuss] 11 | * Bug reports: 12 | * Source: 13 | * Overview: 14 | * Docs: 15 | * [repo Manifest Format](./docs/manifest-format.md) 16 | * [repo Hooks](./docs/repo-hooks.md) 17 | * [Submitting patches](./SUBMITTING_PATCHES.md) 18 | * Running Repo in [Microsoft Windows](./docs/windows.md) 19 | * GitHub mirror: 20 | * Postsubmit tests: 21 | 22 | ## Contact 23 | 24 | Please use the [repo-discuss] mailing list or [issue tracker] for questions. 25 | 26 | You can [file a new bug report][new-bug] under the "repo" component. 27 | 28 | Please do not e-mail individual developers for support. 29 | They do not have the bandwidth for it, and often times questions have already 30 | been asked on [repo-discuss] or bugs posted to the [issue tracker]. 31 | So please search those sites first. 32 | 33 | ## Install 34 | 35 | Many distros include repo, so you might be able to install from there. 36 | ```sh 37 | # Debian/Ubuntu. 38 | $ sudo apt-get install repo 39 | 40 | # Gentoo. 41 | $ sudo emerge dev-vcs/repo 42 | ``` 43 | 44 | You can install it manually as well as it's a single script. 45 | ```sh 46 | $ mkdir -p ~/.bin 47 | $ PATH="${HOME}/.bin:${PATH}" 48 | $ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo 49 | $ chmod a+rx ~/.bin/repo 50 | ``` 51 | 52 | 53 | [new-bug]: https://issues.gerritcodereview.com/issues/new?component=1370071 54 | [issue tracker]: https://issues.gerritcodereview.com/issues?q=is:open%20componentid:1370071 55 | [repo-discuss]: https://groups.google.com/forum/#!forum/repo-discuss 56 | -------------------------------------------------------------------------------- /completion.bash: -------------------------------------------------------------------------------- 1 | # Copyright 2021 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Programmable bash completion. https://github.com/scop/bash-completion 16 | 17 | # TODO: Handle interspersed options. We handle `repo h`, but not 18 | # `repo --time h`. 19 | 20 | # Complete the list of repo subcommands. 21 | __complete_repo_list_commands() { 22 | local repo=${COMP_WORDS[0]} 23 | ( 24 | # Handle completions if running outside of a checkout. 25 | if ! "${repo}" help --all 2>/dev/null; then 26 | repo help 2>/dev/null 27 | fi 28 | ) | sed -n '/^ /{s/ \([^ ]\+\) .\+/\1/;p}' 29 | } 30 | 31 | # Complete list of all branches available in all projects in the repo client 32 | # checkout. 33 | __complete_repo_list_branches() { 34 | local repo=${COMP_WORDS[0]} 35 | "${repo}" branches 2>/dev/null | \ 36 | sed -n '/|/{s/[ *][Pp ] *\([^ ]\+\) .*/\1/;p}' 37 | } 38 | 39 | # Complete list of all projects available in the repo client checkout. 40 | __complete_repo_list_projects() { 41 | local repo=${COMP_WORDS[0]} 42 | "${repo}" list -n 2>/dev/null 43 | "${repo}" list -p --relative-to=. 2>/dev/null 44 | } 45 | 46 | # Complete the repo argument. 47 | __complete_repo_command() { 48 | if [[ ${COMP_CWORD} -ne 1 ]]; then 49 | return 1 50 | fi 51 | 52 | local command=${COMP_WORDS[1]} 53 | COMPREPLY=($(compgen -W "$(__complete_repo_list_commands)" -- "${command}")) 54 | return 0 55 | } 56 | 57 | # Complete repo subcommands that take . 58 | __complete_repo_command_branch_projects() { 59 | local current=$1 60 | if [[ ${COMP_CWORD} -eq 2 ]]; then 61 | COMPREPLY=($(compgen -W "$(__complete_repo_list_branches)" -- "${current}")) 62 | else 63 | COMPREPLY=($(compgen -W "$(__complete_repo_list_projects)" -- "${current}")) 64 | fi 65 | } 66 | 67 | # Complete repo subcommands that take only . 68 | __complete_repo_command_projects() { 69 | local current=$1 70 | COMPREPLY=($(compgen -W "$(__complete_repo_list_projects)" -- "${current}")) 71 | } 72 | 73 | # Complete `repo help`. 74 | __complete_repo_command_help() { 75 | local current=$1 76 | # CWORD=1 is "start". 77 | # CWORD=2 is the which we complete here. 78 | if [[ ${COMP_CWORD} -eq 2 ]]; then 79 | COMPREPLY=( 80 | $(compgen -W "$(__complete_repo_list_commands)" -- "${current}") 81 | ) 82 | fi 83 | } 84 | 85 | # Complete `repo forall`. 86 | __complete_repo_command_forall() { 87 | local current=$1 88 | # CWORD=1 is "forall". 89 | # CWORD=2+ are *until* we hit the -c option. 90 | local i 91 | for (( i = 0; i < COMP_CWORD; ++i )); do 92 | if [[ "${COMP_WORDS[i]}" == "-c" ]]; then 93 | return 0 94 | fi 95 | done 96 | 97 | COMPREPLY=( 98 | $(compgen -W "$(__complete_repo_list_projects)" -- "${current}") 99 | ) 100 | } 101 | 102 | # Complete `repo start`. 103 | __complete_repo_command_start() { 104 | local current=$1 105 | # CWORD=1 is "start". 106 | # CWORD=2 is the which we don't complete. 107 | # CWORD=3+ are which we complete here. 108 | if [[ ${COMP_CWORD} -gt 2 ]]; then 109 | COMPREPLY=( 110 | $(compgen -W "$(__complete_repo_list_projects)" -- "${current}") 111 | ) 112 | fi 113 | } 114 | 115 | # Complete the repo subcommand arguments. 116 | __complete_repo_arg() { 117 | if [[ ${COMP_CWORD} -le 1 ]]; then 118 | return 1 119 | fi 120 | 121 | local command=${COMP_WORDS[1]} 122 | local current=${COMP_WORDS[COMP_CWORD]} 123 | case ${command} in 124 | abandon|checkout) 125 | __complete_repo_command_branch_projects "${current}" 126 | return 0 127 | ;; 128 | 129 | branch|branches|diff|info|list|overview|prune|rebase|smartsync|stage|status|\ 130 | sync|upload) 131 | __complete_repo_command_projects "${current}" 132 | return 0 133 | ;; 134 | 135 | help|start|forall) 136 | __complete_repo_command_${command} "${current}" 137 | return 0 138 | ;; 139 | 140 | *) 141 | return 1 142 | ;; 143 | esac 144 | } 145 | 146 | # Complete the repo arguments. 147 | __complete_repo() { 148 | COMPREPLY=() 149 | __complete_repo_command && return 0 150 | __complete_repo_arg && return 0 151 | return 0 152 | } 153 | 154 | # Fallback to the default complete methods if we aren't able to provide anything 155 | # useful. This will allow e.g. local paths to be used when it makes sense. 156 | complete -F __complete_repo -o bashdefault -o default repo 157 | -------------------------------------------------------------------------------- /constraints.txt: -------------------------------------------------------------------------------- 1 | # NB: Keep in sync with run_tests.vpython3. 2 | black<26 3 | -------------------------------------------------------------------------------- /docs/python-support.md: -------------------------------------------------------------------------------- 1 | # Supported Python Versions 2 | 3 | This documents the current supported Python versions, and tries to provide 4 | guidance for when we decide to drop support for older versions. 5 | 6 | ## Summary 7 | 8 | * Python 3.6 (released Dec 2016) is required starting with repo-2.0. 9 | * Older versions of Python (e.g. v2.7) may use old releases via the repo-1.x 10 | branch, but no support is provided. 11 | 12 | ## repo hooks 13 | 14 | Projects that use [repo hooks] run on independent schedules. 15 | Since it's not possible to detect what version of Python the hooks were written 16 | or tested against, we always import & exec them with the active Python version. 17 | 18 | If the user's Python is too new for the [repo hooks], then it is up to the hooks 19 | maintainer to update. 20 | 21 | ## Repo launcher 22 | 23 | The [repo launcher] is an independent script that can support older versions of 24 | Python without holding back the rest of the codebase. 25 | If it detects the current version of Python is too old, it will try to reexec 26 | via a newer version of Python via standard `pythonX.Y` interpreter names. 27 | 28 | However, this is provided as a nicety when it is not onerous, and there is no 29 | official support for older versions of Python than the rest of the codebase. 30 | 31 | If your default python interpreters are too old to run the launcher even though 32 | you have newer versions installed, your choices are: 33 | 34 | * Modify the [repo launcher]'s shebang to suite your environment. 35 | * Download an older version of the [repo launcher] and don't upgrade it. 36 | Be aware that we do not guarantee old repo launchers will work with current 37 | versions of repo. Bug reports using old launchers will not be accepted. 38 | 39 | ## When to drop support 40 | 41 | So far, Python 3.6 has provided most of the interesting features that we want 42 | (e.g. typing & f-strings), and there haven't been features in newer versions 43 | that are critical to us. 44 | 45 | That said, let's assume we need functionality that only exists in Python 3.7. 46 | How do we decide when it's acceptable to drop Python 3.6? 47 | 48 | 1. Review the [Project References](./release-process.md#project-references) to 49 | see what major distros are using the previous version of Python, and when 50 | they go EOL. Generally we care about Ubuntu LTS & current/previous Debian 51 | stable versions. 52 | * If they're all EOL already, then go for it, drop support. 53 | * If they aren't EOL, start a thread on [repo-discuss] to see how the user 54 | base feels about the proposal. 55 | 1. Update the "soft" versions in the codebase. This will start warning users 56 | that the older version is deprecated. 57 | * Update [repo](/repo) if the launcher needs updating. 58 | This only helps with people who download newer launchers. 59 | * Update [main.py](/main.py) for the main codebase. 60 | This warns for everyone regardless of [repo launcher] version. 61 | * Update [requirements.json](/requirements.json). 62 | This allows [repo launcher] to display warnings/errors without having 63 | to execute the new codebase. This helps in case of syntax or module 64 | changes where older versions won't even be able to import the new code. 65 | 1. After some grace period (ideally at least 2 quarters after the first release 66 | with the updated soft requirements), update the "hard" versions, and then 67 | start using the new functionality. 68 | 69 | ## Python 2.7 & 3.0-3.5 70 | 71 | > **There is no support for these versions.** 72 | > **Do not file bugs if you are using old Python versions.** 73 | > **Any such reports will be marked invalid and ignored.** 74 | > **Upgrade your distro and/or runtime instead.** 75 | 76 | Fetch an old version of the [repo launcher]: 77 | 78 | ```sh 79 | $ curl https://storage.googleapis.com/git-repo-downloads/repo-2.32 > ~/.bin/repo-2.32 80 | $ chmod a+rx ~/.bin/repo-2.32 81 | ``` 82 | 83 | Then initialize an old version of repo: 84 | 85 | ```sh 86 | $ repo-2.32 init --repo-rev=repo-1 ... 87 | ``` 88 | 89 | 90 | [repo-discuss]: https://groups.google.com/forum/#!forum/repo-discuss 91 | [repo hooks]: ./repo-hooks.md 92 | [repo launcher]: ../repo 93 | -------------------------------------------------------------------------------- /editor.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2008 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import re 17 | import subprocess 18 | import sys 19 | import tempfile 20 | 21 | from error import EditorError 22 | import platform_utils 23 | 24 | 25 | class Editor: 26 | """Manages the user's preferred text editor.""" 27 | 28 | _editor = None 29 | globalConfig = None 30 | 31 | @classmethod 32 | def _GetEditor(cls): 33 | if cls._editor is None: 34 | cls._editor = cls._SelectEditor() 35 | return cls._editor 36 | 37 | @classmethod 38 | def _SelectEditor(cls): 39 | e = os.getenv("GIT_EDITOR") 40 | if e: 41 | return e 42 | 43 | if cls.globalConfig: 44 | e = cls.globalConfig.GetString("core.editor") 45 | if e: 46 | return e 47 | 48 | e = os.getenv("VISUAL") 49 | if e: 50 | return e 51 | 52 | e = os.getenv("EDITOR") 53 | if e: 54 | return e 55 | 56 | if os.getenv("TERM") == "dumb": 57 | print( 58 | """No editor specified in GIT_EDITOR, core.editor, VISUAL or EDITOR. 59 | Tried to fall back to vi but terminal is dumb. Please configure at 60 | least one of these before using this command.""", # noqa: E501 61 | file=sys.stderr, 62 | ) 63 | sys.exit(1) 64 | 65 | return "vi" 66 | 67 | @classmethod 68 | def EditString(cls, data): 69 | """Opens an editor to edit the given content. 70 | 71 | Args: 72 | data: The text to edit. 73 | 74 | Returns: 75 | New value of edited text. 76 | 77 | Raises: 78 | EditorError: The editor failed to run. 79 | """ 80 | editor = cls._GetEditor() 81 | if editor == ":": 82 | return data 83 | 84 | fd, path = tempfile.mkstemp() 85 | try: 86 | os.write(fd, data.encode("utf-8")) 87 | os.close(fd) 88 | fd = None 89 | 90 | if platform_utils.isWindows(): 91 | # Split on spaces, respecting quoted strings 92 | import shlex 93 | 94 | args = shlex.split(editor) 95 | shell = False 96 | elif re.compile("^.*[$ \t'].*$").match(editor): 97 | args = [editor + ' "$@"', "sh"] 98 | shell = True 99 | else: 100 | args = [editor] 101 | shell = False 102 | args.append(path) 103 | 104 | try: 105 | rc = subprocess.Popen(args, shell=shell).wait() 106 | except OSError as e: 107 | raise EditorError(f"editor failed, {str(e)}: {editor} {path}") 108 | if rc != 0: 109 | raise EditorError( 110 | "editor failed with exit status %d: %s %s" 111 | % (rc, editor, path) 112 | ) 113 | 114 | with open(path, mode="rb") as fd2: 115 | return fd2.read().decode("utf-8") 116 | finally: 117 | if fd: 118 | os.close(fd) 119 | platform_utils.remove(path) 120 | -------------------------------------------------------------------------------- /error.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2008 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import List 16 | 17 | 18 | class BaseRepoError(Exception): 19 | """All repo specific exceptions derive from BaseRepoError.""" 20 | 21 | 22 | class RepoError(BaseRepoError): 23 | """Exceptions thrown inside repo that can be handled.""" 24 | 25 | def __init__(self, *args, project: str = None) -> None: 26 | super().__init__(*args) 27 | self.project = project 28 | 29 | 30 | class RepoExitError(BaseRepoError): 31 | """Exception thrown that result in termination of repo program. 32 | - Should only be handled in main.py 33 | """ 34 | 35 | def __init__( 36 | self, 37 | *args, 38 | exit_code: int = 1, 39 | aggregate_errors: List[Exception] = None, 40 | **kwargs, 41 | ) -> None: 42 | super().__init__(*args, **kwargs) 43 | self.exit_code = exit_code 44 | self.aggregate_errors = aggregate_errors 45 | 46 | 47 | class RepoUnhandledExceptionError(RepoExitError): 48 | """Exception that maintains error as reason for program exit.""" 49 | 50 | def __init__( 51 | self, 52 | error: BaseException, 53 | **kwargs, 54 | ) -> None: 55 | super().__init__(error, **kwargs) 56 | self.error = error 57 | 58 | 59 | class SilentRepoExitError(RepoExitError): 60 | """RepoExitError that should no include CLI logging of issue/issues.""" 61 | 62 | 63 | class ManifestParseError(RepoExitError): 64 | """Failed to parse the manifest file.""" 65 | 66 | 67 | class ManifestInvalidRevisionError(ManifestParseError): 68 | """The revision value in a project is incorrect.""" 69 | 70 | 71 | class ManifestInvalidPathError(ManifestParseError): 72 | """A path used in or is incorrect.""" 73 | 74 | 75 | class NoManifestException(RepoExitError): 76 | """The required manifest does not exist.""" 77 | 78 | def __init__(self, path, reason, **kwargs): 79 | super().__init__(path, reason, **kwargs) 80 | self.path = path 81 | self.reason = reason 82 | 83 | def __str__(self): 84 | return self.reason 85 | 86 | 87 | class EditorError(RepoError): 88 | """Unspecified error from the user's text editor.""" 89 | 90 | def __init__(self, reason, **kwargs): 91 | super().__init__(reason, **kwargs) 92 | self.reason = reason 93 | 94 | def __str__(self): 95 | return self.reason 96 | 97 | 98 | class GitError(RepoError): 99 | """Unspecified git related error.""" 100 | 101 | def __init__(self, message, command_args=None, **kwargs): 102 | super().__init__(message, **kwargs) 103 | self.message = message 104 | self.command_args = command_args 105 | 106 | def __str__(self): 107 | return self.message 108 | 109 | 110 | class GitAuthError(RepoExitError): 111 | """Cannot talk to remote due to auth issue.""" 112 | 113 | 114 | class UploadError(RepoError): 115 | """A bundle upload to Gerrit did not succeed.""" 116 | 117 | def __init__(self, reason, **kwargs): 118 | super().__init__(reason, **kwargs) 119 | self.reason = reason 120 | 121 | def __str__(self): 122 | return self.reason 123 | 124 | 125 | class DownloadError(RepoExitError): 126 | """Cannot download a repository.""" 127 | 128 | def __init__(self, reason, **kwargs): 129 | super().__init__(reason, **kwargs) 130 | self.reason = reason 131 | 132 | def __str__(self): 133 | return self.reason 134 | 135 | 136 | class InvalidArgumentsError(RepoExitError): 137 | """Invalid command Arguments.""" 138 | 139 | 140 | class SyncError(RepoExitError): 141 | """Cannot sync repo.""" 142 | 143 | 144 | class UpdateManifestError(RepoExitError): 145 | """Cannot update manifest.""" 146 | 147 | 148 | class NoSuchProjectError(RepoExitError): 149 | """A specified project does not exist in the work tree.""" 150 | 151 | def __init__(self, name=None, **kwargs): 152 | super().__init__(**kwargs) 153 | self.name = name 154 | 155 | def __str__(self): 156 | if self.name is None: 157 | return "in current directory" 158 | return self.name 159 | 160 | 161 | class InvalidProjectGroupsError(RepoExitError): 162 | """A specified project is not suitable for the specified groups""" 163 | 164 | def __init__(self, name=None, **kwargs): 165 | super().__init__(**kwargs) 166 | self.name = name 167 | 168 | def __str__(self): 169 | if self.name is None: 170 | return "in current directory" 171 | return self.name 172 | 173 | 174 | class RepoChangedException(BaseRepoError): 175 | """Thrown if 'repo sync' results in repo updating its internal 176 | repo or manifest repositories. In this special case we must 177 | use exec to re-execute repo with the new code and manifest. 178 | """ 179 | 180 | def __init__(self, extra_args=None): 181 | super().__init__(extra_args) 182 | self.extra_args = extra_args or [] 183 | 184 | 185 | class HookError(RepoError): 186 | """Thrown if a 'repo-hook' could not be run. 187 | 188 | The common case is that the file wasn't present when we tried to run it. 189 | """ 190 | -------------------------------------------------------------------------------- /fetch.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """This module contains functions used to fetch files from various sources.""" 16 | 17 | import subprocess 18 | import sys 19 | from urllib.parse import urlparse 20 | from urllib.request import urlopen 21 | 22 | from error import RepoExitError 23 | 24 | 25 | class FetchFileError(RepoExitError): 26 | """Exit error when fetch_file fails.""" 27 | 28 | 29 | def fetch_file(url, verbose=False): 30 | """Fetch a file from the specified source using the appropriate protocol. 31 | 32 | Returns: 33 | The contents of the file as bytes. 34 | """ 35 | scheme = urlparse(url).scheme 36 | if scheme == "gs": 37 | cmd = ["gsutil", "cat", url] 38 | errors = [] 39 | try: 40 | result = subprocess.run( 41 | cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True 42 | ) 43 | if result.stderr and verbose: 44 | print( 45 | 'warning: non-fatal error running "gsutil": %s' 46 | % result.stderr, 47 | file=sys.stderr, 48 | ) 49 | return result.stdout 50 | except subprocess.CalledProcessError as e: 51 | errors.append(e) 52 | print( 53 | 'fatal: error running "gsutil": %s' % e.stderr, file=sys.stderr 54 | ) 55 | raise FetchFileError(aggregate_errors=errors) 56 | with urlopen(url) as f: 57 | return f.read() 58 | -------------------------------------------------------------------------------- /git_refs.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | 17 | import platform_utils 18 | from repo_trace import Trace 19 | 20 | 21 | HEAD = "HEAD" 22 | R_CHANGES = "refs/changes/" 23 | R_HEADS = "refs/heads/" 24 | R_TAGS = "refs/tags/" 25 | R_PUB = "refs/published/" 26 | R_WORKTREE = "refs/worktree/" 27 | R_WORKTREE_M = R_WORKTREE + "m/" 28 | R_M = "refs/remotes/m/" 29 | 30 | 31 | class GitRefs: 32 | def __init__(self, gitdir): 33 | self._gitdir = gitdir 34 | self._phyref = None 35 | self._symref = None 36 | self._mtime = {} 37 | 38 | @property 39 | def all(self): 40 | self._EnsureLoaded() 41 | return self._phyref 42 | 43 | def get(self, name): 44 | try: 45 | return self.all[name] 46 | except KeyError: 47 | return "" 48 | 49 | def deleted(self, name): 50 | if self._phyref is not None: 51 | if name in self._phyref: 52 | del self._phyref[name] 53 | 54 | if name in self._symref: 55 | del self._symref[name] 56 | 57 | if name in self._mtime: 58 | del self._mtime[name] 59 | 60 | def symref(self, name): 61 | try: 62 | self._EnsureLoaded() 63 | return self._symref[name] 64 | except KeyError: 65 | return "" 66 | 67 | def _EnsureLoaded(self): 68 | if self._phyref is None or self._NeedUpdate(): 69 | self._LoadAll() 70 | 71 | def _NeedUpdate(self): 72 | with Trace(": scan refs %s", self._gitdir): 73 | for name, mtime in self._mtime.items(): 74 | try: 75 | if mtime != os.path.getmtime( 76 | os.path.join(self._gitdir, name) 77 | ): 78 | return True 79 | except OSError: 80 | return True 81 | return False 82 | 83 | def _LoadAll(self): 84 | with Trace(": load refs %s", self._gitdir): 85 | self._phyref = {} 86 | self._symref = {} 87 | self._mtime = {} 88 | 89 | self._ReadPackedRefs() 90 | self._ReadLoose("refs/") 91 | self._ReadLoose1(os.path.join(self._gitdir, HEAD), HEAD) 92 | 93 | scan = self._symref 94 | attempts = 0 95 | while scan and attempts < 5: 96 | scan_next = {} 97 | for name, dest in scan.items(): 98 | if dest in self._phyref: 99 | self._phyref[name] = self._phyref[dest] 100 | else: 101 | scan_next[name] = dest 102 | scan = scan_next 103 | attempts += 1 104 | 105 | def _ReadPackedRefs(self): 106 | path = os.path.join(self._gitdir, "packed-refs") 107 | try: 108 | fd = open(path) 109 | mtime = os.path.getmtime(path) 110 | except OSError: 111 | return 112 | try: 113 | for line in fd: 114 | line = str(line) 115 | if line[0] == "#": 116 | continue 117 | if line[0] == "^": 118 | continue 119 | 120 | line = line[:-1] 121 | p = line.split(" ") 122 | ref_id = p[0] 123 | name = p[1] 124 | 125 | self._phyref[name] = ref_id 126 | finally: 127 | fd.close() 128 | self._mtime["packed-refs"] = mtime 129 | 130 | def _ReadLoose(self, prefix): 131 | base = os.path.join(self._gitdir, prefix) 132 | for name in platform_utils.listdir(base): 133 | p = os.path.join(base, name) 134 | # We don't implement the full ref validation algorithm, just the 135 | # simple rules that would show up in local filesystems. 136 | # https://git-scm.com/docs/git-check-ref-format 137 | if name.startswith(".") or name.endswith(".lock"): 138 | pass 139 | elif platform_utils.isdir(p): 140 | self._mtime[prefix] = os.path.getmtime(base) 141 | self._ReadLoose(prefix + name + "/") 142 | else: 143 | self._ReadLoose1(p, prefix + name) 144 | 145 | def _ReadLoose1(self, path, name): 146 | try: 147 | with open(path) as fd: 148 | mtime = os.path.getmtime(path) 149 | ref_id = fd.readline() 150 | except (OSError, UnicodeError): 151 | return 152 | 153 | try: 154 | ref_id = ref_id.decode() 155 | except AttributeError: 156 | pass 157 | if not ref_id: 158 | return 159 | ref_id = ref_id[:-1] 160 | 161 | if ref_id.startswith("ref: "): 162 | self._symref[name] = ref_id[5:] 163 | else: 164 | self._phyref[name] = ref_id 165 | self._mtime[name] = mtime 166 | -------------------------------------------------------------------------------- /git_ssh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (C) 2009 The Android Open Source Project 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | exec ssh -o "ControlMaster no" -o "ControlPath $REPO_SSH_SOCK" "$@" 18 | -------------------------------------------------------------------------------- /git_trace2_event_log.py: -------------------------------------------------------------------------------- 1 | from git_command import GetEventTargetPath 2 | from git_command import RepoSourceVersion 3 | from git_trace2_event_log_base import BaseEventLog 4 | 5 | 6 | class EventLog(BaseEventLog): 7 | """Event log that records events that occurred during a repo invocation. 8 | 9 | Events are written to the log as a consecutive JSON entries, one per line. 10 | Entries follow the git trace2 EVENT format. 11 | 12 | Each entry contains the following common keys: 13 | - event: The event name 14 | - sid: session-id - Unique string to allow process instance to be 15 | identified. 16 | - thread: The thread name. 17 | - time: is the UTC time of the event. 18 | 19 | Valid 'event' names and event specific fields are documented here: 20 | https://git-scm.com/docs/api-trace2#_event_format 21 | """ 22 | 23 | def __init__(self, **kwargs): 24 | super().__init__(repo_source_version=RepoSourceVersion(), **kwargs) 25 | 26 | def Write(self, path=None, **kwargs): 27 | if path is None: 28 | path = self._GetEventTargetPath() 29 | return super().Write(path=path, **kwargs) 30 | 31 | def _GetEventTargetPath(self): 32 | return GetEventTargetPath() 33 | -------------------------------------------------------------------------------- /hooks/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # DO NOT EDIT THIS FILE 3 | # All updates should be sent upstream: https://gerrit.googlesource.com/gerrit/ 4 | # This is synced from commit: 62f5bbea67f6dafa6e22a601a0c298214c510caf 5 | # DO NOT EDIT THIS FILE 6 | # 7 | # Part of Gerrit Code Review (https://www.gerritcodereview.com/) 8 | # 9 | # Copyright (C) 2009 The Android Open Source Project 10 | # 11 | # Licensed under the Apache License, Version 2.0 (the "License"); 12 | # you may not use this file except in compliance with the License. 13 | # You may obtain a copy of the License at 14 | # 15 | # http://www.apache.org/licenses/LICENSE-2.0 16 | # 17 | # Unless required by applicable law or agreed to in writing, software 18 | # distributed under the License is distributed on an "AS IS" BASIS, 19 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | # See the License for the specific language governing permissions and 21 | # limitations under the License. 22 | 23 | set -u 24 | 25 | # avoid [[ which is not POSIX sh. 26 | if test "$#" != 1 ; then 27 | echo "$0 requires an argument." 28 | exit 1 29 | fi 30 | 31 | if test ! -f "$1" ; then 32 | echo "file does not exist: $1" 33 | exit 1 34 | fi 35 | 36 | # Do not create a change id if requested 37 | case "$(git config --get gerrit.createChangeId)" in 38 | false) 39 | exit 0 40 | ;; 41 | always) 42 | ;; 43 | *) 44 | # Do not create a change id for squash/fixup commits. 45 | if head -n1 "$1" | LC_ALL=C grep -q '^[a-z][a-z]*! '; then 46 | exit 0 47 | fi 48 | ;; 49 | esac 50 | 51 | 52 | if git rev-parse --verify HEAD >/dev/null 2>&1; then 53 | refhash="$(git rev-parse HEAD)" 54 | else 55 | refhash="$(git hash-object -t tree /dev/null)" 56 | fi 57 | 58 | random=$({ git var GIT_COMMITTER_IDENT ; echo "$refhash" ; cat "$1"; } | git hash-object --stdin) 59 | dest="$1.tmp.${random}" 60 | 61 | trap 'rm -f "$dest" "$dest-2"' EXIT 62 | 63 | if ! cat "$1" | sed -e '/>8/q' | git stripspace --strip-comments > "${dest}" ; then 64 | echo "cannot strip comments from $1" 65 | exit 1 66 | fi 67 | 68 | if test ! -s "${dest}" ; then 69 | echo "file is empty: $1" 70 | exit 1 71 | fi 72 | 73 | reviewurl="$(git config --get gerrit.reviewUrl)" 74 | if test -n "${reviewurl}" ; then 75 | token="Link" 76 | value="${reviewurl%/}/id/I$random" 77 | pattern=".*/id/I[0-9a-f]\{40\}" 78 | else 79 | token="Change-Id" 80 | value="I$random" 81 | pattern=".*" 82 | fi 83 | 84 | if git interpret-trailers --parse < "$1" | grep -q "^$token: $pattern$" ; then 85 | exit 0 86 | fi 87 | 88 | # There must be a Signed-off-by trailer for the code below to work. Insert a 89 | # sentinel at the end to make sure there is one. 90 | # Avoid the --in-place option which only appeared in Git 2.8 91 | if ! git interpret-trailers \ 92 | --trailer "Signed-off-by: SENTINEL" < "$1" > "$dest-2" ; then 93 | echo "cannot insert Signed-off-by sentinel line in $1" 94 | exit 1 95 | fi 96 | 97 | # Make sure the trailer appears before any Signed-off-by trailers by inserting 98 | # it as if it was a Signed-off-by trailer and then use sed to remove the 99 | # Signed-off-by prefix and the Signed-off-by sentinel line. 100 | # Avoid the --in-place option which only appeared in Git 2.8 101 | # Avoid the --where option which only appeared in Git 2.15 102 | if ! git -c trailer.where=before interpret-trailers \ 103 | --trailer "Signed-off-by: $token: $value" < "$dest-2" | 104 | sed -e "s/^Signed-off-by: \($token: \)/\1/" \ 105 | -e "/^Signed-off-by: SENTINEL/d" > "$dest" ; then 106 | echo "cannot insert $token line in $1" 107 | exit 1 108 | fi 109 | 110 | if ! mv "${dest}" "$1" ; then 111 | echo "cannot mv ${dest} to $1" 112 | exit 1 113 | fi 114 | -------------------------------------------------------------------------------- /hooks/pre-auto-gc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # DO NOT EDIT THIS FILE 3 | # All updates should be sent upstream: https://github.com/git/git 4 | # This is synced from commit: 00e10ef10e161a913893b8cb33aa080d4ca5baa6 5 | # DO NOT EDIT THIS FILE 6 | # 7 | # An example hook script to verify if you are on battery, in case you 8 | # are running Linux or OS X. Called by git-gc --auto with no arguments. 9 | # The hook should exit with non-zero status after issuing an appropriate 10 | # message if it wants to stop the auto repacking. 11 | # 12 | # This hook is stored in the contrib/hooks directory. Your distribution 13 | # may have put this somewhere else. If you want to use this hook, you 14 | # should make this script executable then link to it in the repository 15 | # you would like to use it in. 16 | # 17 | # For example, if the hook is stored in 18 | # /usr/share/git-core/contrib/hooks/pre-auto-gc-battery: 19 | # 20 | # cd /path/to/your/repository.git 21 | # ln -sf /usr/share/git-core/contrib/hooks/pre-auto-gc-battery \ 22 | # hooks/pre-auto-gc 23 | 24 | if test -x /sbin/on_ac_power && (/sbin/on_ac_power;test $? -ne 1) 25 | then 26 | exit 0 27 | elif test "$(cat /sys/class/power_supply/AC/online 2>/dev/null)" = 1 28 | then 29 | exit 0 30 | elif grep -q 'on-line' /proc/acpi/ac_adapter/AC/state 2>/dev/null 31 | then 32 | exit 0 33 | elif grep -q '0x01$' /proc/apm 2>/dev/null 34 | then 35 | exit 0 36 | elif grep -q "AC Power \+: 1" /proc/pmu/info 2>/dev/null 37 | then 38 | exit 0 39 | elif test -x /usr/bin/pmset && /usr/bin/pmset -g batt | 40 | grep -q "drawing from 'AC Power'" 41 | then 42 | exit 0 43 | fi 44 | 45 | echo "Auto packing deferred; not on AC" 46 | exit 1 47 | -------------------------------------------------------------------------------- /man/repo-abandon.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo abandon" "Repo Manual" 3 | .SH NAME 4 | repo \- repo abandon - manual page for repo abandon 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,abandon \/\fR[\fI\,--all | \/\fR] [\fI\,\/\fR...] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Permanently abandon a development branch 12 | .PP 13 | This subcommand permanently abandons a development branch by 14 | deleting it (and all its history) from your local repository. 15 | .PP 16 | It is equivalent to "git branch \fB\-D\fR ". 17 | .SH OPTIONS 18 | .TP 19 | \fB\-h\fR, \fB\-\-help\fR 20 | show this help message and exit 21 | .TP 22 | \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR 23 | number of jobs to run in parallel (default: based on 24 | number of CPU cores) 25 | .TP 26 | \fB\-\-all\fR 27 | delete all branches in all projects 28 | .SS Logging options: 29 | .TP 30 | \fB\-v\fR, \fB\-\-verbose\fR 31 | show all output 32 | .TP 33 | \fB\-q\fR, \fB\-\-quiet\fR 34 | only show errors 35 | .SS Multi\-manifest options: 36 | .TP 37 | \fB\-\-outer\-manifest\fR 38 | operate starting at the outermost manifest 39 | .TP 40 | \fB\-\-no\-outer\-manifest\fR 41 | do not operate on outer manifests 42 | .TP 43 | \fB\-\-this\-manifest\-only\fR 44 | only operate on this (sub)manifest 45 | .TP 46 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 47 | operate on this manifest and its submanifests 48 | .PP 49 | Run `repo help abandon` to view the detailed manual. 50 | -------------------------------------------------------------------------------- /man/repo-branch.1: -------------------------------------------------------------------------------- 1 | .so man1/repo-branches.1 -------------------------------------------------------------------------------- /man/repo-branches.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo branches" "Repo Manual" 3 | .SH NAME 4 | repo \- repo branches - manual page for repo branches 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,branches \/\fR[\fI\,\/\fR...] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | View current topic branches 12 | .PP 13 | Summarizes the currently available topic branches. 14 | .PP 15 | # Branch Display 16 | .PP 17 | The branch display output by this command is organized into four 18 | columns of information; for example: 19 | .TP 20 | *P nocolor 21 | | in repo 22 | .TP 23 | repo2 24 | | 25 | .PP 26 | The first column contains a * if the branch is the currently 27 | checked out branch in any of the specified projects, or a blank 28 | if no project has the branch checked out. 29 | .PP 30 | The second column contains either blank, p or P, depending upon 31 | the upload status of the branch. 32 | .IP 33 | (blank): branch not yet published by repo upload 34 | .IP 35 | P: all commits were published by repo upload 36 | p: only some commits were published by repo upload 37 | .PP 38 | The third column contains the branch name. 39 | .PP 40 | The fourth column (after the | separator) lists the projects that 41 | the branch appears in, or does not appear in. If no project list 42 | is shown, then the branch appears in all projects. 43 | .SH OPTIONS 44 | .TP 45 | \fB\-h\fR, \fB\-\-help\fR 46 | show this help message and exit 47 | .TP 48 | \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR 49 | number of jobs to run in parallel (default: based on 50 | number of CPU cores) 51 | .SS Logging options: 52 | .TP 53 | \fB\-v\fR, \fB\-\-verbose\fR 54 | show all output 55 | .TP 56 | \fB\-q\fR, \fB\-\-quiet\fR 57 | only show errors 58 | .SS Multi\-manifest options: 59 | .TP 60 | \fB\-\-outer\-manifest\fR 61 | operate starting at the outermost manifest 62 | .TP 63 | \fB\-\-no\-outer\-manifest\fR 64 | do not operate on outer manifests 65 | .TP 66 | \fB\-\-this\-manifest\-only\fR 67 | only operate on this (sub)manifest 68 | .TP 69 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 70 | operate on this manifest and its submanifests 71 | .PP 72 | Run `repo help branches` to view the detailed manual. 73 | -------------------------------------------------------------------------------- /man/repo-checkout.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo checkout" "Repo Manual" 3 | .SH NAME 4 | repo \- repo checkout - manual page for repo checkout 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,checkout \/\fR[\fI\,\/\fR...] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Checkout a branch for development 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .TP 17 | \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR 18 | number of jobs to run in parallel (default: based on 19 | number of CPU cores) 20 | .SS Logging options: 21 | .TP 22 | \fB\-v\fR, \fB\-\-verbose\fR 23 | show all output 24 | .TP 25 | \fB\-q\fR, \fB\-\-quiet\fR 26 | only show errors 27 | .SS Multi\-manifest options: 28 | .TP 29 | \fB\-\-outer\-manifest\fR 30 | operate starting at the outermost manifest 31 | .TP 32 | \fB\-\-no\-outer\-manifest\fR 33 | do not operate on outer manifests 34 | .TP 35 | \fB\-\-this\-manifest\-only\fR 36 | only operate on this (sub)manifest 37 | .TP 38 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 39 | operate on this manifest and its submanifests 40 | .PP 41 | Run `repo help checkout` to view the detailed manual. 42 | .SH DETAILS 43 | .PP 44 | The 'repo checkout' command checks out an existing branch that was previously 45 | created by 'repo start'. 46 | .PP 47 | The command is equivalent to: 48 | .IP 49 | repo forall [...] \fB\-c\fR git checkout 50 | -------------------------------------------------------------------------------- /man/repo-cherry-pick.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo cherry-pick" "Repo Manual" 3 | .SH NAME 4 | repo \- repo cherry-pick - manual page for repo cherry-pick 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,cherry-pick \/\fR 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Cherry\-pick a change. 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .SS Logging options: 17 | .TP 18 | \fB\-v\fR, \fB\-\-verbose\fR 19 | show all output 20 | .TP 21 | \fB\-q\fR, \fB\-\-quiet\fR 22 | only show errors 23 | .SS Multi\-manifest options: 24 | .TP 25 | \fB\-\-outer\-manifest\fR 26 | operate starting at the outermost manifest 27 | .TP 28 | \fB\-\-no\-outer\-manifest\fR 29 | do not operate on outer manifests 30 | .TP 31 | \fB\-\-this\-manifest\-only\fR 32 | only operate on this (sub)manifest 33 | .TP 34 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 35 | operate on this manifest and its submanifests 36 | .PP 37 | Run `repo help cherry\-pick` to view the detailed manual. 38 | .SH DETAILS 39 | .PP 40 | \&'repo cherry\-pick' cherry\-picks a change from one branch to another. The change 41 | id will be updated, and a reference to the old change id will be added. 42 | -------------------------------------------------------------------------------- /man/repo-diff.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo diff" "Repo Manual" 3 | .SH NAME 4 | repo \- repo diff - manual page for repo diff 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,diff \/\fR[\fI\,\/\fR...] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Show changes between commit and working tree 12 | .PP 13 | The \fB\-u\fR option causes 'repo diff' to generate diff output with file paths 14 | relative to the repository root, so the output can be applied 15 | to the Unix 'patch' command. 16 | .SH OPTIONS 17 | .TP 18 | \fB\-h\fR, \fB\-\-help\fR 19 | show this help message and exit 20 | .TP 21 | \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR 22 | number of jobs to run in parallel (default: based on 23 | number of CPU cores) 24 | .TP 25 | \fB\-u\fR, \fB\-\-absolute\fR 26 | paths are relative to the repository root 27 | .SS Logging options: 28 | .TP 29 | \fB\-v\fR, \fB\-\-verbose\fR 30 | show all output 31 | .TP 32 | \fB\-q\fR, \fB\-\-quiet\fR 33 | only show errors 34 | .SS Multi\-manifest options: 35 | .TP 36 | \fB\-\-outer\-manifest\fR 37 | operate starting at the outermost manifest 38 | .TP 39 | \fB\-\-no\-outer\-manifest\fR 40 | do not operate on outer manifests 41 | .TP 42 | \fB\-\-this\-manifest\-only\fR 43 | only operate on this (sub)manifest 44 | .TP 45 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 46 | operate on this manifest and its submanifests 47 | .PP 48 | Run `repo help diff` to view the detailed manual. 49 | -------------------------------------------------------------------------------- /man/repo-diffmanifests.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo diffmanifests" "Repo Manual" 3 | .SH NAME 4 | repo \- repo diffmanifests - manual page for repo diffmanifests 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,diffmanifests manifest1.xml \/\fR[\fI\,manifest2.xml\/\fR] [\fI\,options\/\fR] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Manifest diff utility 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .TP 17 | \fB\-\-raw\fR 18 | display raw diff 19 | .TP 20 | \fB\-\-no\-color\fR 21 | does not display the diff in color 22 | .TP 23 | \fB\-\-pretty\-format=\fR 24 | print the log using a custom git pretty format string 25 | .SS Logging options: 26 | .TP 27 | \fB\-v\fR, \fB\-\-verbose\fR 28 | show all output 29 | .TP 30 | \fB\-q\fR, \fB\-\-quiet\fR 31 | only show errors 32 | .SS Multi\-manifest options: 33 | .TP 34 | \fB\-\-outer\-manifest\fR 35 | operate starting at the outermost manifest 36 | .TP 37 | \fB\-\-no\-outer\-manifest\fR 38 | do not operate on outer manifests 39 | .TP 40 | \fB\-\-this\-manifest\-only\fR 41 | only operate on this (sub)manifest 42 | .TP 43 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 44 | operate on this manifest and its submanifests 45 | .PP 46 | Run `repo help diffmanifests` to view the detailed manual. 47 | .SH DETAILS 48 | .PP 49 | The repo diffmanifests command shows differences between project revisions of 50 | manifest1 and manifest2. if manifest2 is not specified, current manifest.xml 51 | will be used instead. Both absolute and relative paths may be used for 52 | manifests. Relative paths start from project's ".repo/manifests" folder. 53 | .PP 54 | The \fB\-\-raw\fR option Displays the diff in a way that facilitates parsing, the 55 | project pattern will be [] and the 56 | commit pattern will be with status values respectively : 57 | .IP 58 | A = Added project 59 | R = Removed project 60 | C = Changed project 61 | U = Project with unreachable revision(s) (revision(s) not found) 62 | .PP 63 | for project, and 64 | .IP 65 | A = Added commit 66 | R = Removed commit 67 | .PP 68 | for a commit. 69 | .PP 70 | Only changed projects may contain commits, and commit status always starts with 71 | a space, and are part of last printed project. Unreachable revisions may occur 72 | if project is not up to date or if repo has not been initialized with all the 73 | groups, in which case some projects won't be synced and their revisions won't be 74 | found. 75 | -------------------------------------------------------------------------------- /man/repo-download.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo download" "Repo Manual" 3 | .SH NAME 4 | repo \- repo download - manual page for repo download 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,download {\/\fR[\fI\,project\/\fR] \fI\,change\/\fR[\fI\,/patchset\/\fR]\fI\,}\/\fR... 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Download and checkout a change 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .TP 17 | \fB\-b\fR BRANCH, \fB\-\-branch\fR=\fI\,BRANCH\/\fR 18 | create a new branch first 19 | .TP 20 | \fB\-c\fR, \fB\-\-cherry\-pick\fR 21 | cherry\-pick instead of checkout 22 | .TP 23 | \fB\-x\fR, \fB\-\-record\-origin\fR 24 | pass \fB\-x\fR when cherry\-picking 25 | .TP 26 | \fB\-r\fR, \fB\-\-revert\fR 27 | revert instead of checkout 28 | .TP 29 | \fB\-f\fR, \fB\-\-ff\-only\fR 30 | force fast\-forward merge 31 | .SS Logging options: 32 | .TP 33 | \fB\-v\fR, \fB\-\-verbose\fR 34 | show all output 35 | .TP 36 | \fB\-q\fR, \fB\-\-quiet\fR 37 | only show errors 38 | .SS Multi\-manifest options: 39 | .TP 40 | \fB\-\-outer\-manifest\fR 41 | operate starting at the outermost manifest 42 | .TP 43 | \fB\-\-no\-outer\-manifest\fR 44 | do not operate on outer manifests 45 | .TP 46 | \fB\-\-this\-manifest\-only\fR 47 | only operate on this (sub)manifest 48 | .TP 49 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 50 | operate on this manifest and its submanifests 51 | .PP 52 | Run `repo help download` to view the detailed manual. 53 | .SH DETAILS 54 | .PP 55 | The 'repo download' command downloads a change from the review system and makes 56 | it available in your project's local working directory. If no project is 57 | specified try to use current directory as a project. 58 | -------------------------------------------------------------------------------- /man/repo-forall.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo forall" "Repo Manual" 3 | .SH NAME 4 | repo \- repo forall - manual page for repo forall 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,forall \/\fR[\fI\,\/\fR...] \fI\,-c \/\fR[\fI\,\/\fR...] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Run a shell command in each project 12 | .PP 13 | repo forall \fB\-r\fR str1 [str2] ... \fB\-c\fR [...] 14 | .SH OPTIONS 15 | .TP 16 | \fB\-h\fR, \fB\-\-help\fR 17 | show this help message and exit 18 | .TP 19 | \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR 20 | number of jobs to run in parallel (default: based on 21 | number of CPU cores) 22 | .TP 23 | \fB\-r\fR, \fB\-\-regex\fR 24 | execute the command only on projects matching regex or 25 | wildcard expression 26 | .TP 27 | \fB\-i\fR, \fB\-\-inverse\-regex\fR 28 | execute the command only on projects not matching 29 | regex or wildcard expression 30 | .TP 31 | \fB\-g\fR GROUPS, \fB\-\-groups\fR=\fI\,GROUPS\/\fR 32 | execute the command only on projects matching the 33 | specified groups 34 | .TP 35 | \fB\-c\fR, \fB\-\-command\fR 36 | command (and arguments) to execute 37 | .TP 38 | \fB\-e\fR, \fB\-\-abort\-on\-errors\fR 39 | abort if a command exits unsuccessfully 40 | .TP 41 | \fB\-\-ignore\-missing\fR 42 | silently skip & do not exit non\-zero due missing 43 | checkouts 44 | .TP 45 | \fB\-\-interactive\fR 46 | force interactive usage 47 | .SS Logging options: 48 | .TP 49 | \fB\-v\fR, \fB\-\-verbose\fR 50 | show all output 51 | .TP 52 | \fB\-q\fR, \fB\-\-quiet\fR 53 | only show errors 54 | .TP 55 | \fB\-p\fR 56 | show project headers before output 57 | .SS Multi\-manifest options: 58 | .TP 59 | \fB\-\-outer\-manifest\fR 60 | operate starting at the outermost manifest 61 | .TP 62 | \fB\-\-no\-outer\-manifest\fR 63 | do not operate on outer manifests 64 | .TP 65 | \fB\-\-this\-manifest\-only\fR 66 | only operate on this (sub)manifest 67 | .TP 68 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 69 | operate on this manifest and its submanifests 70 | .PP 71 | Run `repo help forall` to view the detailed manual. 72 | .SH DETAILS 73 | .PP 74 | Executes the same shell command in each project. 75 | .PP 76 | The \fB\-r\fR option allows running the command only on projects matching regex or 77 | wildcard expression. 78 | .PP 79 | By default, projects are processed non\-interactively in parallel. If you want to 80 | run interactive commands, make sure to pass \fB\-\-interactive\fR to force \fB\-\-jobs\fR 1. 81 | While the processing order of projects is not guaranteed, the order of project 82 | output is stable. 83 | .PP 84 | Output Formatting 85 | .PP 86 | The \fB\-p\fR option causes 'repo forall' to bind pipes to the command's stdin, stdout 87 | and stderr streams, and pipe all output into a continuous stream that is 88 | displayed in a single pager session. Project headings are inserted before the 89 | output of each command is displayed. If the command produces no output in a 90 | project, no heading is displayed. 91 | .PP 92 | The formatting convention used by \fB\-p\fR is very suitable for some types of 93 | searching, e.g. `repo forall \fB\-p\fR \fB\-c\fR git log \fB\-SFoo\fR` will print all commits that 94 | add or remove references to Foo. 95 | .PP 96 | The \fB\-v\fR option causes 'repo forall' to display stderr messages if a command 97 | produces output only on stderr. Normally the \fB\-p\fR option causes command output to 98 | be suppressed until the command produces at least one byte of output on stdout. 99 | .PP 100 | Environment 101 | .PP 102 | pwd is the project's working directory. If the current client is a mirror 103 | client, then pwd is the Git repository. 104 | .PP 105 | REPO_PROJECT is set to the unique name of the project. 106 | .PP 107 | REPO_PATH is the path relative the the root of the client. 108 | .PP 109 | REPO_OUTERPATH is the path of the sub manifest's root relative to the root of 110 | the client. 111 | .PP 112 | REPO_INNERPATH is the path relative to the root of the sub manifest. 113 | .PP 114 | REPO_REMOTE is the name of the remote system from the manifest. 115 | .PP 116 | REPO_LREV is the name of the revision from the manifest, translated to a local 117 | tracking branch. If you need to pass the manifest revision to a locally executed 118 | git command, use REPO_LREV. 119 | .PP 120 | REPO_RREV is the name of the revision from the manifest, exactly as written in 121 | the manifest. 122 | .PP 123 | REPO_COUNT is the total number of projects being iterated. 124 | .PP 125 | REPO_I is the current (1\-based) iteration count. Can be used in conjunction with 126 | REPO_COUNT to add a simple progress indicator to your command. 127 | .PP 128 | REPO__* are any extra environment variables, specified by the "annotation" 129 | element under any project element. This can be useful for differentiating trees 130 | based on user\-specific criteria, or simply annotating tree details. 131 | .PP 132 | shell positional arguments ($1, $2, .., $#) are set to any arguments following 133 | . 134 | .PP 135 | Example: to list projects: 136 | .IP 137 | repo forall \fB\-c\fR 'echo $REPO_PROJECT' 138 | .PP 139 | Notice that $REPO_PROJECT is quoted to ensure it is expanded in the context of 140 | running instead of in the calling shell. 141 | .PP 142 | Unless \fB\-p\fR is used, stdin, stdout, stderr are inherited from the terminal and are 143 | not redirected. 144 | .PP 145 | If \fB\-e\fR is used, when a command exits unsuccessfully, 'repo forall' will abort 146 | without iterating through the remaining projects. 147 | -------------------------------------------------------------------------------- /man/repo-gc.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "April 2025" "repo gc" "Repo Manual" 3 | .SH NAME 4 | repo \- repo gc - manual page for repo gc 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,gc\/\fR 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Cleaning up internal repo and Git state. 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .TP 17 | \fB\-n\fR, \fB\-\-dry\-run\fR 18 | do everything except actually delete 19 | .TP 20 | \fB\-y\fR, \fB\-\-yes\fR 21 | answer yes to all safe prompts 22 | .TP 23 | \fB\-\-repack\fR 24 | repack all projects that use partial clone with 25 | filter=blob:none 26 | .SS Logging options: 27 | .TP 28 | \fB\-v\fR, \fB\-\-verbose\fR 29 | show all output 30 | .TP 31 | \fB\-q\fR, \fB\-\-quiet\fR 32 | only show errors 33 | .SS Multi\-manifest options: 34 | .TP 35 | \fB\-\-outer\-manifest\fR 36 | operate starting at the outermost manifest 37 | .TP 38 | \fB\-\-no\-outer\-manifest\fR 39 | do not operate on outer manifests 40 | .TP 41 | \fB\-\-this\-manifest\-only\fR 42 | only operate on this (sub)manifest 43 | .TP 44 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 45 | operate on this manifest and its submanifests 46 | .PP 47 | Run `repo help gc` to view the detailed manual. 48 | -------------------------------------------------------------------------------- /man/repo-grep.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo grep" "Repo Manual" 3 | .SH NAME 4 | repo \- repo grep - manual page for repo grep 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,grep {pattern | -e pattern} \/\fR[\fI\,\/\fR...] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Print lines matching a pattern 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .TP 17 | \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR 18 | number of jobs to run in parallel (default: based on 19 | number of CPU cores) 20 | .SS Logging options: 21 | .TP 22 | \fB\-\-verbose\fR 23 | show all output 24 | .TP 25 | \fB\-q\fR, \fB\-\-quiet\fR 26 | only show errors 27 | .SS Multi\-manifest options: 28 | .TP 29 | \fB\-\-outer\-manifest\fR 30 | operate starting at the outermost manifest 31 | .TP 32 | \fB\-\-no\-outer\-manifest\fR 33 | do not operate on outer manifests 34 | .TP 35 | \fB\-\-this\-manifest\-only\fR 36 | only operate on this (sub)manifest 37 | .TP 38 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 39 | operate on this manifest and its submanifests 40 | .SS Sources: 41 | .TP 42 | \fB\-\-cached\fR 43 | Search the index, instead of the work tree 44 | .TP 45 | \fB\-r\fR TREEish, \fB\-\-revision\fR=\fI\,TREEish\/\fR 46 | Search TREEish, instead of the work tree 47 | .SS Pattern: 48 | .TP 49 | \fB\-e\fR PATTERN 50 | Pattern to search for 51 | .TP 52 | \fB\-i\fR, \fB\-\-ignore\-case\fR 53 | Ignore case differences 54 | .TP 55 | \fB\-a\fR, \fB\-\-text\fR 56 | Process binary files as if they were text 57 | .TP 58 | \fB\-I\fR 59 | Don't match the pattern in binary files 60 | .TP 61 | \fB\-w\fR, \fB\-\-word\-regexp\fR 62 | Match the pattern only at word boundaries 63 | .TP 64 | \fB\-v\fR, \fB\-\-invert\-match\fR 65 | Select non\-matching lines 66 | .TP 67 | \fB\-G\fR, \fB\-\-basic\-regexp\fR 68 | Use POSIX basic regexp for patterns (default) 69 | .TP 70 | \fB\-E\fR, \fB\-\-extended\-regexp\fR 71 | Use POSIX extended regexp for patterns 72 | .TP 73 | \fB\-F\fR, \fB\-\-fixed\-strings\fR 74 | Use fixed strings (not regexp) for pattern 75 | .SS Pattern Grouping: 76 | .TP 77 | \fB\-\-all\-match\fR 78 | Limit match to lines that have all patterns 79 | .TP 80 | \fB\-\-and\fR, \fB\-\-or\fR, \fB\-\-not\fR 81 | Boolean operators to combine patterns 82 | .TP 83 | \-(, \-) 84 | Boolean operator grouping 85 | .SS Output: 86 | .TP 87 | \fB\-n\fR 88 | Prefix the line number to matching lines 89 | .TP 90 | \fB\-C\fR CONTEXT 91 | Show CONTEXT lines around match 92 | .TP 93 | \fB\-B\fR CONTEXT 94 | Show CONTEXT lines before match 95 | .TP 96 | \fB\-A\fR CONTEXT 97 | Show CONTEXT lines after match 98 | .TP 99 | \fB\-l\fR, \fB\-\-name\-only\fR, \fB\-\-files\-with\-matches\fR 100 | Show only file names containing matching lines 101 | .TP 102 | \fB\-L\fR, \fB\-\-files\-without\-match\fR 103 | Show only file names not containing matching lines 104 | .PP 105 | Run `repo help grep` to view the detailed manual. 106 | .SH DETAILS 107 | .PP 108 | Search for the specified patterns in all project files. 109 | .PP 110 | Boolean Options 111 | .PP 112 | The following options can appear as often as necessary to express the pattern to 113 | locate: 114 | .HP 115 | \fB\-e\fR PATTERN 116 | .HP 117 | \fB\-\-and\fR, \fB\-\-or\fR, \fB\-\-not\fR, \-(, \-) 118 | .PP 119 | Further, the \fB\-r\fR/\-\-revision option may be specified multiple times in order to 120 | scan multiple trees. If the same file matches in more than one tree, only the 121 | first result is reported, prefixed by the revision name it was found under. 122 | .PP 123 | Examples 124 | .PP 125 | Look for a line that has '#define' and either 'MAX_PATH or 'PATH_MAX': 126 | .IP 127 | repo grep \fB\-e\fR '#define' \fB\-\-and\fR \-\e( \fB\-e\fR MAX_PATH \fB\-e\fR PATH_MAX \e) 128 | .PP 129 | Look for a line that has 'NODE' or 'Unexpected' in files that contain a line 130 | that matches both expressions: 131 | .IP 132 | repo grep \fB\-\-all\-match\fR \fB\-e\fR NODE \fB\-e\fR Unexpected 133 | -------------------------------------------------------------------------------- /man/repo-help.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo help" "Repo Manual" 3 | .SH NAME 4 | repo \- repo help - manual page for repo help 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,help \/\fR[\fI\,--all|command\/\fR] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Display detailed help on a command 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .TP 17 | \fB\-a\fR, \fB\-\-all\fR 18 | show the complete list of commands 19 | .TP 20 | \fB\-\-help\-all\fR 21 | show the \fB\-\-help\fR of all commands 22 | .SS Logging options: 23 | .TP 24 | \fB\-v\fR, \fB\-\-verbose\fR 25 | show all output 26 | .TP 27 | \fB\-q\fR, \fB\-\-quiet\fR 28 | only show errors 29 | .SS Multi\-manifest options: 30 | .TP 31 | \fB\-\-outer\-manifest\fR 32 | operate starting at the outermost manifest 33 | .TP 34 | \fB\-\-no\-outer\-manifest\fR 35 | do not operate on outer manifests 36 | .TP 37 | \fB\-\-this\-manifest\-only\fR 38 | only operate on this (sub)manifest 39 | .TP 40 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 41 | operate on this manifest and its submanifests 42 | .PP 43 | Run `repo help help` to view the detailed manual. 44 | .SH DETAILS 45 | .PP 46 | Displays detailed usage information about a command. 47 | -------------------------------------------------------------------------------- /man/repo-info.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo info" "Repo Manual" 3 | .SH NAME 4 | repo \- repo info - manual page for repo info 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,info \/\fR[\fI\,-dl\/\fR] [\fI\,-o \/\fR[\fI\,-c\/\fR]] [\fI\,\/\fR...] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Get info on the manifest branch, current branch or unmerged branches 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .TP 17 | \fB\-d\fR, \fB\-\-diff\fR 18 | show full info and commit diff including remote 19 | branches 20 | .TP 21 | \fB\-o\fR, \fB\-\-overview\fR 22 | show overview of all local commits 23 | .TP 24 | \fB\-c\fR, \fB\-\-current\-branch\fR 25 | consider only checked out branches 26 | .TP 27 | \fB\-\-no\-current\-branch\fR 28 | consider all local branches 29 | .TP 30 | \fB\-l\fR, \fB\-\-local\-only\fR 31 | disable all remote operations 32 | .SS Logging options: 33 | .TP 34 | \fB\-v\fR, \fB\-\-verbose\fR 35 | show all output 36 | .TP 37 | \fB\-q\fR, \fB\-\-quiet\fR 38 | only show errors 39 | .SS Multi\-manifest options: 40 | .TP 41 | \fB\-\-outer\-manifest\fR 42 | operate starting at the outermost manifest 43 | .TP 44 | \fB\-\-no\-outer\-manifest\fR 45 | do not operate on outer manifests 46 | .TP 47 | \fB\-\-this\-manifest\-only\fR 48 | only operate on this (sub)manifest 49 | .TP 50 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 51 | operate on this manifest and its submanifests 52 | .PP 53 | Run `repo help info` to view the detailed manual. 54 | -------------------------------------------------------------------------------- /man/repo-list.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo list" "Repo Manual" 3 | .SH NAME 4 | repo \- repo list - manual page for repo list 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,list \/\fR[\fI\,-f\/\fR] [\fI\,\/\fR...] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | List projects and their associated directories 12 | .PP 13 | repo list [\-f] \fB\-r\fR str1 [str2]... 14 | .SH OPTIONS 15 | .TP 16 | \fB\-h\fR, \fB\-\-help\fR 17 | show this help message and exit 18 | .TP 19 | \fB\-r\fR, \fB\-\-regex\fR 20 | filter the project list based on regex or wildcard 21 | matching of strings 22 | .TP 23 | \fB\-g\fR GROUPS, \fB\-\-groups\fR=\fI\,GROUPS\/\fR 24 | filter the project list based on the groups the 25 | project is in 26 | .TP 27 | \fB\-a\fR, \fB\-\-all\fR 28 | show projects regardless of checkout state 29 | .TP 30 | \fB\-n\fR, \fB\-\-name\-only\fR 31 | display only the name of the repository 32 | .TP 33 | \fB\-p\fR, \fB\-\-path\-only\fR 34 | display only the path of the repository 35 | .TP 36 | \fB\-f\fR, \fB\-\-fullpath\fR 37 | display the full work tree path instead of the 38 | relative path 39 | .TP 40 | \fB\-\-relative\-to\fR=\fI\,PATH\/\fR 41 | display paths relative to this one (default: top of 42 | repo client checkout) 43 | .SS Logging options: 44 | .TP 45 | \fB\-v\fR, \fB\-\-verbose\fR 46 | show all output 47 | .TP 48 | \fB\-q\fR, \fB\-\-quiet\fR 49 | only show errors 50 | .SS Multi\-manifest options: 51 | .TP 52 | \fB\-\-outer\-manifest\fR 53 | operate starting at the outermost manifest 54 | .TP 55 | \fB\-\-no\-outer\-manifest\fR 56 | do not operate on outer manifests 57 | .TP 58 | \fB\-\-this\-manifest\-only\fR 59 | only operate on this (sub)manifest 60 | .TP 61 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 62 | operate on this manifest and its submanifests 63 | .PP 64 | Run `repo help list` to view the detailed manual. 65 | .SH DETAILS 66 | .PP 67 | List all projects; pass '.' to list the project for the cwd. 68 | .PP 69 | By default, only projects that currently exist in the checkout are shown. If you 70 | want to list all projects (using the specified filter settings), use the \fB\-\-all\fR 71 | option. If you want to show all projects regardless of the manifest groups, then 72 | also pass \fB\-\-groups\fR all. 73 | .PP 74 | This is similar to running: repo forall \fB\-c\fR 'echo "$REPO_PATH : $REPO_PROJECT"'. 75 | -------------------------------------------------------------------------------- /man/repo-overview.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo overview" "Repo Manual" 3 | .SH NAME 4 | repo \- repo overview - manual page for repo overview 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,overview \/\fR[\fI\,--current-branch\/\fR] [\fI\,\/\fR...] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Display overview of unmerged project branches 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .TP 17 | \fB\-c\fR, \fB\-\-current\-branch\fR 18 | consider only checked out branches 19 | .TP 20 | \fB\-\-no\-current\-branch\fR 21 | consider all local branches 22 | .SS Logging options: 23 | .TP 24 | \fB\-v\fR, \fB\-\-verbose\fR 25 | show all output 26 | .TP 27 | \fB\-q\fR, \fB\-\-quiet\fR 28 | only show errors 29 | .SS Multi\-manifest options: 30 | .TP 31 | \fB\-\-outer\-manifest\fR 32 | operate starting at the outermost manifest 33 | .TP 34 | \fB\-\-no\-outer\-manifest\fR 35 | do not operate on outer manifests 36 | .TP 37 | \fB\-\-this\-manifest\-only\fR 38 | only operate on this (sub)manifest 39 | .TP 40 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 41 | operate on this manifest and its submanifests 42 | .PP 43 | Run `repo help overview` to view the detailed manual. 44 | .SH DETAILS 45 | .PP 46 | The 'repo overview' command is used to display an overview of the projects 47 | branches, and list any local commits that have not yet been merged into the 48 | project. 49 | .PP 50 | The \fB\-c\fR/\-\-current\-branch option can be used to restrict the output to only 51 | branches currently checked out in each project. By default, all branches are 52 | displayed. 53 | -------------------------------------------------------------------------------- /man/repo-prune.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo prune" "Repo Manual" 3 | .SH NAME 4 | repo \- repo prune - manual page for repo prune 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,prune \/\fR[\fI\,\/\fR...] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Prune (delete) already merged topics 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .TP 17 | \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR 18 | number of jobs to run in parallel (default: based on 19 | number of CPU cores) 20 | .SS Logging options: 21 | .TP 22 | \fB\-v\fR, \fB\-\-verbose\fR 23 | show all output 24 | .TP 25 | \fB\-q\fR, \fB\-\-quiet\fR 26 | only show errors 27 | .SS Multi\-manifest options: 28 | .TP 29 | \fB\-\-outer\-manifest\fR 30 | operate starting at the outermost manifest 31 | .TP 32 | \fB\-\-no\-outer\-manifest\fR 33 | do not operate on outer manifests 34 | .TP 35 | \fB\-\-this\-manifest\-only\fR 36 | only operate on this (sub)manifest 37 | .TP 38 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 39 | operate on this manifest and its submanifests 40 | .PP 41 | Run `repo help prune` to view the detailed manual. 42 | -------------------------------------------------------------------------------- /man/repo-rebase.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo rebase" "Repo Manual" 3 | .SH NAME 4 | repo \- repo rebase - manual page for repo rebase 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,rebase {\/\fR[\fI\,\/\fR...] \fI\,| -i \/\fR...\fI\,}\/\fR 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Rebase local branches on upstream branch 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .TP 17 | \fB\-\-fail\-fast\fR 18 | stop rebasing after first error is hit 19 | .TP 20 | \fB\-f\fR, \fB\-\-force\-rebase\fR 21 | pass \fB\-\-force\-rebase\fR to git rebase 22 | .TP 23 | \fB\-\-no\-ff\fR 24 | pass \fB\-\-no\-ff\fR to git rebase 25 | .TP 26 | \fB\-\-autosquash\fR 27 | pass \fB\-\-autosquash\fR to git rebase 28 | .TP 29 | \fB\-\-whitespace\fR=\fI\,WS\/\fR 30 | pass \fB\-\-whitespace\fR to git rebase 31 | .TP 32 | \fB\-\-auto\-stash\fR 33 | stash local modifications before starting 34 | .TP 35 | \fB\-m\fR, \fB\-\-onto\-manifest\fR 36 | rebase onto the manifest version instead of upstream 37 | HEAD (this helps to make sure the local tree stays 38 | consistent if you previously synced to a manifest) 39 | .SS Logging options: 40 | .TP 41 | \fB\-v\fR, \fB\-\-verbose\fR 42 | show all output 43 | .TP 44 | \fB\-q\fR, \fB\-\-quiet\fR 45 | only show errors 46 | .TP 47 | \fB\-i\fR, \fB\-\-interactive\fR 48 | interactive rebase (single project only) 49 | .SS Multi\-manifest options: 50 | .TP 51 | \fB\-\-outer\-manifest\fR 52 | operate starting at the outermost manifest 53 | .TP 54 | \fB\-\-no\-outer\-manifest\fR 55 | do not operate on outer manifests 56 | .TP 57 | \fB\-\-this\-manifest\-only\fR 58 | only operate on this (sub)manifest 59 | .TP 60 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 61 | operate on this manifest and its submanifests 62 | .PP 63 | Run `repo help rebase` to view the detailed manual. 64 | .SH DETAILS 65 | .PP 66 | \&'repo rebase' uses git rebase to move local changes in the current topic branch 67 | to the HEAD of the upstream history, useful when you have made commits in a 68 | topic branch but need to incorporate new upstream changes "underneath" them. 69 | -------------------------------------------------------------------------------- /man/repo-selfupdate.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo selfupdate" "Repo Manual" 3 | .SH NAME 4 | repo \- repo selfupdate - manual page for repo selfupdate 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,selfupdate\/\fR 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Update repo to the latest version 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .SS Logging options: 17 | .TP 18 | \fB\-v\fR, \fB\-\-verbose\fR 19 | show all output 20 | .TP 21 | \fB\-q\fR, \fB\-\-quiet\fR 22 | only show errors 23 | .SS Multi\-manifest options: 24 | .TP 25 | \fB\-\-outer\-manifest\fR 26 | operate starting at the outermost manifest 27 | .TP 28 | \fB\-\-no\-outer\-manifest\fR 29 | do not operate on outer manifests 30 | .TP 31 | \fB\-\-this\-manifest\-only\fR 32 | only operate on this (sub)manifest 33 | .TP 34 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 35 | operate on this manifest and its submanifests 36 | .SS repo Version options: 37 | .TP 38 | \fB\-\-no\-repo\-verify\fR 39 | do not verify repo source code 40 | .PP 41 | Run `repo help selfupdate` to view the detailed manual. 42 | .SH DETAILS 43 | .PP 44 | The 'repo selfupdate' command upgrades repo to the latest version, if a newer 45 | version is available. 46 | .PP 47 | Normally this is done automatically by 'repo sync' and does not need to be 48 | performed by an end\-user. 49 | -------------------------------------------------------------------------------- /man/repo-smartsync.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "September 2024" "repo smartsync" "Repo Manual" 3 | .SH NAME 4 | repo \- repo smartsync - manual page for repo smartsync 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,smartsync \/\fR[\fI\,\/\fR...] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Update working tree to the latest known good revision 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .TP 17 | \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR 18 | number of jobs to run in parallel (default: based on 19 | number of CPU cores) 20 | .TP 21 | \fB\-\-jobs\-network\fR=\fI\,JOBS\/\fR 22 | number of network jobs to run in parallel (defaults to 23 | \fB\-\-jobs\fR or 1) 24 | .TP 25 | \fB\-\-jobs\-checkout\fR=\fI\,JOBS\/\fR 26 | number of local checkout jobs to run in parallel 27 | (defaults to \fB\-\-jobs\fR or 8) 28 | .TP 29 | \fB\-f\fR, \fB\-\-force\-broken\fR 30 | obsolete option (to be deleted in the future) 31 | .TP 32 | \fB\-\-fail\-fast\fR 33 | stop syncing after first error is hit 34 | .TP 35 | \fB\-\-force\-sync\fR 36 | overwrite an existing git directory if it needs to 37 | point to a different object directory. WARNING: this 38 | may cause loss of data 39 | .TP 40 | \fB\-\-force\-checkout\fR 41 | force checkout even if it results in throwing away 42 | uncommitted modifications. WARNING: this may cause 43 | loss of data 44 | .TP 45 | \fB\-\-force\-remove\-dirty\fR 46 | force remove projects with uncommitted modifications 47 | if projects no longer exist in the manifest. WARNING: 48 | this may cause loss of data 49 | .TP 50 | \fB\-\-rebase\fR 51 | rebase local commits regardless of whether they are 52 | published 53 | .TP 54 | \fB\-l\fR, \fB\-\-local\-only\fR 55 | only update working tree, don't fetch 56 | .TP 57 | \fB\-\-no\-manifest\-update\fR, \fB\-\-nmu\fR 58 | use the existing manifest checkout as\-is. (do not 59 | update to the latest revision) 60 | .TP 61 | \fB\-n\fR, \fB\-\-network\-only\fR 62 | fetch only, don't update working tree 63 | .TP 64 | \fB\-d\fR, \fB\-\-detach\fR 65 | detach projects back to manifest revision 66 | .TP 67 | \fB\-c\fR, \fB\-\-current\-branch\fR 68 | fetch only current branch from server 69 | .TP 70 | \fB\-\-no\-current\-branch\fR 71 | fetch all branches from server 72 | .TP 73 | \fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml 74 | temporary manifest to use for this sync 75 | .TP 76 | \fB\-\-clone\-bundle\fR 77 | enable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS 78 | .TP 79 | \fB\-\-no\-clone\-bundle\fR 80 | disable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS 81 | .TP 82 | \fB\-u\fR MANIFEST_SERVER_USERNAME, \fB\-\-manifest\-server\-username\fR=\fI\,MANIFEST_SERVER_USERNAME\/\fR 83 | username to authenticate with the manifest server 84 | .TP 85 | \fB\-p\fR MANIFEST_SERVER_PASSWORD, \fB\-\-manifest\-server\-password\fR=\fI\,MANIFEST_SERVER_PASSWORD\/\fR 86 | password to authenticate with the manifest server 87 | .TP 88 | \fB\-\-fetch\-submodules\fR 89 | fetch submodules from server 90 | .TP 91 | \fB\-\-use\-superproject\fR 92 | use the manifest superproject to sync projects; 93 | implies \fB\-c\fR 94 | .TP 95 | \fB\-\-no\-use\-superproject\fR 96 | disable use of manifest superprojects 97 | .TP 98 | \fB\-\-tags\fR 99 | fetch tags 100 | .TP 101 | \fB\-\-no\-tags\fR 102 | don't fetch tags (default) 103 | .TP 104 | \fB\-\-optimized\-fetch\fR 105 | only fetch projects fixed to sha1 if revision does not 106 | exist locally 107 | .TP 108 | \fB\-\-retry\-fetches\fR=\fI\,RETRY_FETCHES\/\fR 109 | number of times to retry fetches on transient errors 110 | .TP 111 | \fB\-\-prune\fR 112 | delete refs that no longer exist on the remote 113 | (default) 114 | .TP 115 | \fB\-\-no\-prune\fR 116 | do not delete refs that no longer exist on the remote 117 | .TP 118 | \fB\-\-auto\-gc\fR 119 | run garbage collection on all synced projects 120 | .TP 121 | \fB\-\-no\-auto\-gc\fR 122 | do not run garbage collection on any projects 123 | (default) 124 | .SS Logging options: 125 | .TP 126 | \fB\-v\fR, \fB\-\-verbose\fR 127 | show all output 128 | .TP 129 | \fB\-q\fR, \fB\-\-quiet\fR 130 | only show errors 131 | .SS Multi\-manifest options: 132 | .TP 133 | \fB\-\-outer\-manifest\fR 134 | operate starting at the outermost manifest 135 | .TP 136 | \fB\-\-no\-outer\-manifest\fR 137 | do not operate on outer manifests 138 | .TP 139 | \fB\-\-this\-manifest\-only\fR 140 | only operate on this (sub)manifest 141 | .TP 142 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 143 | operate on this manifest and its submanifests 144 | .SS repo Version options: 145 | .TP 146 | \fB\-\-no\-repo\-verify\fR 147 | do not verify repo source code 148 | .PP 149 | Run `repo help smartsync` to view the detailed manual. 150 | .SH DETAILS 151 | .PP 152 | The 'repo smartsync' command is a shortcut for sync \fB\-s\fR. 153 | -------------------------------------------------------------------------------- /man/repo-stage.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo stage" "Repo Manual" 3 | .SH NAME 4 | repo \- repo stage - manual page for repo stage 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,stage -i \/\fR[\fI\,\/\fR...] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Stage file(s) for commit 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .SS Logging options: 17 | .TP 18 | \fB\-v\fR, \fB\-\-verbose\fR 19 | show all output 20 | .TP 21 | \fB\-q\fR, \fB\-\-quiet\fR 22 | only show errors 23 | .TP 24 | \fB\-i\fR, \fB\-\-interactive\fR 25 | use interactive staging 26 | .SS Multi\-manifest options: 27 | .TP 28 | \fB\-\-outer\-manifest\fR 29 | operate starting at the outermost manifest 30 | .TP 31 | \fB\-\-no\-outer\-manifest\fR 32 | do not operate on outer manifests 33 | .TP 34 | \fB\-\-this\-manifest\-only\fR 35 | only operate on this (sub)manifest 36 | .TP 37 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 38 | operate on this manifest and its submanifests 39 | .PP 40 | Run `repo help stage` to view the detailed manual. 41 | .SH DETAILS 42 | .PP 43 | The 'repo stage' command stages files to prepare the next commit. 44 | -------------------------------------------------------------------------------- /man/repo-start.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo start" "Repo Manual" 3 | .SH NAME 4 | repo \- repo start - manual page for repo start 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,start \/\fR[\fI\,--all | \/\fR...] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Start a new branch for development 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .TP 17 | \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR 18 | number of jobs to run in parallel (default: based on 19 | number of CPU cores) 20 | .TP 21 | \fB\-\-all\fR 22 | begin branch in all projects 23 | .TP 24 | \fB\-r\fR REVISION, \fB\-\-rev\fR=\fI\,REVISION\/\fR, \fB\-\-revision\fR=\fI\,REVISION\/\fR 25 | point branch at this revision instead of upstream 26 | .TP 27 | \fB\-\-head\fR, \fB\-\-HEAD\fR 28 | abbreviation for \fB\-\-rev\fR HEAD 29 | .SS Logging options: 30 | .TP 31 | \fB\-v\fR, \fB\-\-verbose\fR 32 | show all output 33 | .TP 34 | \fB\-q\fR, \fB\-\-quiet\fR 35 | only show errors 36 | .SS Multi\-manifest options: 37 | .TP 38 | \fB\-\-outer\-manifest\fR 39 | operate starting at the outermost manifest 40 | .TP 41 | \fB\-\-no\-outer\-manifest\fR 42 | do not operate on outer manifests 43 | .TP 44 | \fB\-\-this\-manifest\-only\fR 45 | only operate on this (sub)manifest 46 | .TP 47 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 48 | operate on this manifest and its submanifests 49 | .PP 50 | Run `repo help start` to view the detailed manual. 51 | .SH DETAILS 52 | .PP 53 | \&'repo start' begins a new branch of development, starting from the revision 54 | specified in the manifest. 55 | -------------------------------------------------------------------------------- /man/repo-status.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo status" "Repo Manual" 3 | .SH NAME 4 | repo \- repo status - manual page for repo status 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,status \/\fR[\fI\,\/\fR...] 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Show the working tree status 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .TP 17 | \fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR 18 | number of jobs to run in parallel (default: based on 19 | number of CPU cores) 20 | .TP 21 | \fB\-o\fR, \fB\-\-orphans\fR 22 | include objects in working directory outside of repo 23 | projects 24 | .SS Logging options: 25 | .TP 26 | \fB\-v\fR, \fB\-\-verbose\fR 27 | show all output 28 | .TP 29 | \fB\-q\fR, \fB\-\-quiet\fR 30 | only show errors 31 | .SS Multi\-manifest options: 32 | .TP 33 | \fB\-\-outer\-manifest\fR 34 | operate starting at the outermost manifest 35 | .TP 36 | \fB\-\-no\-outer\-manifest\fR 37 | do not operate on outer manifests 38 | .TP 39 | \fB\-\-this\-manifest\-only\fR 40 | only operate on this (sub)manifest 41 | .TP 42 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 43 | operate on this manifest and its submanifests 44 | .PP 45 | Run `repo help status` to view the detailed manual. 46 | .SH DETAILS 47 | .PP 48 | \&'repo status' compares the working tree to the staging area (aka index), and the 49 | most recent commit on this branch (HEAD), in each project specified. A summary 50 | is displayed, one line per file where there is a difference between these three 51 | states. 52 | .PP 53 | The \fB\-j\fR/\-\-jobs option can be used to run multiple status queries in parallel. 54 | .PP 55 | The \fB\-o\fR/\-\-orphans option can be used to show objects that are in the working 56 | directory, but not associated with a repo project. This includes unmanaged 57 | top\-level files and directories, but also includes deeper items. For example, if 58 | dir/subdir/proj1 and dir/subdir/proj2 are repo projects, dir/subdir/proj3 will 59 | be shown if it is not known to repo. 60 | .PP 61 | Status Display 62 | .PP 63 | The status display is organized into three columns of information, for example 64 | if the file 'subcmds/status.py' is modified in the project 'repo' on branch 65 | \&'devwork': 66 | .TP 67 | project repo/ 68 | branch devwork 69 | .TP 70 | \fB\-m\fR 71 | subcmds/status.py 72 | .PP 73 | The first column explains how the staging area (index) differs from the last 74 | commit (HEAD). Its values are always displayed in upper case and have the 75 | following meanings: 76 | .TP 77 | \-: 78 | no difference 79 | .TP 80 | A: 81 | added (not in HEAD, in index ) 82 | .TP 83 | M: 84 | modified ( in HEAD, in index, different content ) 85 | .TP 86 | D: 87 | deleted ( in HEAD, not in index ) 88 | .TP 89 | R: 90 | renamed (not in HEAD, in index, path changed ) 91 | .TP 92 | C: 93 | copied (not in HEAD, in index, copied from another) 94 | .TP 95 | T: 96 | mode changed ( in HEAD, in index, same content ) 97 | .TP 98 | U: 99 | unmerged; conflict resolution required 100 | .PP 101 | The second column explains how the working directory differs from the index. Its 102 | values are always displayed in lower case and have the following meanings: 103 | .TP 104 | \-: 105 | new / unknown (not in index, in work tree ) 106 | .TP 107 | m: 108 | modified ( in index, in work tree, modified ) 109 | .TP 110 | d: 111 | deleted ( in index, not in work tree ) 112 | -------------------------------------------------------------------------------- /man/repo-version.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "July 2022" "repo version" "Repo Manual" 3 | .SH NAME 4 | repo \- repo version - manual page for repo version 5 | .SH SYNOPSIS 6 | .B repo 7 | \fI\,version\/\fR 8 | .SH DESCRIPTION 9 | Summary 10 | .PP 11 | Display the version of repo 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | show this help message and exit 16 | .SS Logging options: 17 | .TP 18 | \fB\-v\fR, \fB\-\-verbose\fR 19 | show all output 20 | .TP 21 | \fB\-q\fR, \fB\-\-quiet\fR 22 | only show errors 23 | .SS Multi\-manifest options: 24 | .TP 25 | \fB\-\-outer\-manifest\fR 26 | operate starting at the outermost manifest 27 | .TP 28 | \fB\-\-no\-outer\-manifest\fR 29 | do not operate on outer manifests 30 | .TP 31 | \fB\-\-this\-manifest\-only\fR 32 | only operate on this (sub)manifest 33 | .TP 34 | \fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR 35 | operate on this manifest and its submanifests 36 | .PP 37 | Run `repo help version` to view the detailed manual. 38 | -------------------------------------------------------------------------------- /man/repo.1: -------------------------------------------------------------------------------- 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. 2 | .TH REPO "1" "April 2025" "repo" "Repo Manual" 3 | .SH NAME 4 | repo \- repository management tool built on top of git 5 | .SH SYNOPSIS 6 | .B repo 7 | [\fI\,-p|--paginate|--no-pager\/\fR] \fI\,COMMAND \/\fR[\fI\,ARGS\/\fR] 8 | .SH OPTIONS 9 | .TP 10 | \fB\-h\fR, \fB\-\-help\fR 11 | show this help message and exit 12 | .TP 13 | \fB\-\-help\-all\fR 14 | show this help message with all subcommands and exit 15 | .TP 16 | \fB\-p\fR, \fB\-\-paginate\fR 17 | display command output in the pager 18 | .TP 19 | \fB\-\-no\-pager\fR 20 | disable the pager 21 | .TP 22 | \fB\-\-color\fR=\fI\,COLOR\/\fR 23 | control color usage: auto, always, never 24 | .TP 25 | \fB\-\-trace\fR 26 | trace git command execution (REPO_TRACE=1) 27 | .TP 28 | \fB\-\-trace\-to\-stderr\fR 29 | trace outputs go to stderr in addition to 30 | \&.repo/TRACE_FILE 31 | .TP 32 | \fB\-\-trace\-python\fR 33 | trace python command execution 34 | .TP 35 | \fB\-\-time\fR 36 | time repo command execution 37 | .TP 38 | \fB\-\-version\fR 39 | display this version of repo 40 | .TP 41 | \fB\-\-show\-toplevel\fR 42 | display the path of the top\-level directory of the 43 | repo client checkout 44 | .TP 45 | \fB\-\-event\-log\fR=\fI\,EVENT_LOG\/\fR 46 | filename of event log to append timeline to 47 | .TP 48 | \fB\-\-git\-trace2\-event\-log\fR=\fI\,GIT_TRACE2_EVENT_LOG\/\fR 49 | directory to write git trace2 event log to 50 | .TP 51 | \fB\-\-submanifest\-path\fR=\fI\,REL_PATH\/\fR 52 | submanifest path 53 | .SS "The complete list of recognized repo commands is:" 54 | .TP 55 | abandon 56 | Permanently abandon a development branch 57 | .TP 58 | branch 59 | View current topic branches 60 | .TP 61 | branches 62 | View current topic branches 63 | .TP 64 | checkout 65 | Checkout a branch for development 66 | .TP 67 | cherry\-pick 68 | Cherry\-pick a change. 69 | .TP 70 | diff 71 | Show changes between commit and working tree 72 | .TP 73 | diffmanifests 74 | Manifest diff utility 75 | .TP 76 | download 77 | Download and checkout a change 78 | .TP 79 | forall 80 | Run a shell command in each project 81 | .TP 82 | gc 83 | Cleaning up internal repo and Git state. 84 | .TP 85 | grep 86 | Print lines matching a pattern 87 | .TP 88 | help 89 | Display detailed help on a command 90 | .TP 91 | info 92 | Get info on the manifest branch, current branch or unmerged branches 93 | .TP 94 | init 95 | Initialize a repo client checkout in the current directory 96 | .TP 97 | list 98 | List projects and their associated directories 99 | .TP 100 | manifest 101 | Manifest inspection utility 102 | .TP 103 | overview 104 | Display overview of unmerged project branches 105 | .TP 106 | prune 107 | Prune (delete) already merged topics 108 | .TP 109 | rebase 110 | Rebase local branches on upstream branch 111 | .TP 112 | selfupdate 113 | Update repo to the latest version 114 | .TP 115 | smartsync 116 | Update working tree to the latest known good revision 117 | .TP 118 | stage 119 | Stage file(s) for commit 120 | .TP 121 | start 122 | Start a new branch for development 123 | .TP 124 | status 125 | Show the working tree status 126 | .TP 127 | sync 128 | Update working tree to the latest revision 129 | .TP 130 | upload 131 | Upload changes for code review 132 | .TP 133 | version 134 | Display the version of repo 135 | .PP 136 | See 'repo help ' for more information on a specific command. 137 | Bug reports: https://issues.gerritcodereview.com/issues/new?component=1370071 138 | -------------------------------------------------------------------------------- /pager.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2008 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import select 17 | import subprocess 18 | import sys 19 | 20 | import platform_utils 21 | 22 | 23 | active = False 24 | pager_process = None 25 | old_stdout = None 26 | old_stderr = None 27 | 28 | 29 | def RunPager(globalConfig): 30 | if not os.isatty(0) or not os.isatty(1): 31 | return 32 | pager = _SelectPager(globalConfig) 33 | if pager == "" or pager == "cat": 34 | return 35 | 36 | if platform_utils.isWindows(): 37 | _PipePager(pager) 38 | else: 39 | _ForkPager(pager) 40 | 41 | 42 | def TerminatePager(): 43 | global pager_process 44 | if pager_process: 45 | sys.stdout.flush() 46 | sys.stderr.flush() 47 | pager_process.stdin.close() 48 | pager_process.wait() 49 | pager_process = None 50 | # Restore initial stdout/err in case there is more output in this 51 | # process after shutting down the pager process. 52 | sys.stdout = old_stdout 53 | sys.stderr = old_stderr 54 | 55 | 56 | def _PipePager(pager): 57 | global pager_process, old_stdout, old_stderr 58 | assert pager_process is None, "Only one active pager process at a time" 59 | # Create pager process, piping stdout/err into its stdin. 60 | try: 61 | pager_process = subprocess.Popen( 62 | [pager], stdin=subprocess.PIPE, stdout=sys.stdout, stderr=sys.stderr 63 | ) 64 | except FileNotFoundError: 65 | sys.exit(f'fatal: cannot start pager "{pager}"') 66 | old_stdout = sys.stdout 67 | old_stderr = sys.stderr 68 | sys.stdout = pager_process.stdin 69 | sys.stderr = pager_process.stdin 70 | 71 | 72 | def _ForkPager(pager): 73 | global active 74 | # This process turns into the pager; a child it forks will 75 | # do the real processing and output back to the pager. This 76 | # is necessary to keep the pager in control of the tty. 77 | try: 78 | r, w = os.pipe() 79 | pid = os.fork() 80 | if not pid: 81 | os.dup2(w, 1) 82 | os.dup2(w, 2) 83 | os.close(r) 84 | os.close(w) 85 | active = True 86 | return 87 | 88 | os.dup2(r, 0) 89 | os.close(r) 90 | os.close(w) 91 | 92 | _BecomePager(pager) 93 | except Exception: 94 | print("fatal: cannot start pager '%s'" % pager, file=sys.stderr) 95 | sys.exit(255) 96 | 97 | 98 | def _SelectPager(globalConfig): 99 | try: 100 | return os.environ["GIT_PAGER"] 101 | except KeyError: 102 | pass 103 | 104 | pager = globalConfig.GetString("core.pager") 105 | if pager: 106 | return pager 107 | 108 | try: 109 | return os.environ["PAGER"] 110 | except KeyError: 111 | pass 112 | 113 | return "less" 114 | 115 | 116 | def _BecomePager(pager): 117 | # Delaying execution of the pager until we have output 118 | # ready works around a long-standing bug in popularly 119 | # available versions of 'less', a better 'more'. 120 | _a, _b, _c = select.select([0], [], [0]) 121 | 122 | # This matches the behavior of git, which sets $LESS to `FRX` if it is not 123 | # set. See: 124 | # https://git-scm.com/docs/git-config#Documentation/git-config.txt-corepager 125 | os.environ.setdefault("LESS", "FRX") 126 | 127 | try: 128 | os.execvp(pager, [pager]) 129 | except OSError: 130 | os.execv("/bin/sh", ["sh", "-c", pager]) 131 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | [tool.black] 16 | line-length = 80 17 | # NB: Keep in sync with tox.ini. 18 | target-version = ['py36', 'py37', 'py38', 'py39', 'py310', 'py311'] #, 'py312' 19 | 20 | [tool.pytest.ini_options] 21 | markers = """ 22 | skip_cq: Skip tests in the CQ. Should be rarely used! 23 | """ 24 | -------------------------------------------------------------------------------- /release/README.md: -------------------------------------------------------------------------------- 1 | These are helper tools for managing official releases. 2 | See the [release process](../docs/release-process.md) document for more details. 3 | -------------------------------------------------------------------------------- /release/sign-tag.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (C) 2020 The Android Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Helper tool for signing repo release tags correctly. 17 | 18 | This is intended to be run only by the official Repo release managers, but it 19 | could be run by people maintaining their own fork of the project. 20 | 21 | NB: Check docs/release-process.md for production freeze information. 22 | """ 23 | 24 | import argparse 25 | import os 26 | import re 27 | import subprocess 28 | import sys 29 | 30 | import util 31 | 32 | 33 | # We currently sign with the old DSA key as it's been around the longest. 34 | # We should transition to RSA by Jun 2020, and ECC by Jun 2021. 35 | KEYID = util.KEYID_DSA 36 | 37 | # Regular expression to validate tag names. 38 | RE_VALID_TAG = r"^v([0-9]+[.])+[0-9]+$" 39 | 40 | 41 | def sign(opts): 42 | """Tag the commit & sign it!""" 43 | # We use ! at the end of the key so that gpg uses this specific key. 44 | # Otherwise it uses the key as a lookup into the overall key and uses the 45 | # default signing key. i.e. It will see that KEYID_RSA is a subkey of 46 | # another key, and use the primary key to sign instead of the subkey. 47 | cmd = [ 48 | "git", 49 | "tag", 50 | "-s", 51 | opts.tag, 52 | "-u", 53 | f"{opts.key}!", 54 | "-m", 55 | f"repo {opts.tag}", 56 | opts.commit, 57 | ] 58 | 59 | key = "GNUPGHOME" 60 | print("+", f'export {key}="{opts.gpgdir}"') 61 | oldvalue = os.getenv(key) 62 | os.putenv(key, opts.gpgdir) 63 | util.run(opts, cmd) 64 | if oldvalue is None: 65 | os.unsetenv(key) 66 | else: 67 | os.putenv(key, oldvalue) 68 | 69 | 70 | def check(opts): 71 | """Check the signature.""" 72 | util.run(opts, ["git", "tag", "--verify", opts.tag]) 73 | 74 | 75 | def postmsg(opts): 76 | """Helpful info to show at the end for release manager.""" 77 | cmd = ["git", "rev-parse", "remotes/origin/stable"] 78 | ret = util.run(opts, cmd, encoding="utf-8", stdout=subprocess.PIPE) 79 | current_release = ret.stdout.strip() 80 | 81 | cmd = [ 82 | "git", 83 | "log", 84 | "--format=%h (%aN) %s", 85 | "--no-merges", 86 | f"remotes/origin/stable..{opts.tag}", 87 | ] 88 | ret = util.run(opts, cmd, encoding="utf-8", stdout=subprocess.PIPE) 89 | shortlog = ret.stdout.strip() 90 | 91 | print( 92 | f""" 93 | Here's the short log since the last release. 94 | {shortlog} 95 | 96 | To push release to the public: 97 | git push origin {opts.commit}:stable {opts.tag} -n 98 | NB: People will start upgrading to this version immediately. 99 | 100 | To roll back a release: 101 | git push origin --force {current_release}:stable -n 102 | """ 103 | ) 104 | 105 | 106 | def get_parser(): 107 | """Get a CLI parser.""" 108 | parser = argparse.ArgumentParser( 109 | description=__doc__, 110 | formatter_class=argparse.RawDescriptionHelpFormatter, 111 | ) 112 | parser.add_argument( 113 | "-n", 114 | "--dry-run", 115 | dest="dryrun", 116 | action="store_true", 117 | help="show everything that would be done", 118 | ) 119 | parser.add_argument( 120 | "--gpgdir", 121 | default=os.path.join(util.HOMEDIR, ".gnupg", "repo"), 122 | help="path to dedicated gpg dir with release keys " 123 | "(default: ~/.gnupg/repo/)", 124 | ) 125 | parser.add_argument( 126 | "-f", "--force", action="store_true", help="force signing of any tag" 127 | ) 128 | parser.add_argument( 129 | "--keyid", dest="key", help="alternative signing key to use" 130 | ) 131 | parser.add_argument("tag", help='the tag to create (e.g. "v2.0")') 132 | parser.add_argument( 133 | "commit", default="HEAD", nargs="?", help="the commit to tag" 134 | ) 135 | return parser 136 | 137 | 138 | def main(argv): 139 | """The main func!""" 140 | parser = get_parser() 141 | opts = parser.parse_args(argv) 142 | 143 | if not os.path.exists(opts.gpgdir): 144 | parser.error(f"--gpgdir does not exist: {opts.gpgdir}") 145 | 146 | if not opts.force and not re.match(RE_VALID_TAG, opts.tag): 147 | parser.error( 148 | f'tag "{opts.tag}" does not match regex "{RE_VALID_TAG}"; ' 149 | "use --force to sign anyways" 150 | ) 151 | 152 | if opts.key: 153 | print(f"Using custom key to sign: {opts.key}") 154 | else: 155 | print("Using official Repo release key to sign") 156 | opts.key = KEYID 157 | util.import_release_key(opts) 158 | 159 | sign(opts) 160 | check(opts) 161 | postmsg(opts) 162 | 163 | return 0 164 | 165 | 166 | if __name__ == "__main__": 167 | sys.exit(main(sys.argv[1:])) 168 | -------------------------------------------------------------------------------- /release/update-hooks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (C) 2024 The Android Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Helper tool for updating hooks from their various upstreams.""" 17 | 18 | import argparse 19 | import base64 20 | import json 21 | from pathlib import Path 22 | import sys 23 | from typing import List, Optional 24 | import urllib.request 25 | 26 | 27 | assert sys.version_info >= (3, 8), "Python 3.8+ required" 28 | 29 | 30 | TOPDIR = Path(__file__).resolve().parent.parent 31 | HOOKS_DIR = TOPDIR / "hooks" 32 | 33 | 34 | def update_hook_commit_msg() -> None: 35 | """Update commit-msg hook from Gerrit.""" 36 | hook = HOOKS_DIR / "commit-msg" 37 | print( 38 | f"{hook.name}: Updating from https://gerrit.googlesource.com/gerrit/" 39 | "+/HEAD/resources/com/google/gerrit/server/tools/root/hooks/commit-msg" 40 | ) 41 | 42 | # Get the current commit. 43 | url = "https://gerrit.googlesource.com/gerrit/+/HEAD?format=JSON" 44 | with urllib.request.urlopen(url) as fp: 45 | data = fp.read() 46 | # Discard the xss protection. 47 | data = data.split(b"\n", 1)[1] 48 | data = json.loads(data) 49 | commit = data["commit"] 50 | 51 | # Fetch the data for that commit. 52 | url = ( 53 | f"https://gerrit.googlesource.com/gerrit/+/{commit}/" 54 | "resources/com/google/gerrit/server/tools/root/hooks/commit-msg" 55 | ) 56 | with urllib.request.urlopen(f"{url}?format=TEXT") as fp: 57 | data = fp.read() 58 | 59 | # gitiles base64 encodes text data. 60 | data = base64.b64decode(data) 61 | 62 | # Inject header into the hook. 63 | lines = data.split(b"\n") 64 | lines = ( 65 | lines[:1] 66 | + [ 67 | b"# DO NOT EDIT THIS FILE", 68 | ( 69 | b"# All updates should be sent upstream: " 70 | b"https://gerrit.googlesource.com/gerrit/" 71 | ), 72 | f"# This is synced from commit: {commit}".encode("utf-8"), 73 | b"# DO NOT EDIT THIS FILE", 74 | ] 75 | + lines[1:] 76 | ) 77 | data = b"\n".join(lines) 78 | 79 | # Update the hook. 80 | hook.write_bytes(data) 81 | hook.chmod(0o755) 82 | 83 | 84 | def update_hook_pre_auto_gc() -> None: 85 | """Update pre-auto-gc hook from git.""" 86 | hook = HOOKS_DIR / "pre-auto-gc" 87 | print( 88 | f"{hook.name}: Updating from https://github.com/git/git/" 89 | "HEAD/contrib/hooks/pre-auto-gc-battery" 90 | ) 91 | 92 | # Get the current commit. 93 | headers = { 94 | "Accept": "application/vnd.github+json", 95 | "X-GitHub-Api-Version": "2022-11-28", 96 | } 97 | url = "https://api.github.com/repos/git/git/git/refs/heads/master" 98 | req = urllib.request.Request(url, headers=headers) 99 | with urllib.request.urlopen(req) as fp: 100 | data = fp.read() 101 | data = json.loads(data) 102 | 103 | # Fetch the data for that commit. 104 | commit = data["object"]["sha"] 105 | url = ( 106 | f"https://raw.githubusercontent.com/git/git/{commit}/" 107 | "contrib/hooks/pre-auto-gc-battery" 108 | ) 109 | with urllib.request.urlopen(url) as fp: 110 | data = fp.read() 111 | 112 | # Inject header into the hook. 113 | lines = data.split(b"\n") 114 | lines = ( 115 | lines[:1] 116 | + [ 117 | b"# DO NOT EDIT THIS FILE", 118 | ( 119 | b"# All updates should be sent upstream: " 120 | b"https://github.com/git/git/" 121 | ), 122 | f"# This is synced from commit: {commit}".encode("utf-8"), 123 | b"# DO NOT EDIT THIS FILE", 124 | ] 125 | + lines[1:] 126 | ) 127 | data = b"\n".join(lines) 128 | 129 | # Update the hook. 130 | hook.write_bytes(data) 131 | hook.chmod(0o755) 132 | 133 | 134 | def main(argv: Optional[List[str]] = None) -> Optional[int]: 135 | parser = argparse.ArgumentParser(description=__doc__) 136 | parser.parse_args(argv) 137 | 138 | update_hook_commit_msg() 139 | update_hook_pre_auto_gc() 140 | 141 | 142 | if __name__ == "__main__": 143 | sys.exit(main(sys.argv[1:])) 144 | -------------------------------------------------------------------------------- /release/update-manpages: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (C) 2021 The Android Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Helper tool for generating manual page for all repo commands. 17 | 18 | This is intended to be run before every official Repo release. 19 | """ 20 | 21 | import sys 22 | 23 | import update_manpages 24 | 25 | 26 | sys.exit(update_manpages.main(sys.argv[1:])) 27 | -------------------------------------------------------------------------------- /release/util.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Random utility code for release tools.""" 16 | 17 | import os 18 | import re 19 | import shlex 20 | import subprocess 21 | import sys 22 | 23 | 24 | assert sys.version_info >= (3, 6), "This module requires Python 3.6+" 25 | 26 | 27 | TOPDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 28 | HOMEDIR = os.path.expanduser("~") 29 | 30 | 31 | # These are the release keys we sign with. 32 | KEYID_DSA = "8BB9AD793E8E6153AF0F9A4416530D5E920F5C65" 33 | KEYID_RSA = "A34A13BE8E76BFF46A0C022DA2E75A824AAB9624" 34 | KEYID_ECC = "E1F9040D7A3F6DAFAC897CD3D3B95DA243E48A39" 35 | 36 | 37 | def cmdstr(cmd): 38 | """Get a nicely quoted shell command.""" 39 | return " ".join(shlex.quote(x) for x in cmd) 40 | 41 | 42 | def run(opts, cmd, check=True, **kwargs): 43 | """Helper around subprocess.run to include logging.""" 44 | print("+", cmdstr(cmd)) 45 | if opts.dryrun: 46 | cmd = ["true", "--"] + cmd 47 | try: 48 | return subprocess.run(cmd, check=check, **kwargs) 49 | except subprocess.CalledProcessError as e: 50 | print(f"aborting: {e}", file=sys.stderr) 51 | sys.exit(1) 52 | 53 | 54 | def import_release_key(opts): 55 | """Import the public key of the official release repo signing key.""" 56 | # Extract the key from our repo launcher. 57 | launcher = getattr(opts, "launcher", os.path.join(TOPDIR, "repo")) 58 | print(f'Importing keys from "{launcher}" launcher script') 59 | with open(launcher, encoding="utf-8") as fp: 60 | data = fp.read() 61 | 62 | keys = re.findall( 63 | r"\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n[^-]*" 64 | r"\n-----END PGP PUBLIC KEY BLOCK-----\n", 65 | data, 66 | flags=re.M, 67 | ) 68 | run(opts, ["gpg", "--import"], input="\n".join(keys).encode("utf-8")) 69 | 70 | print("Marking keys as fully trusted") 71 | run( 72 | opts, 73 | ["gpg", "--import-ownertrust"], 74 | input=f"{KEYID_DSA}:6:\n".encode("utf-8"), 75 | ) 76 | -------------------------------------------------------------------------------- /repo_logging.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Logic for printing user-friendly logs in repo.""" 16 | 17 | import logging 18 | 19 | from color import Coloring 20 | from error import RepoExitError 21 | 22 | 23 | SEPARATOR = "=" * 80 24 | MAX_PRINT_ERRORS = 5 25 | 26 | 27 | class _ConfigMock: 28 | """Default coloring config to use when Logging.config is not set.""" 29 | 30 | def __init__(self): 31 | self.default_values = {"color.ui": "auto"} 32 | 33 | def GetString(self, x): 34 | return self.default_values.get(x, None) 35 | 36 | 37 | class _LogColoring(Coloring): 38 | """Coloring outstream for logging.""" 39 | 40 | def __init__(self, config): 41 | super().__init__(config, "logs") 42 | self.error = self.nofmt_colorer("error", fg="red") 43 | self.warning = self.nofmt_colorer("warn", fg="yellow") 44 | self.levelMap = { 45 | "WARNING": self.warning, 46 | "ERROR": self.error, 47 | } 48 | 49 | 50 | class _LogColoringFormatter(logging.Formatter): 51 | """Coloring formatter for logging.""" 52 | 53 | def __init__(self, config=None, *args, **kwargs): 54 | self.config = config if config else _ConfigMock() 55 | self.colorer = _LogColoring(self.config) 56 | super().__init__(*args, **kwargs) 57 | 58 | def format(self, record): 59 | """Formats |record| with color.""" 60 | msg = super().format(record) 61 | colorer = self.colorer.levelMap.get(record.levelname) 62 | return msg if not colorer else colorer(msg) 63 | 64 | 65 | class RepoLogger(logging.Logger): 66 | """Repo Logging Module.""" 67 | 68 | def __init__(self, name: str, config=None, **kwargs): 69 | super().__init__(name, **kwargs) 70 | handler = logging.StreamHandler() 71 | handler.setFormatter(_LogColoringFormatter(config)) 72 | self.addHandler(handler) 73 | 74 | def log_aggregated_errors(self, err: RepoExitError): 75 | """Print all aggregated logs.""" 76 | self.error(SEPARATOR) 77 | 78 | if not err.aggregate_errors: 79 | self.error("Repo command failed: %s", type(err).__name__) 80 | self.error("\t%s", str(err)) 81 | return 82 | 83 | self.error( 84 | "Repo command failed due to the following `%s` errors:", 85 | type(err).__name__, 86 | ) 87 | self.error( 88 | "\n".join(str(e) for e in err.aggregate_errors[:MAX_PRINT_ERRORS]) 89 | ) 90 | 91 | diff = len(err.aggregate_errors) - MAX_PRINT_ERRORS 92 | if diff > 0: 93 | self.error("+%d additional errors...", diff) 94 | -------------------------------------------------------------------------------- /repo_trace.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2008 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Logic for tracing repo interactions. 16 | 17 | Activated via `repo --trace ...` or `REPO_TRACE=1 repo ...`. 18 | 19 | Temporary: Tracing is always on. Set `REPO_TRACE=0` to turn off. 20 | To also include trace outputs in stderr do `repo --trace_to_stderr ...` 21 | """ 22 | 23 | import contextlib 24 | import os 25 | import sys 26 | import tempfile 27 | import time 28 | 29 | import platform_utils 30 | 31 | 32 | # Env var to implicitly turn on tracing. 33 | REPO_TRACE = "REPO_TRACE" 34 | 35 | # Temporarily set tracing to always on unless user expicitly sets to 0. 36 | _TRACE = os.environ.get(REPO_TRACE) != "0" 37 | _TRACE_TO_STDERR = False 38 | _TRACE_FILE = None 39 | _TRACE_FILE_NAME = "TRACE_FILE" 40 | _MAX_SIZE = 70 # in MiB 41 | _NEW_COMMAND_SEP = "+++++++++++++++NEW COMMAND+++++++++++++++++++" 42 | 43 | 44 | def IsTraceToStderr(): 45 | """Whether traces are written to stderr.""" 46 | return _TRACE_TO_STDERR 47 | 48 | 49 | def IsTrace(): 50 | """Whether tracing is enabled.""" 51 | return _TRACE 52 | 53 | 54 | def SetTraceToStderr(): 55 | """Enables tracing logging to stderr.""" 56 | global _TRACE_TO_STDERR 57 | _TRACE_TO_STDERR = True 58 | 59 | 60 | def SetTrace(): 61 | """Enables tracing.""" 62 | global _TRACE 63 | _TRACE = True 64 | 65 | 66 | def _SetTraceFile(quiet): 67 | """Sets the trace file location.""" 68 | global _TRACE_FILE 69 | _TRACE_FILE = _GetTraceFile(quiet) 70 | 71 | 72 | class Trace(contextlib.ContextDecorator): 73 | """Used to capture and save git traces.""" 74 | 75 | def _time(self): 76 | """Generate nanoseconds of time in a py3.6 safe way""" 77 | return int(time.time() * 1e9) 78 | 79 | def __init__(self, fmt, *args, first_trace=False, quiet=True): 80 | """Initialize the object. 81 | 82 | Args: 83 | fmt: The format string for the trace. 84 | *args: Arguments to pass to formatting. 85 | first_trace: Whether this is the first trace of a `repo` invocation. 86 | quiet: Whether to suppress notification of trace file location. 87 | """ 88 | if not IsTrace(): 89 | return 90 | self._trace_msg = fmt % args 91 | 92 | if not _TRACE_FILE: 93 | _SetTraceFile(quiet) 94 | 95 | if first_trace: 96 | _ClearOldTraces() 97 | self._trace_msg = f"{_NEW_COMMAND_SEP} {self._trace_msg}" 98 | 99 | def __enter__(self): 100 | if not IsTrace(): 101 | return self 102 | 103 | print_msg = ( 104 | f"PID: {os.getpid()} START: {self._time()} :{self._trace_msg}\n" 105 | ) 106 | 107 | with open(_TRACE_FILE, "a") as f: 108 | print(print_msg, file=f) 109 | 110 | if _TRACE_TO_STDERR: 111 | print(print_msg, file=sys.stderr) 112 | 113 | return self 114 | 115 | def __exit__(self, *exc): 116 | if not IsTrace(): 117 | return False 118 | 119 | print_msg = ( 120 | f"PID: {os.getpid()} END: {self._time()} :{self._trace_msg}\n" 121 | ) 122 | 123 | with open(_TRACE_FILE, "a") as f: 124 | print(print_msg, file=f) 125 | 126 | if _TRACE_TO_STDERR: 127 | print(print_msg, file=sys.stderr) 128 | 129 | return False 130 | 131 | 132 | def _GetTraceFile(quiet): 133 | """Get the trace file or create one.""" 134 | # TODO: refactor to pass repodir to Trace. 135 | repo_dir = os.path.dirname(os.path.dirname(__file__)) 136 | trace_file = os.path.join(repo_dir, _TRACE_FILE_NAME) 137 | if not quiet: 138 | print(f"Trace outputs in {trace_file}", file=sys.stderr) 139 | return trace_file 140 | 141 | 142 | def _ClearOldTraces(): 143 | """Clear the oldest commands if trace file is too big.""" 144 | try: 145 | with open(_TRACE_FILE, errors="ignore") as f: 146 | if os.path.getsize(f.name) / (1024 * 1024) <= _MAX_SIZE: 147 | return 148 | trace_lines = f.readlines() 149 | except FileNotFoundError: 150 | return 151 | 152 | while sum(len(x) for x in trace_lines) / (1024 * 1024) > _MAX_SIZE: 153 | for i, line in enumerate(trace_lines): 154 | if "END:" in line and _NEW_COMMAND_SEP in line: 155 | trace_lines = trace_lines[i + 1 :] 156 | break 157 | else: 158 | # The last chunk is bigger than _MAX_SIZE, so just throw everything 159 | # away. 160 | trace_lines = [] 161 | 162 | while trace_lines and trace_lines[-1] == "\n": 163 | trace_lines = trace_lines[:-1] 164 | # Write to a temporary file with a unique name in the same filesystem 165 | # before replacing the original trace file. 166 | temp_dir, temp_prefix = os.path.split(_TRACE_FILE) 167 | with tempfile.NamedTemporaryFile( 168 | "w", dir=temp_dir, prefix=temp_prefix, delete=False 169 | ) as f: 170 | f.writelines(trace_lines) 171 | platform_utils.rename(f.name, _TRACE_FILE) 172 | -------------------------------------------------------------------------------- /requirements.json: -------------------------------------------------------------------------------- 1 | # This file declares various requirements for this version of repo. The 2 | # launcher script will load it and check the constraints before trying to run 3 | # us. This avoids issues of the launcher using an old version of Python (e.g. 4 | # 3.5) while the codebase has moved on to requiring something much newer (e.g. 5 | # 3.8). If the launcher tried to import us, it would fail with syntax errors. 6 | 7 | # This is a JSON file with line-level comments allowed. 8 | 9 | # Always keep backwards compatibility in mine. The launcher script is robust 10 | # against missing values, but when a field is renamed/removed, it means older 11 | # versions of the launcher script won't be able to enforce the constraint. 12 | 13 | # When requiring versions, always use lists as they are easy to parse & compare 14 | # in Python. Strings would require futher processing to turn into a list. 15 | 16 | # Version constraints should be expressed in pairs: soft & hard. Soft versions 17 | # are when we start warning users that their software too old and we're planning 18 | # on dropping support for it, so they need to start planning system upgrades. 19 | # Hard versions are when we refuse to work the tool. Users will be shown an 20 | # error message before we abort entirely. 21 | 22 | # When deciding whether to upgrade a version requirement, check out the distro 23 | # lists to see who will be impacted: 24 | # https://gerrit.googlesource.com/git-repo/+/HEAD/docs/release-process.md#Project-References 25 | 26 | { 27 | # The repo launcher itself. This allows us to force people to upgrade as some 28 | # ignore the warnings about it being out of date, or install ancient versions 29 | # to start with for whatever reason. 30 | # 31 | # NB: Repo launchers started checking this file with repo-2.12, so listing 32 | # versions older than that won't make a difference. 33 | "repo": { 34 | "hard": [2, 11], 35 | "soft": [2, 11] 36 | }, 37 | 38 | # Supported Python versions. 39 | # 40 | # python-3.6 is in Ubuntu Bionic. 41 | # python-3.7 is in Debian Buster. 42 | "python": { 43 | "hard": [3, 6], 44 | "soft": [3, 6] 45 | }, 46 | 47 | # Supported git versions. 48 | # 49 | # git-1.9.1 is in Ubuntu Trusty. 50 | # git-2.1.4 is in Debian Jessie. 51 | # git-2.7.4 is in Ubuntu Xenial. 52 | # git-2.11.0 is in Debian Stretch. 53 | # git-2.17.0 is in Ubuntu Bionic. 54 | # git-2.20.1 is in Debian Buster. 55 | "git": { 56 | "hard": [1, 9, 1], 57 | "soft": [2, 7, 4] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /run_tests: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2019 The Android Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Wrapper to run linters and pytest with the right settings.""" 17 | 18 | import functools 19 | import os 20 | import shutil 21 | import subprocess 22 | import sys 23 | from typing import List 24 | 25 | 26 | ROOT_DIR = os.path.dirname(os.path.realpath(__file__)) 27 | 28 | 29 | @functools.lru_cache() 30 | def is_ci() -> bool: 31 | """Whether we're running in our CI system.""" 32 | return os.getenv("LUCI_CQ") == "yes" 33 | 34 | 35 | def run_pytest(argv: List[str]) -> int: 36 | """Returns the exit code from pytest.""" 37 | if is_ci(): 38 | argv = ["-m", "not skip_cq"] + argv 39 | 40 | return subprocess.run( 41 | [sys.executable, "-m", "pytest"] + argv, 42 | check=False, 43 | cwd=ROOT_DIR, 44 | ).returncode 45 | 46 | 47 | def run_pytest_py38(argv: List[str]) -> int: 48 | """Returns the exit code from pytest under Python 3.8.""" 49 | if is_ci(): 50 | argv = ["-m", "not skip_cq"] + argv 51 | 52 | try: 53 | return subprocess.run( 54 | [ 55 | "vpython3", 56 | "-vpython-spec", 57 | "run_tests.vpython3.8", 58 | "-m", 59 | "pytest", 60 | ] 61 | + argv, 62 | check=False, 63 | cwd=ROOT_DIR, 64 | ).returncode 65 | except FileNotFoundError: 66 | # Skip if the user doesn't have vpython from depot_tools. 67 | return 0 68 | 69 | 70 | def run_black(): 71 | """Returns the exit code from black.""" 72 | # Black by default only matches .py files. We have to list standalone 73 | # scripts manually. 74 | extra_programs = [ 75 | "repo", 76 | "run_tests", 77 | "release/update-hooks", 78 | "release/update-manpages", 79 | ] 80 | return subprocess.run( 81 | [sys.executable, "-m", "black", "--check", ROOT_DIR] + extra_programs, 82 | check=False, 83 | cwd=ROOT_DIR, 84 | ).returncode 85 | 86 | 87 | def run_flake8(): 88 | """Returns the exit code from flake8.""" 89 | return subprocess.run( 90 | [sys.executable, "-m", "flake8", ROOT_DIR], 91 | check=False, 92 | cwd=ROOT_DIR, 93 | ).returncode 94 | 95 | 96 | def run_isort(): 97 | """Returns the exit code from isort.""" 98 | return subprocess.run( 99 | [sys.executable, "-m", "isort", "--check", ROOT_DIR], 100 | check=False, 101 | cwd=ROOT_DIR, 102 | ).returncode 103 | 104 | 105 | def run_update_manpages() -> int: 106 | """Returns the exit code from release/update-manpages.""" 107 | # Allow this to fail on CI, but not local devs. 108 | if is_ci() and not shutil.which("help2man"): 109 | print("update-manpages: help2man not found; skipping test") 110 | return 0 111 | 112 | return subprocess.run( 113 | [sys.executable, "release/update-manpages", "--check"], 114 | check=False, 115 | cwd=ROOT_DIR, 116 | ).returncode 117 | 118 | 119 | def main(argv): 120 | """The main entry.""" 121 | checks = ( 122 | functools.partial(run_pytest, argv), 123 | functools.partial(run_pytest_py38, argv), 124 | run_black, 125 | run_flake8, 126 | run_isort, 127 | run_update_manpages, 128 | ) 129 | # Run all the tests all the time to get full feedback. Don't exit on the 130 | # first error as that makes it more difficult to iterate in the CQ. 131 | return 1 if sum(c() for c in checks) else 0 132 | 133 | 134 | if __name__ == "__main__": 135 | sys.exit(main(sys.argv[1:])) 136 | -------------------------------------------------------------------------------- /run_tests.vpython3: -------------------------------------------------------------------------------- 1 | # This is a vpython "spec" file. 2 | # 3 | # Read more about `vpython` and how to modify this file here: 4 | # https://chromium.googlesource.com/infra/infra/+/main/doc/users/vpython.md 5 | # List of available wheels: 6 | # https://chromium.googlesource.com/infra/infra/+/main/infra/tools/dockerbuild/wheels.md 7 | 8 | python_version: "3.11" 9 | 10 | wheel: < 11 | name: "infra/python/wheels/pytest-py3" 12 | version: "version:8.3.4" 13 | > 14 | 15 | # Required by pytest==8.3.4 16 | wheel: < 17 | name: "infra/python/wheels/py-py2_py3" 18 | version: "version:1.11.0" 19 | > 20 | 21 | # Required by pytest==8.3.4 22 | wheel: < 23 | name: "infra/python/wheels/iniconfig-py3" 24 | version: "version:1.1.1" 25 | > 26 | 27 | # Required by pytest==8.3.4 28 | wheel: < 29 | name: "infra/python/wheels/packaging-py3" 30 | version: "version:23.0" 31 | > 32 | 33 | # Required by pytest==8.3.4 34 | wheel: < 35 | name: "infra/python/wheels/pluggy-py3" 36 | version: "version:1.5.0" 37 | > 38 | 39 | # Required by pytest==8.3.4 40 | wheel: < 41 | name: "infra/python/wheels/toml-py3" 42 | version: "version:0.10.1" 43 | > 44 | 45 | # Required by pytest==8.3.4 46 | wheel: < 47 | name: "infra/python/wheels/pyparsing-py3" 48 | version: "version:3.0.7" 49 | > 50 | 51 | # Required by pytest==8.3.4 52 | wheel: < 53 | name: "infra/python/wheels/attrs-py2_py3" 54 | version: "version:21.4.0" 55 | > 56 | 57 | # NB: Keep in sync with constraints.txt. 58 | wheel: < 59 | name: "infra/python/wheels/black-py3" 60 | version: "version:25.1.0" 61 | > 62 | 63 | # Required by black==25.1.0 64 | wheel: < 65 | name: "infra/python/wheels/mypy-extensions-py3" 66 | version: "version:0.4.3" 67 | > 68 | 69 | # Required by black==25.1.0 70 | wheel: < 71 | name: "infra/python/wheels/tomli-py3" 72 | version: "version:2.0.1" 73 | > 74 | 75 | # Required by black==25.1.0 76 | wheel: < 77 | name: "infra/python/wheels/platformdirs-py3" 78 | version: "version:2.5.2" 79 | > 80 | 81 | # Required by black==25.1.0 82 | wheel: < 83 | name: "infra/python/wheels/pathspec-py3" 84 | version: "version:0.9.0" 85 | > 86 | 87 | # Required by black==25.1.0 88 | wheel: < 89 | name: "infra/python/wheels/typing-extensions-py3" 90 | version: "version:4.3.0" 91 | > 92 | 93 | # Required by black==25.1.0 94 | wheel: < 95 | name: "infra/python/wheels/click-py3" 96 | version: "version:8.0.3" 97 | > 98 | 99 | wheel: < 100 | name: "infra/python/wheels/flake8-py2_py3" 101 | version: "version:6.0.0" 102 | > 103 | 104 | # Required by flake8==6.0.0 105 | wheel: < 106 | name: "infra/python/wheels/mccabe-py2_py3" 107 | version: "version:0.7.0" 108 | > 109 | 110 | # Required by flake8==6.0.0 111 | wheel: < 112 | name: "infra/python/wheels/pyflakes-py2_py3" 113 | version: "version:3.0.1" 114 | > 115 | 116 | # Required by flake8==6.0.0 117 | wheel: < 118 | name: "infra/python/wheels/pycodestyle-py2_py3" 119 | version: "version:2.10.0" 120 | > 121 | 122 | wheel: < 123 | name: "infra/python/wheels/isort-py3" 124 | version: "version:5.10.1" 125 | > 126 | -------------------------------------------------------------------------------- /run_tests.vpython3.8: -------------------------------------------------------------------------------- 1 | # This is a vpython "spec" file. 2 | # 3 | # Read more about `vpython` and how to modify this file here: 4 | # https://chromium.googlesource.com/infra/infra/+/main/doc/users/vpython.md 5 | # List of available wheels: 6 | # https://chromium.googlesource.com/infra/infra/+/main/infra/tools/dockerbuild/wheels.md 7 | 8 | python_version: "3.8" 9 | 10 | wheel: < 11 | name: "infra/python/wheels/pytest-py3" 12 | version: "version:8.3.4" 13 | > 14 | 15 | # Required by pytest==8.3.4 16 | wheel: < 17 | name: "infra/python/wheels/py-py2_py3" 18 | version: "version:1.11.0" 19 | > 20 | 21 | # Required by pytest==8.3.4 22 | wheel: < 23 | name: "infra/python/wheels/iniconfig-py3" 24 | version: "version:1.1.1" 25 | > 26 | 27 | # Required by pytest==8.3.4 28 | wheel: < 29 | name: "infra/python/wheels/packaging-py3" 30 | version: "version:23.0" 31 | > 32 | 33 | # Required by pytest==8.3.4 34 | wheel: < 35 | name: "infra/python/wheels/pluggy-py3" 36 | version: "version:1.5.0" 37 | > 38 | 39 | # Required by pytest==8.3.4 40 | wheel: < 41 | name: "infra/python/wheels/toml-py3" 42 | version: "version:0.10.1" 43 | > 44 | 45 | # Required by pytest==8.3.4 46 | wheel: < 47 | name: "infra/python/wheels/tomli-py3" 48 | version: "version:2.1.0" 49 | > 50 | 51 | # Required by pytest==8.3.4 52 | wheel: < 53 | name: "infra/python/wheels/pyparsing-py3" 54 | version: "version:3.0.7" 55 | > 56 | 57 | # Required by pytest==8.3.4 58 | wheel: < 59 | name: "infra/python/wheels/attrs-py2_py3" 60 | version: "version:21.4.0" 61 | > 62 | 63 | # Required by pytest==8.3.4 64 | wheel: < 65 | name: "infra/python/wheels/exceptiongroup-py3" 66 | version: "version:1.1.2" 67 | > 68 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2019 The Android Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the 'License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Python packaging for repo.""" 17 | 18 | import os 19 | 20 | import setuptools 21 | 22 | 23 | TOPDIR = os.path.dirname(os.path.abspath(__file__)) 24 | 25 | 26 | # Rip out the first intro paragraph. 27 | with open(os.path.join(TOPDIR, "README.md")) as fp: 28 | lines = fp.read().splitlines()[2:] 29 | end = lines.index("") 30 | long_description = " ".join(lines[0:end]) 31 | 32 | 33 | # https://packaging.python.org/tutorials/packaging-projects/ 34 | setuptools.setup( 35 | name="repo", 36 | version="2", 37 | maintainer="Various", 38 | maintainer_email="repo-discuss@googlegroups.com", 39 | description="Repo helps manage many Git repositories", 40 | long_description=long_description, 41 | long_description_content_type="text/plain", 42 | url="https://gerrit.googlesource.com/git-repo/", 43 | project_urls={ 44 | "Bug Tracker": "https://issues.gerritcodereview.com/issues?q=is:open%20componentid:1370071", # noqa: E501 45 | }, 46 | # https://pypi.org/classifiers/ 47 | classifiers=[ 48 | "Development Status :: 6 - Mature", 49 | "Environment :: Console", 50 | "Intended Audience :: Developers", 51 | "License :: OSI Approved :: Apache Software License", 52 | "Natural Language :: English", 53 | "Operating System :: MacOS :: MacOS X", 54 | "Operating System :: Microsoft :: Windows :: Windows 10", 55 | "Operating System :: POSIX :: Linux", 56 | "Programming Language :: Python :: 3", 57 | "Programming Language :: Python :: 3 :: Only", 58 | "Topic :: Software Development :: Version Control :: Git", 59 | ], 60 | python_requires=">=3.6", 61 | packages=["subcmds"], 62 | ) 63 | -------------------------------------------------------------------------------- /subcmds/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2008 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | 17 | 18 | # A mapping of the subcommand name to the class that implements it. 19 | all_commands = {} 20 | all_modules = [] 21 | 22 | my_dir = os.path.dirname(__file__) 23 | for py in os.listdir(my_dir): 24 | if py == "__init__.py": 25 | continue 26 | 27 | if py.endswith(".py"): 28 | name = py[:-3] 29 | 30 | clsn = name.capitalize() 31 | while clsn.find("_") > 0: 32 | h = clsn.index("_") 33 | clsn = clsn[0:h] + clsn[h + 1 :].capitalize() 34 | 35 | mod = __import__(__name__, globals(), locals(), ["%s" % name]) 36 | mod = getattr(mod, name) 37 | try: 38 | cmd = getattr(mod, clsn) 39 | except AttributeError: 40 | raise SyntaxError(f"{__name__}/{py} does not define class {clsn}") 41 | 42 | name = name.replace("_", "-") 43 | cmd.NAME = name 44 | all_commands[name] = cmd 45 | all_modules.append(mod) 46 | 47 | # Add 'branch' as an alias for 'branches'. 48 | all_commands["branch"] = all_commands["branches"] 49 | -------------------------------------------------------------------------------- /subcmds/checkout.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import functools 16 | from typing import NamedTuple 17 | 18 | from command import Command 19 | from command import DEFAULT_LOCAL_JOBS 20 | from error import GitError 21 | from error import RepoExitError 22 | from progress import Progress 23 | from repo_logging import RepoLogger 24 | 25 | 26 | logger = RepoLogger(__file__) 27 | 28 | 29 | class CheckoutBranchResult(NamedTuple): 30 | # Whether the Project is on the branch (i.e. branch exists and no errors) 31 | result: bool 32 | project_idx: int 33 | error: Exception 34 | 35 | 36 | class CheckoutCommandError(RepoExitError): 37 | """Exception thrown when checkout command fails.""" 38 | 39 | 40 | class MissingBranchError(RepoExitError): 41 | """Exception thrown when no project has specified branch.""" 42 | 43 | 44 | class Checkout(Command): 45 | COMMON = True 46 | helpSummary = "Checkout a branch for development" 47 | helpUsage = """ 48 | %prog [...] 49 | """ 50 | helpDescription = """ 51 | The '%prog' command checks out an existing branch that was previously 52 | created by 'repo start'. 53 | 54 | The command is equivalent to: 55 | 56 | repo forall [...] -c git checkout 57 | """ 58 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS 59 | 60 | def ValidateOptions(self, opt, args): 61 | if not args: 62 | self.Usage() 63 | 64 | @classmethod 65 | def _ExecuteOne(cls, nb, project_idx): 66 | """Checkout one project.""" 67 | error = None 68 | result = None 69 | project = cls.get_parallel_context()["projects"][project_idx] 70 | try: 71 | result = project.CheckoutBranch(nb) 72 | except GitError as e: 73 | error = e 74 | return CheckoutBranchResult(result, project_idx, error) 75 | 76 | def Execute(self, opt, args): 77 | nb = args[0] 78 | err = [] 79 | err_projects = [] 80 | success = [] 81 | all_projects = self.GetProjects( 82 | args[1:], all_manifests=not opt.this_manifest_only 83 | ) 84 | 85 | def _ProcessResults(_pool, pm, results): 86 | for result in results: 87 | project = all_projects[result.project_idx] 88 | if result.error is not None: 89 | err.append(result.error) 90 | err_projects.append(project) 91 | elif result.result: 92 | success.append(project) 93 | pm.update(msg="") 94 | 95 | with self.ParallelContext(): 96 | self.get_parallel_context()["projects"] = all_projects 97 | self.ExecuteInParallel( 98 | opt.jobs, 99 | functools.partial(self._ExecuteOne, nb), 100 | range(len(all_projects)), 101 | callback=_ProcessResults, 102 | output=Progress( 103 | f"Checkout {nb}", len(all_projects), quiet=opt.quiet 104 | ), 105 | ) 106 | 107 | if err_projects: 108 | for p in err_projects: 109 | logger.error("error: %s/: cannot checkout %s", p.relpath, nb) 110 | raise CheckoutCommandError(aggregate_errors=err) 111 | elif not success: 112 | msg = f"error: no project has branch {nb}" 113 | logger.error(msg) 114 | raise MissingBranchError(msg) 115 | -------------------------------------------------------------------------------- /subcmds/cherry_pick.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import re 16 | import sys 17 | 18 | from command import Command 19 | from error import GitError 20 | from git_command import GitCommand 21 | from repo_logging import RepoLogger 22 | 23 | 24 | CHANGE_ID_RE = re.compile(r"^\s*Change-Id: I([0-9a-f]{40})\s*$") 25 | logger = RepoLogger(__file__) 26 | 27 | 28 | class CherryPick(Command): 29 | COMMON = True 30 | helpSummary = "Cherry-pick a change." 31 | helpUsage = """ 32 | %prog 33 | """ 34 | helpDescription = """ 35 | '%prog' cherry-picks a change from one branch to another. 36 | The change id will be updated, and a reference to the old 37 | change id will be added. 38 | """ 39 | 40 | def ValidateOptions(self, opt, args): 41 | if len(args) != 1: 42 | self.Usage() 43 | 44 | def Execute(self, opt, args): 45 | reference = args[0] 46 | 47 | p = GitCommand( 48 | None, 49 | ["rev-parse", "--verify", reference], 50 | capture_stdout=True, 51 | capture_stderr=True, 52 | verify_command=True, 53 | ) 54 | try: 55 | p.Wait() 56 | except GitError: 57 | logger.error(p.stderr) 58 | raise 59 | 60 | sha1 = p.stdout.strip() 61 | 62 | p = GitCommand( 63 | None, 64 | ["cat-file", "commit", sha1], 65 | capture_stdout=True, 66 | verify_command=True, 67 | ) 68 | 69 | try: 70 | p.Wait() 71 | except GitError: 72 | logger.error("error: Failed to retrieve old commit message") 73 | raise 74 | 75 | old_msg = self._StripHeader(p.stdout) 76 | 77 | p = GitCommand( 78 | None, 79 | ["cherry-pick", sha1], 80 | capture_stdout=True, 81 | capture_stderr=True, 82 | verify_command=True, 83 | ) 84 | 85 | try: 86 | p.Wait() 87 | except GitError as e: 88 | logger.error(e) 89 | logger.warning( 90 | "NOTE: When committing (please see above) and editing the " 91 | "commit message, please remove the old Change-Id-line and " 92 | "add:\n%s", 93 | self._GetReference(sha1), 94 | ) 95 | raise 96 | 97 | if p.stdout: 98 | print(p.stdout.strip(), file=sys.stdout) 99 | if p.stderr: 100 | print(p.stderr.strip(), file=sys.stderr) 101 | 102 | # The cherry-pick was applied correctly. We just need to edit 103 | # the commit message. 104 | new_msg = self._Reformat(old_msg, sha1) 105 | 106 | p = GitCommand( 107 | None, 108 | ["commit", "--amend", "-F", "-"], 109 | input=new_msg, 110 | capture_stdout=True, 111 | capture_stderr=True, 112 | verify_command=True, 113 | ) 114 | try: 115 | p.Wait() 116 | except GitError: 117 | logger.error("error: Failed to update commit message") 118 | raise 119 | 120 | def _IsChangeId(self, line): 121 | return CHANGE_ID_RE.match(line) 122 | 123 | def _GetReference(self, sha1): 124 | return "(cherry picked from commit %s)" % sha1 125 | 126 | def _StripHeader(self, commit_msg): 127 | lines = commit_msg.splitlines() 128 | return "\n".join(lines[lines.index("") + 1 :]) 129 | 130 | def _Reformat(self, old_msg, sha1): 131 | new_msg = [] 132 | 133 | for line in old_msg.splitlines(): 134 | if not self._IsChangeId(line): 135 | new_msg.append(line) 136 | 137 | # Add a blank line between the message and the change id/reference. 138 | try: 139 | if new_msg[-1].strip() != "": 140 | new_msg.append("") 141 | except IndexError: 142 | pass 143 | 144 | new_msg.append(self._GetReference(sha1)) 145 | return "\n".join(new_msg) 146 | -------------------------------------------------------------------------------- /subcmds/diff.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2008 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import functools 16 | import io 17 | 18 | from command import DEFAULT_LOCAL_JOBS 19 | from command import PagedCommand 20 | 21 | 22 | class Diff(PagedCommand): 23 | COMMON = True 24 | helpSummary = "Show changes between commit and working tree" 25 | helpUsage = """ 26 | %prog [...] 27 | 28 | The -u option causes '%prog' to generate diff output with file paths 29 | relative to the repository root, so the output can be applied 30 | to the Unix 'patch' command. 31 | """ 32 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS 33 | 34 | def _Options(self, p): 35 | p.add_option( 36 | "-u", 37 | "--absolute", 38 | action="store_true", 39 | help="paths are relative to the repository root", 40 | ) 41 | 42 | @classmethod 43 | def _ExecuteOne(cls, absolute, local, project_idx): 44 | """Obtains the diff for a specific project. 45 | 46 | Args: 47 | absolute: Paths are relative to the root. 48 | local: a boolean, if True, the path is relative to the local 49 | (sub)manifest. If false, the path is relative to the outermost 50 | manifest. 51 | project_idx: Project index to get status of. 52 | 53 | Returns: 54 | The status of the project. 55 | """ 56 | buf = io.StringIO() 57 | project = cls.get_parallel_context()["projects"][project_idx] 58 | ret = project.PrintWorkTreeDiff(absolute, output_redir=buf, local=local) 59 | return (ret, buf.getvalue()) 60 | 61 | def Execute(self, opt, args): 62 | all_projects = self.GetProjects( 63 | args, all_manifests=not opt.this_manifest_only 64 | ) 65 | 66 | def _ProcessResults(_pool, _output, results): 67 | ret = 0 68 | for state, output in results: 69 | if output: 70 | print(output, end="") 71 | if not state: 72 | ret = 1 73 | return ret 74 | 75 | with self.ParallelContext(): 76 | self.get_parallel_context()["projects"] = all_projects 77 | return self.ExecuteInParallel( 78 | opt.jobs, 79 | functools.partial( 80 | self._ExecuteOne, opt.absolute, opt.this_manifest_only 81 | ), 82 | range(len(all_projects)), 83 | callback=_ProcessResults, 84 | ordered=True, 85 | chunksize=1, 86 | ) 87 | -------------------------------------------------------------------------------- /subcmds/list.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | 17 | from command import Command 18 | from command import MirrorSafeCommand 19 | 20 | 21 | class List(Command, MirrorSafeCommand): 22 | COMMON = True 23 | helpSummary = "List projects and their associated directories" 24 | helpUsage = """ 25 | %prog [-f] [...] 26 | %prog [-f] -r str1 [str2]... 27 | """ 28 | helpDescription = """ 29 | List all projects; pass '.' to list the project for the cwd. 30 | 31 | By default, only projects that currently exist in the checkout are shown. If 32 | you want to list all projects (using the specified filter settings), use the 33 | --all option. If you want to show all projects regardless of the manifest 34 | groups, then also pass --groups all. 35 | 36 | This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'. 37 | """ 38 | 39 | def _Options(self, p): 40 | p.add_option( 41 | "-r", 42 | "--regex", 43 | action="store_true", 44 | help="filter the project list based on regex or wildcard matching " 45 | "of strings", 46 | ) 47 | p.add_option( 48 | "-g", 49 | "--groups", 50 | help="filter the project list based on the groups the project is " 51 | "in", 52 | ) 53 | p.add_option( 54 | "-a", 55 | "--all", 56 | action="store_true", 57 | help="show projects regardless of checkout state", 58 | ) 59 | p.add_option( 60 | "-n", 61 | "--name-only", 62 | action="store_true", 63 | help="display only the name of the repository", 64 | ) 65 | p.add_option( 66 | "-p", 67 | "--path-only", 68 | action="store_true", 69 | help="display only the path of the repository", 70 | ) 71 | p.add_option( 72 | "-f", 73 | "--fullpath", 74 | action="store_true", 75 | help="display the full work tree path instead of the relative path", 76 | ) 77 | p.add_option( 78 | "--relative-to", 79 | metavar="PATH", 80 | help="display paths relative to this one (default: top of repo " 81 | "client checkout)", 82 | ) 83 | 84 | def ValidateOptions(self, opt, args): 85 | if opt.fullpath and opt.name_only: 86 | self.OptionParser.error("cannot combine -f and -n") 87 | 88 | # Resolve any symlinks so the output is stable. 89 | if opt.relative_to: 90 | opt.relative_to = os.path.realpath(opt.relative_to) 91 | 92 | def Execute(self, opt, args): 93 | """List all projects and the associated directories. 94 | 95 | This may be possible to do with 'repo forall', but repo newbies have 96 | trouble figuring that out. The idea here is that it should be more 97 | discoverable. 98 | 99 | Args: 100 | opt: The options. 101 | args: Positional args. Can be a list of projects to list, or empty. 102 | """ 103 | if not opt.regex: 104 | projects = self.GetProjects( 105 | args, 106 | groups=opt.groups, 107 | missing_ok=opt.all, 108 | all_manifests=not opt.this_manifest_only, 109 | ) 110 | else: 111 | projects = self.FindProjects( 112 | args, all_manifests=not opt.this_manifest_only 113 | ) 114 | 115 | def _getpath(x): 116 | if opt.fullpath: 117 | return x.worktree 118 | if opt.relative_to: 119 | return os.path.relpath(x.worktree, opt.relative_to) 120 | return x.RelPath(local=opt.this_manifest_only) 121 | 122 | lines = [] 123 | for project in projects: 124 | if opt.name_only and not opt.path_only: 125 | lines.append("%s" % (project.name)) 126 | elif opt.path_only and not opt.name_only: 127 | lines.append("%s" % (_getpath(project))) 128 | else: 129 | lines.append(f"{_getpath(project)} : {project.name}") 130 | 131 | if lines: 132 | lines.sort() 133 | print("\n".join(lines)) 134 | -------------------------------------------------------------------------------- /subcmds/overview.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2012 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import optparse 16 | 17 | from color import Coloring 18 | from command import PagedCommand 19 | 20 | 21 | class Overview(PagedCommand): 22 | COMMON = True 23 | helpSummary = "Display overview of unmerged project branches" 24 | helpUsage = """ 25 | %prog [--current-branch] [...] 26 | """ 27 | helpDescription = """ 28 | The '%prog' command is used to display an overview of the projects branches, 29 | and list any local commits that have not yet been merged into the project. 30 | 31 | The -c/--current-branch option can be used to restrict the output to only 32 | branches currently checked out in each project. By default, all branches 33 | are displayed. 34 | """ 35 | 36 | def _Options(self, p): 37 | p.add_option( 38 | "-c", 39 | "--current-branch", 40 | action="store_true", 41 | help="consider only checked out branches", 42 | ) 43 | p.add_option( 44 | "--no-current-branch", 45 | dest="current_branch", 46 | action="store_false", 47 | help="consider all local branches", 48 | ) 49 | # Turn this into a warning & remove this someday. 50 | p.add_option( 51 | "-b", 52 | dest="current_branch", 53 | action="store_true", 54 | help=optparse.SUPPRESS_HELP, 55 | ) 56 | 57 | def Execute(self, opt, args): 58 | all_branches = [] 59 | for project in self.GetProjects( 60 | args, all_manifests=not opt.this_manifest_only 61 | ): 62 | br = [project.GetUploadableBranch(x) for x in project.GetBranches()] 63 | br = [x for x in br if x] 64 | if opt.current_branch: 65 | br = [x for x in br if x.name == project.CurrentBranch] 66 | all_branches.extend(br) 67 | 68 | if not all_branches: 69 | return 70 | 71 | class Report(Coloring): 72 | def __init__(self, config): 73 | Coloring.__init__(self, config, "status") 74 | self.project = self.printer("header", attr="bold") 75 | self.text = self.printer("text") 76 | 77 | out = Report(all_branches[0].project.config) 78 | out.text("Deprecated. See repo info -o.") 79 | out.nl() 80 | out.project("Projects Overview") 81 | out.nl() 82 | 83 | project = None 84 | 85 | for branch in all_branches: 86 | if project != branch.project: 87 | project = branch.project 88 | out.nl() 89 | out.project( 90 | "project %s/" 91 | % project.RelPath(local=opt.this_manifest_only) 92 | ) 93 | out.nl() 94 | 95 | commits = branch.commits 96 | date = branch.date 97 | print( 98 | "%s %-33s (%2d commit%s, %s)" 99 | % ( 100 | branch.name == project.CurrentBranch and "*" or " ", 101 | branch.name, 102 | len(commits), 103 | len(commits) != 1 and "s" or " ", 104 | date, 105 | ) 106 | ) 107 | for commit in commits: 108 | print("%-35s - %s" % ("", commit)) 109 | -------------------------------------------------------------------------------- /subcmds/prune.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2008 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import itertools 16 | 17 | from color import Coloring 18 | from command import DEFAULT_LOCAL_JOBS 19 | from command import PagedCommand 20 | 21 | 22 | class Prune(PagedCommand): 23 | COMMON = True 24 | helpSummary = "Prune (delete) already merged topics" 25 | helpUsage = """ 26 | %prog [...] 27 | """ 28 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS 29 | 30 | @classmethod 31 | def _ExecuteOne(cls, project_idx): 32 | """Process one project.""" 33 | project = cls.get_parallel_context()["projects"][project_idx] 34 | return project.PruneHeads() 35 | 36 | def Execute(self, opt, args): 37 | projects = self.GetProjects( 38 | args, all_manifests=not opt.this_manifest_only 39 | ) 40 | 41 | # NB: Should be able to refactor this module to display summary as 42 | # results come back from children. 43 | def _ProcessResults(_pool, _output, results): 44 | return list(itertools.chain.from_iterable(results)) 45 | 46 | with self.ParallelContext(): 47 | self.get_parallel_context()["projects"] = projects 48 | all_branches = self.ExecuteInParallel( 49 | opt.jobs, 50 | self._ExecuteOne, 51 | range(len(projects)), 52 | callback=_ProcessResults, 53 | ordered=True, 54 | ) 55 | 56 | if not all_branches: 57 | return 58 | 59 | class Report(Coloring): 60 | def __init__(self, config): 61 | Coloring.__init__(self, config, "status") 62 | self.project = self.printer("header", attr="bold") 63 | 64 | out = Report(all_branches[0].project.config) 65 | out.project("Pending Branches") 66 | out.nl() 67 | 68 | project = None 69 | 70 | for branch in all_branches: 71 | if project != branch.project: 72 | project = branch.project 73 | out.nl() 74 | out.project( 75 | "project %s/" 76 | % project.RelPath(local=opt.this_manifest_only) 77 | ) 78 | out.nl() 79 | 80 | print( 81 | "%s %-33s " 82 | % ( 83 | branch.name == project.CurrentBranch and "*" or " ", 84 | branch.name, 85 | ), 86 | end="", 87 | ) 88 | 89 | if not branch.base_exists: 90 | print(f"(ignoring: tracking branch is gone: {branch.base})") 91 | else: 92 | commits = branch.commits 93 | date = branch.date 94 | print( 95 | "(%2d commit%s, %s)" 96 | % (len(commits), len(commits) != 1 and "s" or " ", date) 97 | ) 98 | -------------------------------------------------------------------------------- /subcmds/selfupdate.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import optparse 16 | 17 | from command import Command 18 | from command import MirrorSafeCommand 19 | from error import RepoExitError 20 | from repo_logging import RepoLogger 21 | from subcmds.sync import _PostRepoFetch 22 | from subcmds.sync import _PostRepoUpgrade 23 | 24 | 25 | logger = RepoLogger(__file__) 26 | 27 | 28 | class SelfupdateError(RepoExitError): 29 | """Exit error for failed selfupdate command.""" 30 | 31 | 32 | class Selfupdate(Command, MirrorSafeCommand): 33 | COMMON = False 34 | helpSummary = "Update repo to the latest version" 35 | helpUsage = """ 36 | %prog 37 | """ 38 | helpDescription = """ 39 | The '%prog' command upgrades repo to the latest version, if a 40 | newer version is available. 41 | 42 | Normally this is done automatically by 'repo sync' and does not 43 | need to be performed by an end-user. 44 | """ 45 | 46 | def _Options(self, p): 47 | g = p.add_option_group("repo Version options") 48 | g.add_option( 49 | "--no-repo-verify", 50 | dest="repo_verify", 51 | default=True, 52 | action="store_false", 53 | help="do not verify repo source code", 54 | ) 55 | g.add_option( 56 | "--repo-upgraded", 57 | action="store_true", 58 | help=optparse.SUPPRESS_HELP, 59 | ) 60 | 61 | def Execute(self, opt, args): 62 | rp = self.manifest.repoProject 63 | rp.PreSync() 64 | 65 | if opt.repo_upgraded: 66 | _PostRepoUpgrade(self.manifest) 67 | 68 | else: 69 | result = rp.Sync_NetworkHalf() 70 | if result.error: 71 | logger.error("error: can't update repo") 72 | raise SelfupdateError(aggregate_errors=[result.error]) 73 | 74 | rp.bare_git.gc("--auto") 75 | _PostRepoFetch(rp, repo_verify=opt.repo_verify, verbose=True) 76 | -------------------------------------------------------------------------------- /subcmds/smartsync.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from subcmds.sync import Sync 16 | 17 | 18 | class Smartsync(Sync): 19 | COMMON = True 20 | helpSummary = "Update working tree to the latest known good revision" 21 | helpUsage = """ 22 | %prog [...] 23 | """ 24 | helpDescription = """ 25 | The '%prog' command is a shortcut for sync -s. 26 | """ 27 | 28 | def _Options(self, p): 29 | Sync._Options(self, p, show_smart=False) 30 | 31 | def Execute(self, opt, args): 32 | opt.smart_sync = True 33 | Sync.Execute(self, opt, args) 34 | -------------------------------------------------------------------------------- /subcmds/stage.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2008 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import sys 16 | 17 | from color import Coloring 18 | from command import InteractiveCommand 19 | from git_command import GitCommand 20 | from repo_logging import RepoLogger 21 | 22 | 23 | logger = RepoLogger(__file__) 24 | 25 | 26 | class _ProjectList(Coloring): 27 | def __init__(self, gc): 28 | Coloring.__init__(self, gc, "interactive") 29 | self.prompt = self.printer("prompt", fg="blue", attr="bold") 30 | self.header = self.printer("header", attr="bold") 31 | self.help = self.printer("help", fg="red", attr="bold") 32 | 33 | 34 | class Stage(InteractiveCommand): 35 | COMMON = True 36 | helpSummary = "Stage file(s) for commit" 37 | helpUsage = """ 38 | %prog -i [...] 39 | """ 40 | helpDescription = """ 41 | The '%prog' command stages files to prepare the next commit. 42 | """ 43 | 44 | def _Options(self, p): 45 | g = p.get_option_group("--quiet") 46 | g.add_option( 47 | "-i", 48 | "--interactive", 49 | action="store_true", 50 | help="use interactive staging", 51 | ) 52 | 53 | def Execute(self, opt, args): 54 | if opt.interactive: 55 | self._Interactive(opt, args) 56 | else: 57 | self.Usage() 58 | 59 | def _Interactive(self, opt, args): 60 | all_projects = [ 61 | p 62 | for p in self.GetProjects( 63 | args, all_manifests=not opt.this_manifest_only 64 | ) 65 | if p.IsDirty() 66 | ] 67 | if not all_projects: 68 | logger.error("no projects have uncommitted modifications") 69 | return 70 | 71 | out = _ProjectList(self.manifest.manifestProject.config) 72 | while True: 73 | out.header(" %s", "project") 74 | out.nl() 75 | 76 | for i in range(len(all_projects)): 77 | project = all_projects[i] 78 | out.write( 79 | "%3d: %s", 80 | i + 1, 81 | project.RelPath(local=opt.this_manifest_only) + "/", 82 | ) 83 | out.nl() 84 | out.nl() 85 | 86 | out.write("%3d: (", 0) 87 | out.prompt("q") 88 | out.write("uit)") 89 | out.nl() 90 | 91 | out.prompt("project> ") 92 | out.flush() 93 | try: 94 | a = sys.stdin.readline() 95 | except KeyboardInterrupt: 96 | out.nl() 97 | break 98 | if a == "": 99 | out.nl() 100 | break 101 | 102 | a = a.strip() 103 | if a.lower() in ("q", "quit", "exit"): 104 | break 105 | if not a: 106 | continue 107 | 108 | try: 109 | a_index = int(a) 110 | except ValueError: 111 | a_index = None 112 | 113 | if a_index is not None: 114 | if a_index == 0: 115 | break 116 | if 0 < a_index and a_index <= len(all_projects): 117 | _AddI(all_projects[a_index - 1]) 118 | continue 119 | 120 | projects = [ 121 | p 122 | for p in all_projects 123 | if a in [p.name, p.RelPath(local=opt.this_manifest_only)] 124 | ] 125 | if len(projects) == 1: 126 | _AddI(projects[0]) 127 | continue 128 | print("Bye.") 129 | 130 | 131 | def _AddI(project): 132 | p = GitCommand(project, ["add", "--interactive"], bare=False) 133 | p.Wait() 134 | -------------------------------------------------------------------------------- /subcmds/start.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2008 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import functools 16 | from typing import NamedTuple 17 | 18 | from command import Command 19 | from command import DEFAULT_LOCAL_JOBS 20 | from error import RepoExitError 21 | from git_command import git 22 | from git_config import IsImmutable 23 | from progress import Progress 24 | from repo_logging import RepoLogger 25 | 26 | 27 | logger = RepoLogger(__file__) 28 | 29 | 30 | class ExecuteOneResult(NamedTuple): 31 | project_idx: int 32 | error: Exception 33 | 34 | 35 | class StartError(RepoExitError): 36 | """Exit error for failed start command.""" 37 | 38 | 39 | class Start(Command): 40 | COMMON = True 41 | helpSummary = "Start a new branch for development" 42 | helpUsage = """ 43 | %prog [--all | ...] 44 | """ 45 | helpDescription = """ 46 | '%prog' begins a new branch of development, starting from the 47 | revision specified in the manifest. 48 | """ 49 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS 50 | 51 | def _Options(self, p): 52 | p.add_option( 53 | "--all", 54 | action="store_true", 55 | help="begin branch in all projects", 56 | ) 57 | p.add_option( 58 | "-r", 59 | "--rev", 60 | "--revision", 61 | dest="revision", 62 | help="point branch at this revision instead of upstream", 63 | ) 64 | p.add_option( 65 | "--head", 66 | "--HEAD", 67 | dest="revision", 68 | action="store_const", 69 | const="HEAD", 70 | help="abbreviation for --rev HEAD", 71 | ) 72 | 73 | def ValidateOptions(self, opt, args): 74 | if not args: 75 | self.Usage() 76 | 77 | nb = args[0] 78 | if not git.check_ref_format("heads/%s" % nb): 79 | self.OptionParser.error("'%s' is not a valid name" % nb) 80 | 81 | @classmethod 82 | def _ExecuteOne(cls, revision, nb, default_revisionExpr, project_idx): 83 | """Start one project.""" 84 | # If the current revision is immutable, such as a SHA1, a tag or 85 | # a change, then we can't push back to it. Substitute with 86 | # dest_branch, if defined; or with manifest default revision instead. 87 | branch_merge = "" 88 | error = None 89 | project = cls.get_parallel_context()["projects"][project_idx] 90 | if IsImmutable(project.revisionExpr): 91 | if project.dest_branch: 92 | branch_merge = project.dest_branch 93 | else: 94 | branch_merge = default_revisionExpr 95 | 96 | try: 97 | project.StartBranch( 98 | nb, branch_merge=branch_merge, revision=revision 99 | ) 100 | except Exception as e: 101 | logger.error("error: unable to checkout %s: %s", project.name, e) 102 | error = e 103 | return ExecuteOneResult(project_idx, error) 104 | 105 | def Execute(self, opt, args): 106 | nb = args[0] 107 | err_projects = [] 108 | err = [] 109 | projects = [] 110 | if not opt.all: 111 | projects = args[1:] 112 | if len(projects) < 1: 113 | projects = ["."] # start it in the local project by default 114 | 115 | all_projects = self.GetProjects( 116 | projects, 117 | all_manifests=not opt.this_manifest_only, 118 | ) 119 | 120 | def _ProcessResults(_pool, pm, results): 121 | for result in results: 122 | if result.error: 123 | project = all_projects[result.project_idx] 124 | err_projects.append(project) 125 | err.append(result.error) 126 | pm.update(msg="") 127 | 128 | with self.ParallelContext(): 129 | self.get_parallel_context()["projects"] = all_projects 130 | self.ExecuteInParallel( 131 | opt.jobs, 132 | functools.partial( 133 | self._ExecuteOne, 134 | opt.revision, 135 | nb, 136 | self.manifest.default.revisionExpr, 137 | ), 138 | range(len(all_projects)), 139 | callback=_ProcessResults, 140 | output=Progress( 141 | f"Starting {nb}", len(all_projects), quiet=opt.quiet 142 | ), 143 | chunksize=1, 144 | ) 145 | 146 | if err_projects: 147 | for p in err_projects: 148 | logger.error( 149 | "error: %s/: cannot start %s", 150 | p.RelPath(local=opt.this_manifest_only), 151 | nb, 152 | ) 153 | msg_fmt = "cannot start %d project(s)" 154 | self.git_event_log.ErrorEvent( 155 | msg_fmt % (len(err_projects)), msg_fmt 156 | ) 157 | raise StartError(aggregate_errors=err) 158 | -------------------------------------------------------------------------------- /subcmds/version.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import platform 16 | import sys 17 | 18 | from command import Command 19 | from command import MirrorSafeCommand 20 | from git_command import git 21 | from git_command import RepoSourceVersion 22 | from git_command import user_agent 23 | from git_refs import HEAD 24 | from wrapper import Wrapper 25 | 26 | 27 | class Version(Command, MirrorSafeCommand): 28 | wrapper_version = None 29 | wrapper_path = None 30 | 31 | COMMON = False 32 | helpSummary = "Display the version of repo" 33 | helpUsage = """ 34 | %prog 35 | """ 36 | 37 | def Execute(self, opt, args): 38 | rp = self.manifest.repoProject 39 | rem = rp.GetRemote() 40 | branch = rp.GetBranch("default") 41 | 42 | # These might not be the same. Report them both. 43 | src_ver = RepoSourceVersion() 44 | rp_ver = rp.bare_git.describe(HEAD) 45 | print(f"repo version {rp_ver}") 46 | print(f" (from {rem.url})") 47 | print(f" (tracking {branch.merge})") 48 | print(f" ({rp.bare_git.log('-1', '--format=%cD', HEAD)})") 49 | 50 | if self.wrapper_path is not None: 51 | print(f"repo launcher version {self.wrapper_version}") 52 | print(f" (from {self.wrapper_path})") 53 | 54 | if src_ver != rp_ver: 55 | print(f" (currently at {src_ver})") 56 | 57 | print(f"repo User-Agent {user_agent.repo}") 58 | print(f"git {git.version_tuple().full}") 59 | print(f"git User-Agent {user_agent.git}") 60 | print(f"Python {sys.version}") 61 | uname = platform.uname() 62 | if sys.version_info.major < 3: 63 | # Python 3 returns a named tuple, but Python 2 is simpler. 64 | print(uname) 65 | else: 66 | print(f"OS {uname.system} {uname.release} ({uname.version})") 67 | processor = uname.processor if uname.processor else "unknown" 68 | print(f"CPU {uname.machine} ({processor})") 69 | print("Bug reports:", Wrapper().BUG_URL) 70 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Common fixtures for pytests.""" 16 | 17 | import pathlib 18 | 19 | import pytest 20 | 21 | import platform_utils 22 | import repo_trace 23 | 24 | 25 | @pytest.fixture(autouse=True) 26 | def disable_repo_trace(tmp_path): 27 | """Set an environment marker to relax certain strict checks for test code.""" # noqa: E501 28 | repo_trace._TRACE_FILE = str(tmp_path / "TRACE_FILE_from_test") 29 | 30 | 31 | # adapted from pytest-home 0.5.1 32 | def _set_home(monkeypatch, path: pathlib.Path): 33 | """ 34 | Set the home dir using a pytest monkeypatch context. 35 | """ 36 | win = platform_utils.isWindows() 37 | vars = ["HOME"] + win * ["USERPROFILE"] 38 | for var in vars: 39 | monkeypatch.setenv(var, str(path)) 40 | return path 41 | 42 | 43 | # copied from 44 | # https://github.com/pytest-dev/pytest/issues/363#issuecomment-1335631998 45 | @pytest.fixture(scope="session") 46 | def monkeysession(): 47 | with pytest.MonkeyPatch.context() as mp: 48 | yield mp 49 | 50 | 51 | @pytest.fixture(autouse=True, scope="session") 52 | def session_tmp_home_dir(tmp_path_factory, monkeysession): 53 | """Set HOME to a temporary directory, avoiding user's .gitconfig. 54 | 55 | b/302797407 56 | 57 | Set home at session scope to take effect prior to 58 | ``test_wrapper.GitCheckoutTestCase.setUpClass``. 59 | """ 60 | return _set_home(monkeysession, tmp_path_factory.mktemp("home")) 61 | 62 | 63 | # adapted from pytest-home 0.5.1 64 | @pytest.fixture(autouse=True) 65 | def tmp_home_dir(monkeypatch, tmp_path_factory): 66 | """Set HOME to a temporary directory. 67 | 68 | Ensures that state doesn't accumulate in $HOME across tests. 69 | 70 | Note that in conjunction with session_tmp_homedir, the HOME 71 | dir is patched twice, once at session scope, and then again at 72 | the function scope. 73 | """ 74 | return _set_home(monkeypatch, tmp_path_factory.mktemp("home")) 75 | 76 | 77 | @pytest.fixture(autouse=True) 78 | def setup_user_identity(monkeysession, scope="session"): 79 | """Set env variables for author and committer name and email.""" 80 | monkeysession.setenv("GIT_AUTHOR_NAME", "Foo Bar") 81 | monkeysession.setenv("GIT_COMMITTER_NAME", "Foo Bar") 82 | monkeysession.setenv("GIT_AUTHOR_EMAIL", "foo@bar.baz") 83 | monkeysession.setenv("GIT_COMMITTER_EMAIL", "foo@bar.baz") 84 | -------------------------------------------------------------------------------- /tests/fixtures/.gitignore: -------------------------------------------------------------------------------- 1 | /.repo_not.present.gitconfig.json 2 | /.repo_test.gitconfig.json 3 | -------------------------------------------------------------------------------- /tests/fixtures/test.gitconfig: -------------------------------------------------------------------------------- 1 | [section] 2 | empty 3 | nonempty = true 4 | boolinvalid = oops 5 | booltrue = true 6 | boolfalse = false 7 | intinvalid = oops 8 | inthex = 0x10 9 | inthexk = 0x10k 10 | int = 10 11 | intk = 10k 12 | intm = 10m 13 | intg = 10g 14 | 15 | [color "status"] 16 | one = yellow 17 | two = magenta cyan 18 | three = black red ul 19 | reset = reset 20 | none 21 | empty = 22 | -------------------------------------------------------------------------------- /tests/test_color.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2024 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Unittests for the color.py module.""" 16 | 17 | import os 18 | import unittest 19 | 20 | import color 21 | import git_config 22 | 23 | 24 | def fixture(*paths): 25 | """Return a path relative to test/fixtures.""" 26 | return os.path.join(os.path.dirname(__file__), "fixtures", *paths) 27 | 28 | 29 | class ColoringTests(unittest.TestCase): 30 | """tests of the Coloring class.""" 31 | 32 | def setUp(self): 33 | """Create a GitConfig object using the test.gitconfig fixture.""" 34 | config_fixture = fixture("test.gitconfig") 35 | self.config = git_config.GitConfig(config_fixture) 36 | color.SetDefaultColoring("true") 37 | self.color = color.Coloring(self.config, "status") 38 | 39 | def test_Color_Parse_all_params_none(self): 40 | """all params are None""" 41 | val = self.color._parse(None, None, None, None) 42 | self.assertEqual("", val) 43 | 44 | def test_Color_Parse_first_parameter_none(self): 45 | """check fg & bg & attr""" 46 | val = self.color._parse(None, "black", "red", "ul") 47 | self.assertEqual("\x1b[4;30;41m", val) 48 | 49 | def test_Color_Parse_one_entry(self): 50 | """check fg""" 51 | val = self.color._parse("one", None, None, None) 52 | self.assertEqual("\033[33m", val) 53 | 54 | def test_Color_Parse_two_entry(self): 55 | """check fg & bg""" 56 | val = self.color._parse("two", None, None, None) 57 | self.assertEqual("\033[35;46m", val) 58 | 59 | def test_Color_Parse_three_entry(self): 60 | """check fg & bg & attr""" 61 | val = self.color._parse("three", None, None, None) 62 | self.assertEqual("\033[4;30;41m", val) 63 | 64 | def test_Color_Parse_reset_entry(self): 65 | """check reset entry""" 66 | val = self.color._parse("reset", None, None, None) 67 | self.assertEqual("\033[m", val) 68 | 69 | def test_Color_Parse_empty_entry(self): 70 | """check empty entry""" 71 | val = self.color._parse("none", "blue", "white", "dim") 72 | self.assertEqual("\033[2;34;47m", val) 73 | val = self.color._parse("empty", "green", "white", "bold") 74 | self.assertEqual("\033[1;32;47m", val) 75 | -------------------------------------------------------------------------------- /tests/test_editor.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Unittests for the editor.py module.""" 16 | 17 | import unittest 18 | 19 | from editor import Editor 20 | 21 | 22 | class EditorTestCase(unittest.TestCase): 23 | """Take care of resetting Editor state across tests.""" 24 | 25 | def setUp(self): 26 | self.setEditor(None) 27 | 28 | def tearDown(self): 29 | self.setEditor(None) 30 | 31 | @staticmethod 32 | def setEditor(editor): 33 | Editor._editor = editor 34 | 35 | 36 | class GetEditor(EditorTestCase): 37 | """Check GetEditor behavior.""" 38 | 39 | def test_basic(self): 40 | """Basic checking of _GetEditor.""" 41 | self.setEditor(":") 42 | self.assertEqual(":", Editor._GetEditor()) 43 | 44 | 45 | class EditString(EditorTestCase): 46 | """Check EditString behavior.""" 47 | 48 | def test_no_editor(self): 49 | """Check behavior when no editor is available.""" 50 | self.setEditor(":") 51 | self.assertEqual("foo", Editor.EditString("foo")) 52 | 53 | def test_cat_editor(self): 54 | """Check behavior when editor is `cat`.""" 55 | self.setEditor("cat") 56 | self.assertEqual("foo", Editor.EditString("foo")) 57 | -------------------------------------------------------------------------------- /tests/test_error.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Unittests for the error.py module.""" 16 | 17 | import inspect 18 | import pickle 19 | import unittest 20 | 21 | import command 22 | import error 23 | import fetch 24 | import git_command 25 | import project 26 | from subcmds import all_modules 27 | 28 | 29 | imports = all_modules + [ 30 | error, 31 | project, 32 | git_command, 33 | fetch, 34 | command, 35 | ] 36 | 37 | 38 | class PickleTests(unittest.TestCase): 39 | """Make sure all our custom exceptions can be pickled.""" 40 | 41 | def getExceptions(self): 42 | """Return all our custom exceptions.""" 43 | for entry in imports: 44 | for name in dir(entry): 45 | cls = getattr(entry, name) 46 | if isinstance(cls, type) and issubclass(cls, Exception): 47 | yield cls 48 | 49 | def testExceptionLookup(self): 50 | """Make sure our introspection logic works.""" 51 | classes = list(self.getExceptions()) 52 | self.assertIn(error.HookError, classes) 53 | # Don't assert the exact number to avoid being a change-detector test. 54 | self.assertGreater(len(classes), 10) 55 | 56 | def testPickle(self): 57 | """Try to pickle all the exceptions.""" 58 | for cls in self.getExceptions(): 59 | args = inspect.getfullargspec(cls.__init__).args[1:] 60 | obj = cls(*args) 61 | p = pickle.dumps(obj) 62 | try: 63 | newobj = pickle.loads(p) 64 | except Exception as e: # pylint: disable=broad-except 65 | self.fail( 66 | "Class %s is unable to be pickled: %s\n" 67 | "Incomplete super().__init__(...) call?" % (cls, e) 68 | ) 69 | self.assertIsInstance(newobj, cls) 70 | self.assertEqual(str(obj), str(newobj)) 71 | -------------------------------------------------------------------------------- /tests/test_hooks.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Unittests for the hooks.py module.""" 16 | 17 | import unittest 18 | 19 | import hooks 20 | 21 | 22 | class RepoHookShebang(unittest.TestCase): 23 | """Check shebang parsing in RepoHook.""" 24 | 25 | def test_no_shebang(self): 26 | """Lines w/out shebangs should be rejected.""" 27 | DATA = ("", "#\n# foo\n", "# Bad shebang in script\n#!/foo\n") 28 | for data in DATA: 29 | self.assertIsNone(hooks.RepoHook._ExtractInterpFromShebang(data)) 30 | 31 | def test_direct_interp(self): 32 | """Lines whose shebang points directly to the interpreter.""" 33 | DATA = ( 34 | ("#!/foo", "/foo"), 35 | ("#! /foo", "/foo"), 36 | ("#!/bin/foo ", "/bin/foo"), 37 | ("#! /usr/foo ", "/usr/foo"), 38 | ("#! /usr/foo -args", "/usr/foo"), 39 | ) 40 | for shebang, interp in DATA: 41 | self.assertEqual( 42 | hooks.RepoHook._ExtractInterpFromShebang(shebang), interp 43 | ) 44 | 45 | def test_env_interp(self): 46 | """Lines whose shebang launches through `env`.""" 47 | DATA = ( 48 | ("#!/usr/bin/env foo", "foo"), 49 | ("#!/bin/env foo", "foo"), 50 | ("#! /bin/env /bin/foo ", "/bin/foo"), 51 | ) 52 | for shebang, interp in DATA: 53 | self.assertEqual( 54 | hooks.RepoHook._ExtractInterpFromShebang(shebang), interp 55 | ) 56 | -------------------------------------------------------------------------------- /tests/test_platform_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Unittests for the platform_utils.py module.""" 16 | 17 | import os 18 | import tempfile 19 | import unittest 20 | 21 | import platform_utils 22 | 23 | 24 | class RemoveTests(unittest.TestCase): 25 | """Check remove() helper.""" 26 | 27 | def testMissingOk(self): 28 | """Check missing_ok handling.""" 29 | with tempfile.TemporaryDirectory() as tmpdir: 30 | path = os.path.join(tmpdir, "test") 31 | 32 | # Should not fail. 33 | platform_utils.remove(path, missing_ok=True) 34 | 35 | # Should fail. 36 | self.assertRaises(OSError, platform_utils.remove, path) 37 | self.assertRaises( 38 | OSError, platform_utils.remove, path, missing_ok=False 39 | ) 40 | 41 | # Should not fail if it exists. 42 | open(path, "w").close() 43 | platform_utils.remove(path, missing_ok=True) 44 | self.assertFalse(os.path.exists(path)) 45 | 46 | open(path, "w").close() 47 | platform_utils.remove(path) 48 | self.assertFalse(os.path.exists(path)) 49 | 50 | open(path, "w").close() 51 | platform_utils.remove(path, missing_ok=False) 52 | self.assertFalse(os.path.exists(path)) 53 | -------------------------------------------------------------------------------- /tests/test_repo_logging.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Unit test for repo_logging module.""" 16 | 17 | import contextlib 18 | import io 19 | import logging 20 | import unittest 21 | from unittest import mock 22 | 23 | from color import SetDefaultColoring 24 | from error import RepoExitError 25 | from repo_logging import RepoLogger 26 | 27 | 28 | class TestRepoLogger(unittest.TestCase): 29 | @mock.patch.object(RepoLogger, "error") 30 | def test_log_aggregated_errors_logs_aggregated_errors(self, mock_error): 31 | """Test if log_aggregated_errors logs a list of aggregated errors.""" 32 | logger = RepoLogger(__name__) 33 | logger.log_aggregated_errors( 34 | RepoExitError( 35 | aggregate_errors=[ 36 | Exception("foo"), 37 | Exception("bar"), 38 | Exception("baz"), 39 | Exception("hello"), 40 | Exception("world"), 41 | Exception("test"), 42 | ] 43 | ) 44 | ) 45 | 46 | mock_error.assert_has_calls( 47 | [ 48 | mock.call("=" * 80), 49 | mock.call( 50 | "Repo command failed due to the following `%s` errors:", 51 | "RepoExitError", 52 | ), 53 | mock.call("foo\nbar\nbaz\nhello\nworld"), 54 | mock.call("+%d additional errors...", 1), 55 | ] 56 | ) 57 | 58 | @mock.patch.object(RepoLogger, "error") 59 | def test_log_aggregated_errors_logs_single_error(self, mock_error): 60 | """Test if log_aggregated_errors logs empty aggregated_errors.""" 61 | logger = RepoLogger(__name__) 62 | logger.log_aggregated_errors(RepoExitError()) 63 | 64 | mock_error.assert_has_calls( 65 | [ 66 | mock.call("=" * 80), 67 | mock.call("Repo command failed: %s", "RepoExitError"), 68 | ] 69 | ) 70 | 71 | def test_log_with_format_string(self): 72 | """Test different log levels with format strings.""" 73 | 74 | # Set color output to "always" for consistent test results. 75 | # This ensures the logger's behavior is uniform across different 76 | # environments and git configurations. 77 | SetDefaultColoring("always") 78 | 79 | # Regex pattern to match optional ANSI color codes. 80 | # \033 - Escape character 81 | # \[ - Opening square bracket 82 | # [0-9;]* - Zero or more digits or semicolons 83 | # m - Ending 'm' character 84 | # ? - Makes the entire group optional 85 | opt_color = r"(\033\[[0-9;]*m)?" 86 | 87 | for level in (logging.INFO, logging.WARN, logging.ERROR): 88 | name = logging.getLevelName(level) 89 | 90 | with self.subTest(level=level, name=name): 91 | output = io.StringIO() 92 | 93 | with contextlib.redirect_stderr(output): 94 | logger = RepoLogger(__name__) 95 | logger.log(level, "%s", "100% pass") 96 | 97 | self.assertRegex( 98 | output.getvalue().strip(), 99 | f"^{opt_color}100% pass{opt_color}$", 100 | f"failed for level {name}", 101 | ) 102 | -------------------------------------------------------------------------------- /tests/test_repo_trace.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Unittests for the repo_trace.py module.""" 16 | 17 | import os 18 | import unittest 19 | from unittest import mock 20 | 21 | import repo_trace 22 | 23 | 24 | class TraceTests(unittest.TestCase): 25 | """Check Trace behavior.""" 26 | 27 | def testTrace_MaxSizeEnforced(self): 28 | content = "git chicken" 29 | 30 | with repo_trace.Trace(content, first_trace=True): 31 | pass 32 | first_trace_size = os.path.getsize(repo_trace._TRACE_FILE) 33 | 34 | with repo_trace.Trace(content): 35 | pass 36 | self.assertGreater( 37 | os.path.getsize(repo_trace._TRACE_FILE), first_trace_size 38 | ) 39 | 40 | # Check we clear everything is the last chunk is larger than _MAX_SIZE. 41 | with mock.patch("repo_trace._MAX_SIZE", 0): 42 | with repo_trace.Trace(content, first_trace=True): 43 | pass 44 | self.assertEqual( 45 | first_trace_size, os.path.getsize(repo_trace._TRACE_FILE) 46 | ) 47 | 48 | # Check we only clear the chunks we need to. 49 | repo_trace._MAX_SIZE = (first_trace_size + 1) / (1024 * 1024) 50 | with repo_trace.Trace(content, first_trace=True): 51 | pass 52 | self.assertEqual( 53 | first_trace_size * 2, os.path.getsize(repo_trace._TRACE_FILE) 54 | ) 55 | 56 | with repo_trace.Trace(content, first_trace=True): 57 | pass 58 | self.assertEqual( 59 | first_trace_size * 2, os.path.getsize(repo_trace._TRACE_FILE) 60 | ) 61 | -------------------------------------------------------------------------------- /tests/test_ssh.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Unittests for the ssh.py module.""" 16 | 17 | import multiprocessing 18 | import subprocess 19 | import unittest 20 | from unittest import mock 21 | 22 | import ssh 23 | 24 | 25 | class SshTests(unittest.TestCase): 26 | """Tests the ssh functions.""" 27 | 28 | def test_parse_ssh_version(self): 29 | """Check _parse_ssh_version() handling.""" 30 | ver = ssh._parse_ssh_version("Unknown\n") 31 | self.assertEqual(ver, ()) 32 | ver = ssh._parse_ssh_version("OpenSSH_1.0\n") 33 | self.assertEqual(ver, (1, 0)) 34 | ver = ssh._parse_ssh_version( 35 | "OpenSSH_6.6.1p1 Ubuntu-2ubuntu2.13, OpenSSL 1.0.1f 6 Jan 2014\n" 36 | ) 37 | self.assertEqual(ver, (6, 6, 1)) 38 | ver = ssh._parse_ssh_version( 39 | "OpenSSH_7.6p1 Ubuntu-4ubuntu0.3, OpenSSL 1.0.2n 7 Dec 2017\n" 40 | ) 41 | self.assertEqual(ver, (7, 6)) 42 | ver = ssh._parse_ssh_version("OpenSSH_9.0p1, LibreSSL 3.3.6\n") 43 | self.assertEqual(ver, (9, 0)) 44 | 45 | def test_version(self): 46 | """Check version() handling.""" 47 | with mock.patch("ssh._run_ssh_version", return_value="OpenSSH_1.2\n"): 48 | self.assertEqual(ssh.version(), (1, 2)) 49 | 50 | def test_context_manager_empty(self): 51 | """Verify context manager with no clients works correctly.""" 52 | with multiprocessing.Manager() as manager: 53 | with ssh.ProxyManager(manager): 54 | pass 55 | 56 | def test_context_manager_child_cleanup(self): 57 | """Verify orphaned clients & masters get cleaned up.""" 58 | with multiprocessing.Manager() as manager: 59 | with ssh.ProxyManager(manager) as ssh_proxy: 60 | client = subprocess.Popen(["sleep", "964853320"]) 61 | ssh_proxy.add_client(client) 62 | master = subprocess.Popen(["sleep", "964853321"]) 63 | ssh_proxy.add_master(master) 64 | # If the process still exists, these will throw timeout errors. 65 | client.wait(0) 66 | master.wait(0) 67 | 68 | def test_ssh_sock(self): 69 | """Check sock() function.""" 70 | manager = multiprocessing.Manager() 71 | proxy = ssh.ProxyManager(manager) 72 | with mock.patch("tempfile.mkdtemp", return_value="/tmp/foo"): 73 | # Old ssh version uses port. 74 | with mock.patch("ssh.version", return_value=(6, 6)): 75 | self.assertTrue(proxy.sock().endswith("%p")) 76 | 77 | proxy._sock_path = None 78 | # New ssh version uses hash. 79 | with mock.patch("ssh.version", return_value=(6, 7)): 80 | self.assertTrue(proxy.sock().endswith("%C")) 81 | -------------------------------------------------------------------------------- /tests/test_subcmds.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Unittests for the subcmds module (mostly __init__.py than subcommands).""" 16 | 17 | import optparse 18 | import unittest 19 | 20 | import subcmds 21 | 22 | 23 | class AllCommands(unittest.TestCase): 24 | """Check registered all_commands.""" 25 | 26 | def test_required_basic(self): 27 | """Basic checking of registered commands.""" 28 | # NB: We don't test all subcommands as we want to avoid "change 29 | # detection" tests, so we just look for the most common/important ones 30 | # here that are unlikely to ever change. 31 | for cmd in {"cherry-pick", "help", "init", "start", "sync", "upload"}: 32 | self.assertIn(cmd, subcmds.all_commands) 33 | 34 | def test_naming(self): 35 | """Verify we don't add things that we shouldn't.""" 36 | for cmd in subcmds.all_commands: 37 | # Reject filename suffixes like "help.py". 38 | self.assertNotIn(".", cmd) 39 | 40 | # Make sure all '_' were converted to '-'. 41 | self.assertNotIn("_", cmd) 42 | 43 | # Reject internal python paths like "__init__". 44 | self.assertFalse(cmd.startswith("__")) 45 | 46 | def test_help_desc_style(self): 47 | """Force some consistency in option descriptions. 48 | 49 | Python's optparse & argparse has a few default options like --help. 50 | Their option description text uses lowercase sentence fragments, so 51 | enforce our options follow the same style so UI is consistent. 52 | 53 | We enforce: 54 | * Text starts with lowercase. 55 | * Text doesn't end with period. 56 | """ 57 | for name, cls in subcmds.all_commands.items(): 58 | cmd = cls() 59 | parser = cmd.OptionParser 60 | for option in parser.option_list: 61 | if option.help == optparse.SUPPRESS_HELP: 62 | continue 63 | 64 | c = option.help[0] 65 | self.assertEqual( 66 | c.lower(), 67 | c, 68 | msg=f"subcmds/{name}.py: {option.get_opt_string()}: " 69 | f'help text should start with lowercase: "{option.help}"', 70 | ) 71 | 72 | self.assertNotEqual( 73 | option.help[-1], 74 | ".", 75 | msg=f"subcmds/{name}.py: {option.get_opt_string()}: " 76 | f'help text should not end in a period: "{option.help}"', 77 | ) 78 | 79 | def test_cli_option_style(self): 80 | """Force some consistency in option flags.""" 81 | for name, cls in subcmds.all_commands.items(): 82 | cmd = cls() 83 | parser = cmd.OptionParser 84 | for option in parser.option_list: 85 | for opt in option._long_opts: 86 | self.assertNotIn( 87 | "_", 88 | opt, 89 | msg=f"subcmds/{name}.py: {opt}: only use dashes in " 90 | "options, not underscores", 91 | ) 92 | 93 | def test_cli_option_dest(self): 94 | """Block redundant dest= arguments.""" 95 | 96 | def _check_dest(opt): 97 | if opt.dest is None or not opt._long_opts: 98 | return 99 | 100 | long = opt._long_opts[0] 101 | assert long.startswith("--") 102 | # This matches optparse's behavior. 103 | implicit_dest = long[2:].replace("-", "_") 104 | if implicit_dest == opt.dest: 105 | bad_opts.append((str(opt), opt.dest)) 106 | 107 | # Hook the option check list. 108 | optparse.Option.CHECK_METHODS.insert(0, _check_dest) 109 | 110 | # Gather all the bad options up front so people can see all bad options 111 | # instead of failing at the first one. 112 | all_bad_opts = {} 113 | for name, cls in subcmds.all_commands.items(): 114 | bad_opts = all_bad_opts[name] = [] 115 | cmd = cls() 116 | # Trigger construction of parser. 117 | cmd.OptionParser 118 | 119 | errmsg = None 120 | for name, bad_opts in sorted(all_bad_opts.items()): 121 | if bad_opts: 122 | if not errmsg: 123 | errmsg = "Omit redundant dest= when defining options.\n" 124 | errmsg += f"\nSubcommand {name} (subcmds/{name}.py):\n" 125 | errmsg += "".join( 126 | f" {opt}: dest='{dest}'\n" for opt, dest in bad_opts 127 | ) 128 | if errmsg: 129 | self.fail(errmsg) 130 | 131 | # Make sure we aren't popping the wrong stuff. 132 | assert optparse.Option.CHECK_METHODS.pop(0) is _check_dest 133 | -------------------------------------------------------------------------------- /tests/test_subcmds_init.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Unittests for the subcmds/init.py module.""" 16 | 17 | import unittest 18 | 19 | from subcmds import init 20 | 21 | 22 | class InitCommand(unittest.TestCase): 23 | """Check registered all_commands.""" 24 | 25 | def setUp(self): 26 | self.cmd = init.Init() 27 | 28 | def test_cli_parser_good(self): 29 | """Check valid command line options.""" 30 | ARGV = ([],) 31 | for argv in ARGV: 32 | opts, args = self.cmd.OptionParser.parse_args(argv) 33 | self.cmd.ValidateOptions(opts, args) 34 | 35 | def test_cli_parser_bad(self): 36 | """Check invalid command line options.""" 37 | ARGV = ( 38 | # Too many arguments. 39 | ["url", "asdf"], 40 | # Conflicting options. 41 | ["--mirror", "--archive"], 42 | ) 43 | for argv in ARGV: 44 | opts, args = self.cmd.OptionParser.parse_args(argv) 45 | with self.assertRaises(SystemExit): 46 | self.cmd.ValidateOptions(opts, args) 47 | -------------------------------------------------------------------------------- /tests/test_subcmds_manifest.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2025 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Unittests for the subcmds/manifest.py module.""" 16 | 17 | import json 18 | from pathlib import Path 19 | from unittest import mock 20 | 21 | import manifest_xml 22 | from subcmds import manifest 23 | 24 | 25 | _EXAMPLE_MANIFEST = """\ 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | """ 34 | 35 | 36 | def _get_cmd(repodir: Path) -> manifest.Manifest: 37 | """Instantiate a manifest command object to test.""" 38 | manifests_git = repodir / "manifests.git" 39 | manifests_git.mkdir() 40 | (manifests_git / "config").write_text( 41 | """ 42 | [remote "origin"] 43 | \turl = http://localhost/manifest 44 | """ 45 | ) 46 | client = manifest_xml.RepoClient(repodir=str(repodir)) 47 | git_event_log = mock.MagicMock(ErrorEvent=mock.Mock(return_value=None)) 48 | return manifest.Manifest( 49 | repodir=client.repodir, 50 | client=client, 51 | manifest=client.manifest, 52 | outer_client=client, 53 | outer_manifest=client.manifest, 54 | git_event_log=git_event_log, 55 | ) 56 | 57 | 58 | def test_output_format_xml_file(tmp_path): 59 | """Test writing XML to a file.""" 60 | path = tmp_path / "manifest.xml" 61 | path.write_text(_EXAMPLE_MANIFEST) 62 | outpath = tmp_path / "output.xml" 63 | cmd = _get_cmd(tmp_path) 64 | opt, args = cmd.OptionParser.parse_args(["--output-file", str(outpath)]) 65 | cmd.Execute(opt, args) 66 | # Normalize the output a bit as we don't exactly care. 67 | normalize = lambda data: "\n".join( 68 | x.strip() for x in data.splitlines() if x.strip() 69 | ) 70 | assert ( 71 | normalize(outpath.read_text()) 72 | == """ 73 | 74 | 75 | 76 | 77 | 78 | """ 79 | ) 80 | 81 | 82 | def test_output_format_xml_stdout(tmp_path, capsys): 83 | """Test writing XML to stdout.""" 84 | path = tmp_path / "manifest.xml" 85 | path.write_text(_EXAMPLE_MANIFEST) 86 | cmd = _get_cmd(tmp_path) 87 | opt, args = cmd.OptionParser.parse_args(["--format", "xml"]) 88 | cmd.Execute(opt, args) 89 | # Normalize the output a bit as we don't exactly care. 90 | normalize = lambda data: "\n".join( 91 | x.strip() for x in data.splitlines() if x.strip() 92 | ) 93 | stdout = capsys.readouterr().out 94 | assert ( 95 | normalize(stdout) 96 | == """ 97 | 98 | 99 | 100 | 101 | 102 | """ 103 | ) 104 | 105 | 106 | def test_output_format_json(tmp_path, capsys): 107 | """Test writing JSON.""" 108 | path = tmp_path / "manifest.xml" 109 | path.write_text(_EXAMPLE_MANIFEST) 110 | cmd = _get_cmd(tmp_path) 111 | opt, args = cmd.OptionParser.parse_args(["--format", "json"]) 112 | cmd.Execute(opt, args) 113 | obj = json.loads(capsys.readouterr().out) 114 | assert obj == { 115 | "default": {"remote": "test-remote", "revision": "refs/heads/main"}, 116 | "project": [{"name": "repohooks", "path": "src/repohooks"}], 117 | "remote": [{"fetch": "http://localhost", "name": "test-remote"}], 118 | "repo-hooks": {"enabled-list": "a b", "in-project": "repohooks"}, 119 | } 120 | 121 | 122 | def test_output_format_json_pretty(tmp_path, capsys): 123 | """Test writing pretty JSON.""" 124 | path = tmp_path / "manifest.xml" 125 | path.write_text(_EXAMPLE_MANIFEST) 126 | cmd = _get_cmd(tmp_path) 127 | opt, args = cmd.OptionParser.parse_args(["--format", "json", "--pretty"]) 128 | cmd.Execute(opt, args) 129 | stdout = capsys.readouterr().out 130 | assert ( 131 | stdout 132 | == """\ 133 | { 134 | "default": { 135 | "remote": "test-remote", 136 | "revision": "refs/heads/main" 137 | }, 138 | "project": [ 139 | { 140 | "name": "repohooks", 141 | "path": "src/repohooks" 142 | } 143 | ], 144 | "remote": [ 145 | { 146 | "fetch": "http://localhost", 147 | "name": "test-remote" 148 | } 149 | ], 150 | "repo-hooks": { 151 | "enabled-list": "a b", 152 | "in-project": "repohooks" 153 | } 154 | } 155 | """ 156 | ) 157 | -------------------------------------------------------------------------------- /tests/test_subcmds_upload.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Unittests for the subcmds/upload.py module.""" 16 | 17 | import unittest 18 | from unittest import mock 19 | 20 | from error import GitError 21 | from error import UploadError 22 | from subcmds import upload 23 | 24 | 25 | class UnexpectedError(Exception): 26 | """An exception not expected by upload command.""" 27 | 28 | 29 | class UploadCommand(unittest.TestCase): 30 | """Check registered all_commands.""" 31 | 32 | def setUp(self): 33 | self.cmd = upload.Upload() 34 | self.branch = mock.MagicMock() 35 | self.people = mock.MagicMock() 36 | self.opt, _ = self.cmd.OptionParser.parse_args([]) 37 | mock.patch.object( 38 | self.cmd, "_AppendAutoList", return_value=None 39 | ).start() 40 | mock.patch.object(self.cmd, "git_event_log").start() 41 | 42 | def tearDown(self): 43 | mock.patch.stopall() 44 | 45 | def test_UploadAndReport_UploadError(self): 46 | """Check UploadExitError raised when UploadError encountered.""" 47 | side_effect = UploadError("upload error") 48 | with mock.patch.object( 49 | self.cmd, "_UploadBranch", side_effect=side_effect 50 | ): 51 | with self.assertRaises(upload.UploadExitError): 52 | self.cmd._UploadAndReport(self.opt, [self.branch], self.people) 53 | 54 | def test_UploadAndReport_GitError(self): 55 | """Check UploadExitError raised when GitError encountered.""" 56 | side_effect = GitError("some git error") 57 | with mock.patch.object( 58 | self.cmd, "_UploadBranch", side_effect=side_effect 59 | ): 60 | with self.assertRaises(upload.UploadExitError): 61 | self.cmd._UploadAndReport(self.opt, [self.branch], self.people) 62 | 63 | def test_UploadAndReport_UnhandledError(self): 64 | """Check UnexpectedError passed through.""" 65 | side_effect = UnexpectedError("some os error") 66 | with mock.patch.object( 67 | self.cmd, "_UploadBranch", side_effect=side_effect 68 | ): 69 | with self.assertRaises(type(side_effect)): 70 | self.cmd._UploadAndReport(self.opt, [self.branch], self.people) 71 | -------------------------------------------------------------------------------- /tests/test_update_manpages.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Unittests for the update_manpages module.""" 16 | 17 | import unittest 18 | 19 | from release import update_manpages 20 | 21 | 22 | class UpdateManpagesTest(unittest.TestCase): 23 | """Tests the update-manpages code.""" 24 | 25 | def test_replace_regex(self): 26 | """Check that replace_regex works.""" 27 | data = "\n\033[1mSummary\033[m\n" 28 | self.assertEqual(update_manpages.replace_regex(data), "\nSummary\n") 29 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # https://tox.readthedocs.io/ 16 | 17 | [tox] 18 | envlist = lint, py36, py37, py38, py39, py310, py311, py312 19 | requires = virtualenv<20.22.0 20 | 21 | [gh-actions] 22 | python = 23 | 3.6: py36 24 | 3.7: py37 25 | 3.8: py38 26 | 3.9: py39 27 | 3.10: py310 28 | 3.11: py311 29 | 3.12: py312 30 | 31 | [testenv] 32 | deps = 33 | -c constraints.txt 34 | black 35 | flake8 36 | isort 37 | pytest 38 | pytest-timeout 39 | commands = {envpython} run_tests {posargs} 40 | setenv = 41 | GIT_AUTHOR_NAME = Repo test author 42 | GIT_COMMITTER_NAME = Repo test committer 43 | EMAIL = repo@gerrit.nodomain 44 | 45 | [testenv:lint] 46 | skip_install = true 47 | deps = 48 | -c constraints.txt 49 | black 50 | flake8 51 | commands = 52 | black --check {posargs:. repo run_tests release/update-hooks release/update-manpages} 53 | flake8 54 | 55 | [testenv:format] 56 | skip_install = true 57 | deps = 58 | -c constraints.txt 59 | black 60 | flake8 61 | commands = 62 | black {posargs:. repo run_tests release/update-hooks release/update-manpages} 63 | flake8 64 | -------------------------------------------------------------------------------- /wrapper.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import functools 16 | import importlib.machinery 17 | import importlib.util 18 | import os 19 | 20 | 21 | def WrapperDir(): 22 | return os.path.dirname(__file__) 23 | 24 | 25 | def WrapperPath(): 26 | return os.path.join(WrapperDir(), "repo") 27 | 28 | 29 | @functools.lru_cache(maxsize=None) 30 | def Wrapper(): 31 | modname = "wrapper" 32 | loader = importlib.machinery.SourceFileLoader(modname, WrapperPath()) 33 | spec = importlib.util.spec_from_loader(modname, loader) 34 | module = importlib.util.module_from_spec(spec) 35 | loader.exec_module(module) 36 | return module 37 | --------------------------------------------------------------------------------